DIRKRB-689 Create principals and export keytabs with host and role.
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java
new file mode 100644
index 0000000..882b10f
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java
@@ -0,0 +1,35 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *
+ */
+package org.apache.kerby.has.common;
+
+import java.io.File;
+
+/**
+ * Server side admin facilities from remote, similar to MIT kadmin remote mode.
+ */
+public interface Hadmin {
+
+
+    String addPrincByRole(String host, String role) throws HasException;
+
+    File getKeytabByHostAndRole(String host, String role) throws HasException;
+
+    void getHostRoles();
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java
new file mode 100644
index 0000000..4661d87
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java
@@ -0,0 +1,140 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.kerby.has.server.admin;
+
+import org.apache.kerby.has.common.Hadmin;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.has.server.web.HostRoleType;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadmin;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadminImpl;
+import org.apache.kerby.kerberos.kerb.identity.backend.BackendConfig;
+import org.apache.kerby.kerberos.kerb.server.KdcConfig;
+import org.apache.kerby.kerberos.kerb.server.KdcSetting;
+import org.apache.kerby.kerberos.kerb.server.KdcUtil;
+import org.apache.kerby.kerberos.kerb.server.ServerSetting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class LocalHasAdmin implements Hadmin {
+    public static final Logger LOG = LoggerFactory.getLogger(LocalHasAdmin.class);
+
+    private final ServerSetting serverSetting;
+    private LocalKadmin kadmin;
+
+    public LocalHasAdmin(HasServer hasServer) throws KrbException {
+        if (hasServer.getKdcServer() == null) {
+            throw new RuntimeException("Could not get HAS KDC server, please start KDC first.");
+        }
+        this.serverSetting = hasServer.getKdcServer().getKdcSetting();
+
+        kadmin = new LocalKadminImpl(serverSetting);
+    }
+
+    /**
+     * Construct with prepared conf dir.
+     *
+     * @param confDir The path of conf dir
+     * @throws KrbException e
+     */
+    public LocalHasAdmin(File confDir) throws KrbException {
+        KdcConfig tmpKdcConfig = KdcUtil.getKdcConfig(confDir);
+        if (tmpKdcConfig == null) {
+            tmpKdcConfig = new KdcConfig();
+        }
+
+        BackendConfig tmpBackendConfig = KdcUtil.getBackendConfig(confDir);
+        if (tmpBackendConfig == null) {
+            tmpBackendConfig = new BackendConfig();
+        }
+
+        this.serverSetting = new KdcSetting(tmpKdcConfig, tmpBackendConfig);
+        kadmin = new LocalKadminImpl(serverSetting);
+    }
+
+    @Override
+    public String addPrincByRole(String host, String role) throws HasException {
+        String result = "";
+        String realm = "/" + host + "@" + kadmin.getKdcConfig().getKdcRealm();
+        String[] princs = HostRoleType.valueOf(role).getPrincs();
+        if (princs == null) {
+            LOG.error("Cannot find the role of : " + role);
+            return "Cannot find the role of : " + role;
+        }
+        for (String princ : princs) {
+            try {
+                kadmin.addPrincipal(princ + realm);
+                LOG.info("Success to add princ :" + princ + realm);
+                result = result + "Success to add princ :" + princ + realm + "\n";
+            } catch (KrbException e) {
+                LOG.info(e.getMessage());
+                result = e.getMessage() + "\n";
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public File getKeytabByHostAndRole(String host, String role) throws HasException {
+        String realm = "/" + host + "@" + kadmin.getKdcConfig().getKdcRealm();
+        File path = new File("/tmp/" + System.currentTimeMillis());
+        path.mkdirs();
+        File keytab = new File(path, role + "-" + host + ".keytab");
+        if (keytab.exists()) {
+            keytab.delete();
+        }
+        String[] princs = HostRoleType.valueOf(role).getPrincs();
+        for (String princ : princs) {
+            try {
+                if (kadmin.getPrincipal(princ + realm) == null) {
+                    continue;
+                }
+            } catch (KrbException e) {
+                throw new HasException(e);
+            }
+            try {
+                kadmin.exportKeytab(keytab, princ + realm);
+            } catch (KrbException e) {
+                throw new HasException(e);
+            }
+        }
+        return keytab;
+    }
+
+    @Override
+    public void getHostRoles() {
+        for (HostRoleType role : HostRoleType.values()) {
+            System.out.print("\tHostRole: " + role.getName()
+                + ", PrincipalNames: ");
+            String[] princs = role.getPrincs();
+            for (int j = 0; j < princs.length; j++) {
+                System.out.print(princs[j]);
+                if (j == princs.length - 1) {
+                    System.out.println();
+                } else {
+                    System.out.print(", ");
+                }
+            }
+        }
+    }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java
new file mode 100644
index 0000000..104a41f
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public enum HostRoleType {
+    HDFS("HDFS", new String[]{"HTTP", "hdfs"}),
+    YARN("YARN", new String[]{"yarn"}),
+    MAPRED("MAPRED", new String[]{"mapred"}),
+    HBASE("HBASE", new String[]{"hbase"}),
+    ZOOKEEPER("ZOOKEEPER", new String[]{"zookeeper"}),
+    SPARK("SPARK", new String[]{"spark"}),
+    HIVE("HIVE", new String[]{"hive"}),
+    OOZIE("OOZIE", new String[]{"oozie"}),
+    HUE("HUE", new String[]{"hue"});
+
+    private String name;
+    private String[] princs;
+
+    HostRoleType(String name, String[] princs) {
+        this.name = name;
+        this.princs = princs;
+    }
+
+    public String[] getPrincs() {
+        return princs;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java
new file mode 100644
index 0000000..c769645
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java
@@ -0,0 +1,215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest;
+
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.has.server.admin.LocalHasAdmin;
+import org.apache.kerby.has.server.web.HostRoleType;
+import org.apache.kerby.has.server.web.WebServer;
+import org.apache.kerby.has.server.web.rest.param.HostParam;
+import org.apache.kerby.has.server.web.rest.param.HostRoleParam;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONObject;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * HAS Admin web methods implementation.
+ */
+@Path("/admin")
+public class HadminApi {
+
+    @Context
+    private ServletContext context;
+
+    @Context
+    private HttpServletRequest httpRequest;
+
+    private void compressFile(File file, ZipOutputStream out, String basedir) {
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+            ZipEntry entry = new ZipEntry(basedir + file.getName());
+            out.putNextEntry(entry);
+            int count;
+            byte[] data = new byte[8192];
+            while ((count = bis.read(data, 0, 8192)) != -1) {
+                out.write(data, 0, count);
+            }
+            bis.close();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @PUT
+    @Path("/addprincipalsbyrole")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addprincipalsbyrole(@Context HttpServletRequest request) {
+        if (httpRequest.isSecure()) {
+            LocalHasAdmin hasAdmin = null;
+            try {
+                hasAdmin = new LocalHasAdmin(WebServer.getHasServerFromContext(context));
+            } catch (KrbException e) {
+                WebServer.LOG.info("Failed to create local hadmin." + e.getMessage());
+            }
+            JSONObject result = new JSONObject();
+            String msg = "";
+            try {
+                StringBuilder data = new StringBuilder();
+                BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
+                String s;
+                while ((s = br.readLine()) != null) {
+                    data.append(s);
+                }
+                WebServer.LOG.info("Request to create principals by JSON : \n" + data.toString());
+                JSONArray hostArray = new JSONObject(data.toString()).optJSONArray("HOSTS");
+                for (int i = 0; i < hostArray.length(); i++) {
+                    JSONObject host = (JSONObject) hostArray.get(i);
+                    String[] roles = host.getString("hostRoles").split(",");
+                    for (String role : roles) {
+                        msg += hasAdmin.addPrincByRole(host.getString("name"), role.toUpperCase());
+                    }
+                }
+                result.put("result", "success");
+                result.put("msg", msg);
+                return Response.ok(result.toString()).build();
+            } catch (Exception e) {
+                WebServer.LOG.error("Failed to create principals,because : " + e.getMessage());
+                msg = "Failed to create principals,because : " + e.getMessage();
+                return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+            }
+        }
+        return Response.status(Response.Status.FORBIDDEN).entity("HTTPS required.\n").build();
+    }
+
+    /**
+     * @param host Hadoop node
+     * @param role Hadoop role
+     * @return Response
+     */
+    @GET
+    @Path("/exportKeytabsbyrole")
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response exportKeytabsbyrole(@QueryParam(HostParam.NAME) @DefaultValue(HostParam.DEFAULT)
+                                  final HostParam host,
+                                  @QueryParam(HostRoleParam.NAME) @DefaultValue(HostRoleParam.DEFAULT)
+                                  final HostRoleParam role) {
+        if (httpRequest.isSecure()) {
+            WebServer.LOG.info("Request to export keytabs.");
+            String msg;
+            LocalHasAdmin hasAdmin;
+            HasServer hasServer;
+            try {
+                hasServer = WebServer.getHasServerFromContext(context);
+                hasAdmin = new LocalHasAdmin(hasServer);
+            } catch (KrbException e) {
+                msg = "Failed to create local hadmin." + e.getMessage();
+                WebServer.LOG.error(msg);
+                return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+            }
+            if (host.getValue() != null) {
+                if (role.getValue() != null) {
+                    try {
+                        File file = hasAdmin.getKeytabByHostAndRole(host.getValue(), role.getValue());
+                        WebServer.LOG.info("Create keytab file for the " + role.getValue()
+                            + " for " + host.getValue());
+                        return Response.ok(file).header("Content-Disposition",
+                            "attachment; filename=" + role.getValue() + "-"
+                                + host.getValue() + ".keytab").build();
+                    } catch (HasException e) {
+                        msg = "Failed to export keytab File because : " + e.getMessage();
+                        WebServer.LOG.error(msg);
+                        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+                    }
+                } else {
+                    //export keytabs zip file
+                    List<File> keytabs = new ArrayList<>();
+                    for (HostRoleType r : HostRoleType.values()) {
+                        try {
+                            keytabs.add(hasAdmin.getKeytabByHostAndRole(host.getValue(), r.getName()));
+                            WebServer.LOG.info("Create keytab file for the " + r.getName()
+                                + " for " + host.getValue());
+                        } catch (HasException e) {
+                            msg = "Failed to export keytab File because : " + e.getMessage();
+                            WebServer.LOG.error(msg);
+                            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+                        }
+                    }
+                    if (keytabs.size() < 1) {
+                        msg = "Failed to get the keytab from backend.";
+                        WebServer.LOG.error(msg);
+                        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+                    }
+                    File path = new File(hasServer.getWorkDir(), "tmp/zip/"
+                        + System.currentTimeMillis());
+                    path.mkdirs();
+                    File keytabZip = new File(path, "keytab.zip");
+                    if (keytabZip.exists()) {
+                        keytabZip.delete();
+                    }
+                    try {
+                        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(keytabZip));
+                        for (File keytab : keytabs) {
+                            compressFile(keytab, zos, "");
+                        }
+                        zos.close();
+                        WebServer.LOG.info("Success to create the keytab.zip.");
+                        return Response.ok(keytabZip).header("Content-Disposition",
+                            "attachment; filename=keytab.zip").build();
+                    } catch (Exception e) {
+                        msg = "Failed to create the keytab.zip,because : " + e.getMessage();
+                        WebServer.LOG.error(msg);
+                        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+                    }
+                }
+            } else {
+                msg = "The host value is empty.";
+                WebServer.LOG.error(msg);
+                return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
+            }
+        }
+        return Response.status(Response.Status.FORBIDDEN).entity("HTTPS required.\n").build();
+    }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java
new file mode 100644
index 0000000..acf0306
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+public class HostParam extends StringParam {
+  /**
+   * Parameter name.
+   */
+  public static final String NAME = "host";
+  /**
+   * Default parameter value.
+   */
+  public static final String DEFAULT = "";
+
+  private static final Domain DOMAIN = new Domain(NAME, null);
+
+  /**
+   * Constructor.
+   *
+   * @param str a string representation of the parameter value.
+   */
+  public HostParam(final String str) {
+    super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java
new file mode 100644
index 0000000..72706ff
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+public class HostRoleParam extends StringParam {
+  /**
+   * Parameter name.
+   */
+  public static final String NAME = "role";
+  /**
+   * Default parameter value.
+   */
+  public static final String DEFAULT = "";
+
+  private static final Domain DOMAIN = new Domain(NAME, null);
+
+  /**
+   * Constructor.
+   *
+   * @param str a string representation of the parameter value.
+   */
+  public HostRoleParam(final String str) {
+    super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+}