A user interface for viewing and administering clusters
Project: http://git-wip-us.apache.org/repos/asf/helix/repo Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/6cbafef0 Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/6cbafef0 Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/6cbafef0 Branch: refs/heads/master Commit: 6cbafef0351e9957b18c6fcbfe60ad1b4c8de94f Parents: 9087ce0 Author: Greg Brandt <[email protected]> Authored: Sat Mar 28 09:56:14 2015 -0700 Committer: Greg Brandt <[email protected]> Committed: Sat Mar 28 09:56:14 2015 -0700 ---------------------------------------------------------------------- helix-ui/.gitignore | 22 ++ helix-ui/README.md | 77 +++++++ helix-ui/doc/quickstart-admin-instances.png | Bin 0 -> 80350 bytes helix-ui/doc/quickstart-admin-resource.png | Bin 0 -> 74829 bytes helix-ui/doc/quickstart-end-table.png | Bin 0 -> 135259 bytes helix-ui/doc/quickstart-end-visualizer.png | Bin 0 -> 116886 bytes helix-ui/pom.xml | 128 ++++++++++++ helix-ui/sample-admin-server.yml | 12 ++ helix-ui/sample-server.yml | 7 + .../org/apache/helix/ui/HelixUIApplication.java | 74 +++++++ .../ui/HelixUIApplicationConfiguration.java | 30 +++ .../apache/helix/ui/api/ClusterConnection.java | 22 ++ .../org/apache/helix/ui/api/ClusterSpec.java | 33 +++ .../org/apache/helix/ui/api/ConfigTableRow.java | 49 +++++ .../helix/ui/api/D3ResourceCoCentricCircle.java | 116 +++++++++++ .../org/apache/helix/ui/api/IdealStateSpec.java | 94 +++++++++ .../org/apache/helix/ui/api/InstanceSpec.java | 32 +++ .../org/apache/helix/ui/api/ResourceSpec.java | 31 +++ .../apache/helix/ui/api/ResourceStateSpec.java | 68 ++++++ .../helix/ui/api/ResourceStateTableRow.java | 69 +++++++ .../ui/health/ClusterConnectionHealthCheck.java | 24 +++ .../apache/helix/ui/resource/AdminResource.java | 181 ++++++++++++++++ .../helix/ui/resource/DashboardResource.java | 164 +++++++++++++++ .../helix/ui/resource/VisualizerResource.java | 53 +++++ .../apache/helix/ui/task/ClearClientCache.java | 25 +++ .../helix/ui/task/ClearDataCacheTask.java | 25 +++ .../org/apache/helix/ui/util/ClientCache.java | 100 +++++++++ .../org/apache/helix/ui/util/DataCache.java | 188 +++++++++++++++++ .../ui/util/DropWizardApplicationRunner.java | 86 ++++++++ .../helix/ui/util/ZkAddressValidator.java | 37 ++++ .../org/apache/helix/ui/view/ClusterView.java | 85 ++++++++ .../org/apache/helix/ui/view/LandingView.java | 9 + .../org/apache/helix/ui/view/ResourceView.java | 103 +++++++++ helix-ui/src/main/resources/assets/css/app.css | 92 +++++++++ .../assets/css/uikit.almost-flat.min.css | 2 + .../main/resources/assets/fonts/FontAwesome.otf | Bin 0 -> 85908 bytes .../assets/fonts/fontawesome-webfont.eot | Bin 0 -> 56006 bytes .../assets/fonts/fontawesome-webfont.ttf | Bin 0 -> 112160 bytes .../assets/fonts/fontawesome-webfont.woff | Bin 0 -> 65452 bytes .../main/resources/assets/img/helix-logo.png | Bin 0 -> 17992 bytes helix-ui/src/main/resources/assets/js/admin.js | 207 +++++++++++++++++++ helix-ui/src/main/resources/assets/js/app.js | 103 +++++++++ .../assets/js/components/accordion.min.js | 2 + .../assets/js/components/autocomplete.min.js | 2 + .../resources/assets/js/components/cover.min.js | 2 + .../assets/js/components/datepicker.min.js | 3 + .../assets/js/components/form-password.min.js | 2 + .../assets/js/components/form-select.min.js | 2 + .../resources/assets/js/components/grid.min.js | 2 + .../assets/js/components/htmleditor.min.js | 2 + .../assets/js/components/lightbox.min.js | 2 + .../assets/js/components/nestable.min.js | 2 + .../assets/js/components/notify.min.js | 2 + .../assets/js/components/pagination.min.js | 2 + .../assets/js/components/search.min.js | 2 + .../assets/js/components/slideshow-fx.min.js | 2 + .../assets/js/components/slideshow.min.js | 2 + .../assets/js/components/sortable.min.js | 2 + .../assets/js/components/sticky.min.js | 2 + .../assets/js/components/timepicker.min.js | 2 + .../assets/js/components/upload.min.js | 2 + helix-ui/src/main/resources/assets/js/d3.min.js | 5 + .../resources/assets/js/jquery-1.11.2.min.js | 4 + .../main/resources/assets/js/landing-view.js | 6 + .../resources/assets/js/resource-state-table.js | 55 +++++ .../main/resources/assets/js/resource-table.js | 43 ++++ .../src/main/resources/assets/js/uikit.min.js | 3 + .../src/main/resources/assets/js/visualizer.js | 90 ++++++++ .../org/apache/helix/ui/view/cluster-view.ftl | 61 ++++++ .../helix/ui/view/common/cluster-admin.ftl | 5 + .../helix/ui/view/common/config-table.ftl | 24 +++ .../org/apache/helix/ui/view/common/css.ftl | 2 + .../helix/ui/view/common/ideal-state-table.ftl | 34 +++ .../helix/ui/view/common/instance-admin.ftl | 8 + .../helix/ui/view/common/instance-table.ftl | 38 ++++ .../org/apache/helix/ui/view/common/js.ftl | 6 + .../helix/ui/view/common/resource-admin.ftl | 39 ++++ .../ui/view/common/resource-state-table.ftl | 35 ++++ .../helix/ui/view/common/resource-table.ftl | 35 ++++ .../ui/view/common/resource-visualizer.ftl | 10 + .../apache/helix/ui/view/common/side-nav.ftl | 21 ++ .../org/apache/helix/ui/view/landing-view.ftl | 21 ++ .../org/apache/helix/ui/view/resource-view.ftl | 49 +++++ pom.xml | 1 + 84 files changed, 2982 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/.gitignore ---------------------------------------------------------------------- diff --git a/helix-ui/.gitignore b/helix-ui/.gitignore new file mode 100644 index 0000000..12b5d6b --- /dev/null +++ b/helix-ui/.gitignore @@ -0,0 +1,22 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# intellij +.idea +*.ipr +*.iws +*.iml + +# maven +target +dependency-reduced-pom.xml http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/README.md ---------------------------------------------------------------------- diff --git a/helix-ui/README.md b/helix-ui/README.md new file mode 100644 index 0000000..63373eb --- /dev/null +++ b/helix-ui/README.md @@ -0,0 +1,77 @@ +# helix-ui + +After building the project from the root directory (i.e. `./build`), find the +`helix-ui-${version}.jar` artifact in this module's `target` directory. + +To run the UI server in read-only mode with no configuration, execute the +following commands: + +``` +java -jar helix-ui-${version}.jar server +``` + +Navigate to `http://localhost:8080/dashboard` to get started. At this page, +enter a ZooKeeper address, e.g. "localhost:2181,localhost:2182" or +"some-machine:2181/chroot", to get started. + +The following shows using the dashboard to view the end state of MyResource in +the Quick Start, in tabular form: + + + +And using visualization: + + + +## Admin + +In order to run the server in admin mode, set `adminMode: true` in the +application configuration. + +To restrict the ZooKeeper machines that the UI will try to connect to, use the +`zkAddresses` configuration parameter. The application will never try to +connect using a ZooKeeper connection string that contains machines not in that +list. + +When the server is configured to run in admin mode, several buttons to perform +actions like add / drop resource, add / enable / disable / drop instance, etc. +are rendered on the UI at appropriate locations. + +The following shows the admin resource view: + + + +And the following shows the admin instance view: + + + +## Configuration + +For example, the following configuration runs the server in admin mode, only +connecting to "localhost:2181", on ports 60000 for normal application traffic, +and 60001 for admin actions: + +``` +adminMode: true + +zkAddresses: + - "localhost:2181" + +server: + applicationConnectors: + - type: http + port: 60000 + adminConnectors: + - type: http + port: 60001 +``` + +If this configuration exists in a file named `/tmp/my-config.yml`, one would +run the server in the following way: + +``` +java -jar helix-ui-${version}.jar server /tmp/my-config.yml +``` + +For more details on configuration, see [Dropwizard Configuration +Reference](https://dropwizard.github.io/dropwizard/manual/configuration.html) http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/doc/quickstart-admin-instances.png ---------------------------------------------------------------------- diff --git a/helix-ui/doc/quickstart-admin-instances.png b/helix-ui/doc/quickstart-admin-instances.png new file mode 100644 index 0000000..cc23f15 Binary files /dev/null and b/helix-ui/doc/quickstart-admin-instances.png differ http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/doc/quickstart-admin-resource.png ---------------------------------------------------------------------- diff --git a/helix-ui/doc/quickstart-admin-resource.png b/helix-ui/doc/quickstart-admin-resource.png new file mode 100644 index 0000000..d11bad4 Binary files /dev/null and b/helix-ui/doc/quickstart-admin-resource.png differ http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/doc/quickstart-end-table.png ---------------------------------------------------------------------- diff --git a/helix-ui/doc/quickstart-end-table.png b/helix-ui/doc/quickstart-end-table.png new file mode 100644 index 0000000..ce5472d Binary files /dev/null and b/helix-ui/doc/quickstart-end-table.png differ http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/doc/quickstart-end-visualizer.png ---------------------------------------------------------------------- diff --git a/helix-ui/doc/quickstart-end-visualizer.png b/helix-ui/doc/quickstart-end-visualizer.png new file mode 100644 index 0000000..1424992 Binary files /dev/null and b/helix-ui/doc/quickstart-end-visualizer.png differ http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/pom.xml ---------------------------------------------------------------------- diff --git a/helix-ui/pom.xml b/helix-ui/pom.xml new file mode 100644 index 0000000..c200cb2 --- /dev/null +++ b/helix-ui/pom.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <parent> + <groupId>org.apache.helix</groupId> + <artifactId>helix</artifactId> + <version>0.7.2-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>helix-ui</artifactId> + <packaging>jar</packaging> + <name>Apache Helix :: UI</name> + <url>http://maven.apache.org</url> + + <properties> + <dropwizard.version>0.8.0</dropwizard.version> + <osgi.import> + javax.management*, + org.apache.commons.math*;version="[2.1,3)", + org.apache.log4j*;version="[1.2,2)", + * + </osgi.import> + <osgi.ignore> + org.apache.helix.tools* + </osgi.ignore> + </properties> + + <dependencies> + <dependency> + <groupId>io.dropwizard</groupId> + <artifactId>dropwizard-core</artifactId> + <version>${dropwizard.version}</version> + </dependency> + <dependency> + <groupId>io.dropwizard</groupId> + <artifactId>dropwizard-assets</artifactId> + <version>${dropwizard.version}</version> + </dependency> + <dependency> + <groupId>io.dropwizard</groupId> + <artifactId>dropwizard-views-freemarker</artifactId> + <version>${dropwizard.version}</version> + </dependency> + <dependency> + <groupId>org.apache.helix</groupId> + <artifactId>helix-core</artifactId> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <createDependencyReducedPom>true</createDependencyReducedPom> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>org.apache.helix.ui.HelixUIApplication</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>2.4</version> + <configuration> + <archive> + <manifest> + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/sample-admin-server.yml ---------------------------------------------------------------------- diff --git a/helix-ui/sample-admin-server.yml b/helix-ui/sample-admin-server.yml new file mode 100644 index 0000000..cd4b924 --- /dev/null +++ b/helix-ui/sample-admin-server.yml @@ -0,0 +1,12 @@ +adminMode: true + +zkAddresses: + - "localhost:2181" + +server: + applicationConnectors: + - type: http + port: 60000 + adminConnectors: + - type: http + port: 60001 http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/sample-server.yml ---------------------------------------------------------------------- diff --git a/helix-ui/sample-server.yml b/helix-ui/sample-server.yml new file mode 100644 index 0000000..5ee5933 --- /dev/null +++ b/helix-ui/sample-server.yml @@ -0,0 +1,7 @@ +server: + applicationConnectors: + - type: http + port: 50000 + adminConnectors: + - type: http + port: 50001 http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplication.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplication.java b/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplication.java new file mode 100644 index 0000000..aad605b --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplication.java @@ -0,0 +1,74 @@ +package org.apache.helix.ui; + +import com.google.common.collect.ImmutableMap; +import io.dropwizard.Application; +import io.dropwizard.assets.AssetsBundle; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; +import io.dropwizard.views.ViewBundle; +import org.apache.helix.ui.health.ClusterConnectionHealthCheck; +import org.apache.helix.ui.resource.AdminResource; +import org.apache.helix.ui.resource.DashboardResource; +import org.apache.helix.ui.resource.VisualizerResource; +import org.apache.helix.ui.task.ClearClientCache; +import org.apache.helix.ui.task.ClearDataCacheTask; +import org.apache.helix.ui.util.ClientCache; +import org.apache.helix.ui.util.DataCache; +import org.apache.helix.ui.util.ZkAddressValidator; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; + +public class HelixUIApplication extends Application<HelixUIApplicationConfiguration> { + @Override + public String getName() { + return "helix-ui"; + } + + @Override + public void initialize(Bootstrap<HelixUIApplicationConfiguration> bootstrap) { + bootstrap.addBundle(new ViewBundle<HelixUIApplicationConfiguration>() { + @Override + public ImmutableMap<String, ImmutableMap<String, String>> getViewConfiguration(HelixUIApplicationConfiguration config) { + return config.getViewRendererConfiguration(); + } + }); + bootstrap.addBundle(new AssetsBundle("/assets/css", "/assets/css", null, "css")); + bootstrap.addBundle(new AssetsBundle("/assets/js", "/assets/js", null, "js")); + bootstrap.addBundle(new AssetsBundle("/assets/img", "/assets/img", null, "img")); + bootstrap.addBundle(new AssetsBundle("/assets/fonts", "/assets/fonts", null, "fonts")); + } + + @Override + public void run(HelixUIApplicationConfiguration config, Environment environment) throws Exception { + final ZkAddressValidator zkAddressValidator = new ZkAddressValidator(config.getZkAddresses()); + final ClientCache clientCache = new ClientCache(zkAddressValidator); + + // Close all connections when application stops + environment.lifecycle().addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() { + @Override + public void lifeCycleStopping(LifeCycle event) { + clientCache.invalidateAll(); + } + }); + + DataCache dataCache = new DataCache(clientCache); + + + DashboardResource dashboardResource + = new DashboardResource(clientCache, dataCache, config.isAdminMode()); + + environment.healthChecks().register("clusterConnection", new ClusterConnectionHealthCheck(clientCache)); + environment.jersey().register(dashboardResource); + environment.jersey().register(new VisualizerResource(clientCache, dataCache)); + environment.admin().addTask(new ClearDataCacheTask(dataCache)); + environment.admin().addTask(new ClearClientCache(clientCache)); + + if (config.isAdminMode()) { + environment.jersey().register(new AdminResource(clientCache, dataCache)); + } + } + + public static void main(String[] args) throws Exception { + new HelixUIApplication().run(args); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplicationConfiguration.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplicationConfiguration.java b/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplicationConfiguration.java new file mode 100644 index 0000000..e2f433e --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/HelixUIApplicationConfiguration.java @@ -0,0 +1,30 @@ +package org.apache.helix.ui; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableMap; +import io.dropwizard.Configuration; + +import javax.validation.constraints.NotNull; +import java.util.Set; + +public class HelixUIApplicationConfiguration extends Configuration { + @NotNull + private ImmutableMap<String, ImmutableMap<String, String>> viewRendererConfiguration = ImmutableMap.of(); + + private boolean adminMode = false; + + private Set<String> zkAddresses; + + @JsonProperty("viewRendererConfiguration") + public ImmutableMap<String, ImmutableMap<String, String>> getViewRendererConfiguration() { + return viewRendererConfiguration; + } + + public boolean isAdminMode() { + return adminMode; + } + + public Set<String> getZkAddresses() { + return zkAddresses; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterConnection.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterConnection.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterConnection.java new file mode 100644 index 0000000..94af4cf --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterConnection.java @@ -0,0 +1,22 @@ +package org.apache.helix.ui.api; + +import org.apache.helix.manager.zk.ZkClient; +import org.apache.helix.tools.ClusterSetup; + +public class ClusterConnection { + private final ZkClient zkClient; + private final ClusterSetup clusterSetup; + + public ClusterConnection(ZkClient zkClient) { + this.zkClient = zkClient; + this.clusterSetup = new ClusterSetup(zkClient); + } + + public ZkClient getZkClient() { + return zkClient; + } + + public ClusterSetup getClusterSetup() { + return clusterSetup; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterSpec.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterSpec.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterSpec.java new file mode 100644 index 0000000..1e09615 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ClusterSpec.java @@ -0,0 +1,33 @@ +package org.apache.helix.ui.api; + +public class ClusterSpec { + private final String zkAddress; + private final String clusterName; + + public ClusterSpec(String zkAddress, String clusterName) { + this.zkAddress = zkAddress; + this.clusterName = clusterName; + } + + public String getZkAddress() { + return zkAddress; + } + + public String getClusterName() { + return clusterName; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ClusterSpec)) { + return false; + } + ClusterSpec c = (ClusterSpec) o; + return c.zkAddress.equals(zkAddress) && c.clusterName.equals(clusterName); + } + + @Override + public int hashCode() { + return zkAddress.hashCode() + 13 * clusterName.hashCode(); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ConfigTableRow.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ConfigTableRow.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ConfigTableRow.java new file mode 100644 index 0000000..bd423f5 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ConfigTableRow.java @@ -0,0 +1,49 @@ +package org.apache.helix.ui.api; + +public class ConfigTableRow implements Comparable<ConfigTableRow> { + private final String scope; + private final String entity; + private final String name; + private final String value; + + public ConfigTableRow(String scope, + String entity, + String name, + String value) throws Exception { + this.scope = scope; + this.entity = entity; + this.name = name; + this.value = value; + } + + public String getScope() { + return scope; + } + + public String getEntity() { + return entity; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public int compareTo(ConfigTableRow o) { + int nameResult = name.compareTo(o.getName()); + if (nameResult != 0) { + return nameResult; + } + + int valueResult = value.compareTo(o.getValue()); + if (valueResult != 0) { + return valueResult; + } + + return 0; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/D3ResourceCoCentricCircle.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/D3ResourceCoCentricCircle.java b/helix-ui/src/main/java/org/apache/helix/ui/api/D3ResourceCoCentricCircle.java new file mode 100644 index 0000000..b9548bf --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/D3ResourceCoCentricCircle.java @@ -0,0 +1,116 @@ +package org.apache.helix.ui.api; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class D3ResourceCoCentricCircle { + + public enum CircleType { + CLUSTER, + INSTANCE, + PARTITION + } + + private final String name; + private final String parentName; + private final String state; + private final int size; + private final CircleType circleType; + private final Set<D3ResourceCoCentricCircle> children; + + public D3ResourceCoCentricCircle(String name, + String parentName, + String state, + int size, + CircleType circleType, + Set<D3ResourceCoCentricCircle> children) { + this.name = name; + this.parentName = parentName; + this.state = state; + this.size = size; + this.circleType = circleType; + this.children = children; + } + + public String getName() { + return name; + } + + public String getParentName() { + return parentName; + } + + public String getState() { + return state; + } + + public int getSize() { + return size; + } + + public CircleType getCircleType() { + return circleType; + } + + public Set<D3ResourceCoCentricCircle> getChildren() { + return children; + } + + public static D3ResourceCoCentricCircle fromResourceStateSpec(ResourceStateSpec resourceStateSpec) { + Map<String, Set<D3ResourceCoCentricCircle>> partitionByInstance + = new HashMap<String, Set<D3ResourceCoCentricCircle>>(); + + // Group by instance (first level) + for (ResourceStateTableRow row : resourceStateSpec.getResourceStateTable()) { + Set<D3ResourceCoCentricCircle> partitionCircles = partitionByInstance.get(row.getInstanceName()); + if (partitionCircles == null) { + partitionCircles = new HashSet<D3ResourceCoCentricCircle>(); + partitionByInstance.put(row.getInstanceName(), partitionCircles); + } + partitionCircles.add(new D3ResourceCoCentricCircle( + row.getPartitionName(), + row.getInstanceName(), + row.getExternal(), + 10, + CircleType.PARTITION, + null)); + } + + // Group into cluster + Set<D3ResourceCoCentricCircle> instanceCircles = new HashSet<D3ResourceCoCentricCircle>(); + for (Map.Entry<String, Set<D3ResourceCoCentricCircle>> entry : partitionByInstance.entrySet()) { + + InstanceSpec instanceSpec = resourceStateSpec.getInstanceSpecs().get(entry.getKey()); + if (instanceSpec == null) { + throw new IllegalStateException("No instance spec for " + entry.getKey()); + } + + String state; + if (!instanceSpec.isLive()) { + state = "DEAD"; + } else if (!instanceSpec.isEnabled()) { + state = "DISABLED"; + } else { + state = "LIVE"; + } + + instanceCircles.add(new D3ResourceCoCentricCircle( + entry.getKey(), + resourceStateSpec.getResource(), + state, + 100, + CircleType.INSTANCE, + entry.getValue())); + } + + return new D3ResourceCoCentricCircle( + resourceStateSpec.getIdealState().getResourceName(), + null, + "", + 900, + CircleType.CLUSTER, + instanceCircles); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/IdealStateSpec.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/IdealStateSpec.java b/helix-ui/src/main/java/org/apache/helix/ui/api/IdealStateSpec.java new file mode 100644 index 0000000..f37b939 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/IdealStateSpec.java @@ -0,0 +1,94 @@ +package org.apache.helix.ui.api; + +import org.apache.helix.model.IdealState; + +import java.util.List; + +public class IdealStateSpec { + private final int numPartitions; + private final String replicas; + private final String instanceGroupTag; + private final int maxPartitionsPerInstance; + private final String rebalanceMode; + private final String rebalancerClassName; + private final String stateModel; + private final int bucketSize; + private final int rebalanceTimerPeriod; + private final boolean batchMessageMode; + + public IdealStateSpec(int numPartitions, + String replicas, + String instanceGroupTag, + int maxPartitionsPerInstance, + String rebalanceMode, + String rebalancerClassName, + String stateModel, + int bucketSize, + int rebalanceTimerPeriod, + boolean batchMessageMode) { + this.numPartitions = numPartitions; + this.replicas = replicas; + this.instanceGroupTag = instanceGroupTag; + this.maxPartitionsPerInstance = maxPartitionsPerInstance; + this.rebalanceMode = rebalanceMode; + this.rebalancerClassName = rebalancerClassName; + this.stateModel = stateModel; + this.bucketSize = bucketSize; + this.rebalanceTimerPeriod = rebalanceTimerPeriod; + this.batchMessageMode = batchMessageMode; + } + + public int getNumPartitions() { + return numPartitions; + } + + public String getReplicas() { + return replicas; + } + + public String getInstanceGroupTag() { + return instanceGroupTag; + } + + public int getMaxPartitionsPerInstance() { + return maxPartitionsPerInstance; + } + + public String getRebalanceMode() { + return rebalanceMode; + } + + public String getRebalancerClassName() { + return rebalancerClassName; + } + + public String getStateModel() { + return stateModel; + } + + public int getBucketSize() { + return bucketSize; + } + + public int getRebalanceTimerPeriod() { + return rebalanceTimerPeriod; + } + + public boolean isBatchMessageMode() { + return batchMessageMode; + } + + public static IdealStateSpec fromIdealState(IdealState idealState) { + return new IdealStateSpec( + idealState.getNumPartitions(), + idealState.getReplicas(), + idealState.getInstanceGroupTag(), + idealState.getMaxPartitionsPerInstance(), + idealState.getRebalanceMode().toString(), + idealState.getRebalancerClassName(), + idealState.getStateModelDefRef(), + idealState.getBucketSize(), + idealState.getRebalanceTimerPeriod(), + idealState.getBatchMessageMode()); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/InstanceSpec.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/InstanceSpec.java b/helix-ui/src/main/java/org/apache/helix/ui/api/InstanceSpec.java new file mode 100644 index 0000000..e87932c --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/InstanceSpec.java @@ -0,0 +1,32 @@ +package org.apache.helix.ui.api; + +public class InstanceSpec implements Comparable<InstanceSpec> { + private final String instanceName; + private final boolean enabled; + private final boolean live; + + public InstanceSpec(String instanceName, + boolean enabled, + boolean live) { + this.instanceName = instanceName; + this.enabled = enabled; + this.live = live; + } + + public String getInstanceName() { + return instanceName; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isLive() { + return live; + } + + @Override + public int compareTo(InstanceSpec o) { + return instanceName.compareTo(o.instanceName); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceSpec.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceSpec.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceSpec.java new file mode 100644 index 0000000..284fbaa --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceSpec.java @@ -0,0 +1,31 @@ +package org.apache.helix.ui.api; + +public class ResourceSpec extends ClusterSpec { + private final String resourceName; + + public ResourceSpec(String zkAddress, String clusterName, String resourceName) { + super(zkAddress, clusterName); + this.resourceName = resourceName; + } + + public String getResourceName() { + return resourceName; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ResourceSpec)) { + return false; + } + ResourceSpec c = (ResourceSpec) o; + return getZkAddress().equals(c.getZkAddress()) + && getClusterName().equals(c.getClusterName()) + && resourceName.equals(c.getResourceName()); + } + + @Override + public int hashCode() { + return super.hashCode() + 27 * resourceName.hashCode(); + } + +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateSpec.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateSpec.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateSpec.java new file mode 100644 index 0000000..08a8f73 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateSpec.java @@ -0,0 +1,68 @@ +package org.apache.helix.ui.api; + +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ResourceStateSpec { + private final String resource; + private final IdealState idealState; + private final ExternalView externalView; + private final Map<String, InstanceSpec> instanceSpecs; + + public ResourceStateSpec(String resource, + IdealState idealState, + ExternalView externalView, + Map<String, InstanceSpec> instanceSpecs) { + this.resource = resource; + this.idealState = idealState; + this.externalView = externalView; + this.instanceSpecs = instanceSpecs; + } + + public String getResource() { + return resource; + } + + public IdealState getIdealState() { + return idealState; + } + + public ExternalView getExternalView() { + return externalView; + } + + public Map<String, InstanceSpec> getInstanceSpecs() { + return instanceSpecs; + } + + public List<ResourceStateTableRow> getResourceStateTable() { + List<ResourceStateTableRow> resourceStateTable = new ArrayList<ResourceStateTableRow>(); + Set<String> partitionNames = idealState.getPartitionSet(); + for (String partitionName : partitionNames) { + Map<String, String> stateMap = idealState.getInstanceStateMap(partitionName); + if (stateMap != null) { + for (Map.Entry<String, String> entry : stateMap.entrySet()) { + String instanceName = entry.getKey(); + String ideal = entry.getValue(); + + String external = null; + if (externalView != null) { + Map<String, String> externalStateMap = externalView.getStateMap(partitionName); + if (externalStateMap != null) { + external = externalStateMap.get(instanceName); + } + } + + resourceStateTable.add(new ResourceStateTableRow(resource, partitionName, instanceName, ideal, external)); + } + } + } + + return resourceStateTable; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateTableRow.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateTableRow.java b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateTableRow.java new file mode 100644 index 0000000..bf0ce08 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/api/ResourceStateTableRow.java @@ -0,0 +1,69 @@ +package org.apache.helix.ui.api; + +public class ResourceStateTableRow implements Comparable<ResourceStateTableRow> { + private static final String NA = "N/A"; + + private final String resourceName; + private final String partitionName; + private final String instanceName; + private final String ideal; + private final String external; + + public ResourceStateTableRow(String resourceName, + String partitionName, + String instanceName, + String ideal, + String external) { + this.resourceName = resourceName; + this.partitionName = partitionName; + this.instanceName = instanceName; + this.ideal = ideal; + this.external = external == null ? NA : external; + } + + public String getResourceName() { + return resourceName; + } + + public String getPartitionName() { + return partitionName; + } + + public String getInstanceName() { + return instanceName; + } + + public String getIdeal() { + return ideal; + } + + public String getExternal() { + return external; + } + + @Override + public int compareTo(ResourceStateTableRow r) { + int partitionResult = partitionName.compareTo(r.getPartitionName()); + if (partitionResult != 0) { + return partitionResult; + } + + int instanceResult = instanceName.compareTo(r.getInstanceName()); + if (instanceResult != 0) { + return instanceResult; + } + + int idealResult = ideal.compareTo(r.getIdeal()); + if (idealResult != 0) { + return idealResult; + } + + int externalResult = external.compareTo(r.getExternal()); + if (externalResult != 0) { + return externalResult; + } + + return 0; + + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/health/ClusterConnectionHealthCheck.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/health/ClusterConnectionHealthCheck.java b/helix-ui/src/main/java/org/apache/helix/ui/health/ClusterConnectionHealthCheck.java new file mode 100644 index 0000000..4c6df61 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/health/ClusterConnectionHealthCheck.java @@ -0,0 +1,24 @@ +package org.apache.helix.ui.health; + +import com.codahale.metrics.health.HealthCheck; +import org.apache.helix.ui.util.ClientCache; + +import java.util.Set; + +public class ClusterConnectionHealthCheck extends HealthCheck { + + private final ClientCache clientCache; + + public ClusterConnectionHealthCheck(ClientCache clientCache) { + this.clientCache = clientCache; + } + + @Override + protected Result check() throws Exception { + Set<String> deadConnections = clientCache.getDeadConnections(); + if (!deadConnections.isEmpty()) { + return Result.unhealthy("Dead connections to " + deadConnections); + } + return Result.healthy(); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/resource/AdminResource.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/resource/AdminResource.java b/helix-ui/src/main/java/org/apache/helix/ui/resource/AdminResource.java new file mode 100644 index 0000000..e6318f2 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/resource/AdminResource.java @@ -0,0 +1,181 @@ +package org.apache.helix.ui.resource; + +import org.apache.helix.manager.zk.ZKUtil; +import org.apache.helix.model.IdealState; +import org.apache.helix.model.InstanceConfig; +import org.apache.helix.tools.ClusterSetup; +import org.apache.helix.ui.api.ClusterConnection; +import org.apache.helix.ui.util.ClientCache; +import org.apache.helix.ui.util.DataCache; + +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +@Path("/admin") +public class AdminResource { + private final ClientCache clientCache; + private final DataCache dataCache; + + public AdminResource(ClientCache clientCache, DataCache dataCache) { + this.clientCache = clientCache; + this.dataCache = dataCache; + } + + @Path("/{zkAddress}/{clusterName}") + @POST + public Response addCluster(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName) throws Exception { + dataCache.invalidate(); + + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + if (clusterSetup.getClusterManagementTool().getClusters().contains(clusterName)) { + return Response.status(Response.Status.CONFLICT).build(); + } + + clusterSetup.addCluster(clusterName, false); + + dataCache.invalidate(); + + return Response.ok().build(); + } + + @Path("/{zkAddress}/{clusterName}") + @DELETE + public Response dropCluster(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName) throws Exception { + dataCache.invalidate(); + + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + if (!clusterSetup.getClusterManagementTool().getClusters().contains(clusterName)) { + throw new NotFoundException(); + } + + clusterSetup.getClusterManagementTool().dropCluster(clusterName); + + return Response.noContent().build(); + } + + @Path("/{zkAddress}/{clusterName}/instances/{instanceName}") + @POST + public Response addInstance(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName, + @PathParam("instanceName") String instanceName, + @QueryParam("disable") boolean disable, + @QueryParam("failIfNoInstance") boolean failIfNoInstance) throws Exception { + dataCache.invalidate(); + + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + List<String> instances = clusterSetup.getClusterManagementTool().getInstancesInCluster(clusterName); + + Response response; + + if (instances.contains(instanceName)) { + response = Response.notModified().build(); + } else if (failIfNoInstance) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } else { + clusterSetup.addInstanceToCluster(clusterName, instanceName); + response = Response.ok().build(); + } + + clusterSetup.getClusterManagementTool().enableInstance(clusterName, instanceName, !disable); + + return response; + } + + @Path("/{zkAddress}/{clusterName}/instances/{instanceName}") + @DELETE + public Response dropInstance(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName, + @PathParam("instanceName") String instanceName) throws Exception { + dataCache.invalidate(); + + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + InstanceConfig instanceConfig + = clusterSetup.getClusterManagementTool().getInstanceConfig(clusterName, instanceName); + + if (instanceConfig == null) { + throw new NotFoundException(); + } else if (instanceConfig.getInstanceEnabled()) { + return Response.status(Response.Status.BAD_REQUEST) + .header("X-Error-Message", "Cannot drop instance that is enabled") + .build(); + } + + clusterSetup.dropInstanceFromCluster(clusterName, instanceName); + + return Response.noContent().build(); + } + + @Path("/{zkAddress}/{clusterName}/resources/{resourceName}/{partitions}/{replicas}") + @POST + public Response addResource(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName, + @PathParam("resourceName") String resourceName, + @PathParam("partitions") int partitions, + @PathParam("replicas") String replicas, + @QueryParam("rebalance") boolean rebalance, + @QueryParam("stateModel") String stateModel, + @QueryParam("rebalanceMode") String rebalanceMode) throws Exception { + dataCache.invalidate(); + + ClusterConnection conn = clientCache.get(zkAddress); + ClusterSetup clusterSetup = conn.getClusterSetup(); + + if (!ZKUtil.isClusterSetup(clusterName, conn.getZkClient())) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + IdealState existingIdealState + = clusterSetup.getClusterManagementTool().getResourceIdealState(clusterName, resourceName); + + IdealState idealState = new IdealState(resourceName); + idealState.setNumPartitions(partitions); + idealState.setReplicas(replicas); + idealState.setStateModelDefRef(stateModel == null + ? "OnlineOffline" : stateModel); + idealState.setRebalanceMode(rebalanceMode == null + ? IdealState.RebalanceMode.FULL_AUTO : IdealState.RebalanceMode.valueOf(rebalanceMode)); + + Response response; + + if (existingIdealState == null) { + clusterSetup.getClusterManagementTool().addResource(clusterName, resourceName, idealState); + response = Response.ok().build(); + } else if (!existingIdealState.equals(idealState)) { + return Response.status(Response.Status.CONFLICT).build(); + } else { + response = Response.notModified().build(); + } + + if (rebalance) { // TODO this will break if replicas is not integer (e.g. N) + clusterSetup.rebalanceResource(clusterName, resourceName, Integer.valueOf(replicas)); + } + + return response; + } + + @Path("/{zkAddress}/{clusterName}/resources/{resourceName}") + @DELETE + public Response dropResource(@PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName, + @PathParam("resourceName") String resourceName) throws Exception { + dataCache.invalidate(); + + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + List<String> resources = clusterSetup.getClusterManagementTool().getResourcesInCluster(clusterName); + if (!resources.contains(resourceName)) { + throw new NotFoundException(); + } + + clusterSetup.dropResourceFromCluster(clusterName, resourceName); + + return Response.noContent().build(); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/resource/DashboardResource.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/resource/DashboardResource.java b/helix-ui/src/main/java/org/apache/helix/ui/resource/DashboardResource.java new file mode 100644 index 0000000..cb154e5 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/resource/DashboardResource.java @@ -0,0 +1,164 @@ +package org.apache.helix.ui.resource; + +import com.google.common.collect.ImmutableList; +import org.apache.helix.manager.zk.ZKUtil; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.ui.api.*; +import org.apache.helix.ui.util.ClientCache; +import org.apache.helix.ui.util.DataCache; +import org.apache.helix.ui.view.ClusterView; +import org.apache.helix.ui.view.LandingView; +import org.apache.helix.ui.view.ResourceView; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.*; + +@Path("/dashboard") +@Produces(MediaType.TEXT_HTML) +public class DashboardResource { + private static final List<String> REBALANCE_MODES = ImmutableList.of( + IdealState.RebalanceMode.SEMI_AUTO.toString(), + IdealState.RebalanceMode.FULL_AUTO.toString(), + IdealState.RebalanceMode.CUSTOMIZED.toString(), + IdealState.RebalanceMode.USER_DEFINED.toString(), + IdealState.RebalanceMode.TASK.toString()); + + private final boolean adminMode; + private final ClientCache clientCache; + private final DataCache dataCache; + + public DashboardResource(ClientCache clientCache, + DataCache dataCache, + boolean adminMode) { + this.clientCache = clientCache; + this.dataCache = dataCache; + this.adminMode = adminMode; + } + + @GET + public LandingView getLandingView() { + return new LandingView(); + } + + @GET + @Path("/{zkAddress}") + public ClusterView getClusterView(@PathParam("zkAddress") String zkAddress) throws Exception { + clientCache.get(zkAddress); // n.b. will validate + return getClusterView(zkAddress, null); + } + + @GET + @Path("/{zkAddress}/{cluster}") + public ClusterView getClusterView( + @PathParam("zkAddress") String zkAddress, + @PathParam("cluster") String cluster) throws Exception { + ClusterConnection clusterConnection = clientCache.get(zkAddress); + + // All clusters + List<String> clusters = dataCache.getClusterCache().get(zkAddress); + + // The active cluster + String activeCluster = cluster == null ? clusters.get(0) : cluster; + ClusterSpec clusterSpec = new ClusterSpec(zkAddress, activeCluster); + + // Check it + if (!ZKUtil.isClusterSetup(activeCluster, clusterConnection.getZkClient())) { + return new ClusterView(adminMode, zkAddress, clusters, false, activeCluster, null, null, null, null, null); + } + + // Resources in the active cluster + List<String> activeClusterResources = dataCache.getResourceCache().get(clusterSpec); + + // Instances in active cluster + List<InstanceSpec> instanceSpecs = dataCache.getInstanceCache().get(clusterSpec); + + // State models in active cluster + List<String> stateModels + = clusterConnection.getClusterSetup().getClusterManagementTool().getStateModelDefs(activeCluster); + + // Config table + List<ConfigTableRow> configTable = dataCache.getConfigCache().get(clusterSpec); + + return new ClusterView( + adminMode, + zkAddress, + clusters, + true, + activeCluster, + activeClusterResources, + instanceSpecs, + configTable, + stateModels, + REBALANCE_MODES); + } + + @GET + @Path("/{zkAddress}/{cluster}/{resource}") + public ResourceView getResourceView( + @PathParam("zkAddress") String zkAddress, + @PathParam("cluster") String cluster, + @PathParam("resource") String resource) throws Exception { + ClusterConnection clusterConnection = clientCache.get(zkAddress); + + // All clusters + List<String> clusters = dataCache.getClusterCache().get(zkAddress); + + // The active cluster + String activeCluster = cluster == null ? clusters.get(0) : cluster; + ClusterSpec clusterSpec = new ClusterSpec(zkAddress, activeCluster); + + // Check it + if (!ZKUtil.isClusterSetup(activeCluster, clusterConnection.getZkClient())) { + return new ResourceView( + adminMode, zkAddress, clusters, false, activeCluster, null, null, null, null, null, null, null); + } + + // Resources in the active cluster + List<String> activeClusterResources = dataCache.getResourceCache().get(clusterSpec); + if (!activeClusterResources.contains(resource)) { + throw new NotFoundException("No resource " + resource + " in " + activeCluster); + } + + // Instances in active cluster + List<InstanceSpec> instanceSpecs = dataCache.getInstanceCache().get(clusterSpec); + Map<String, InstanceSpec> instanceSpecMap = new HashMap<String, InstanceSpec>(instanceSpecs.size()); + for (InstanceSpec instanceSpec : instanceSpecs) { + instanceSpecMap.put(instanceSpec.getInstanceName(), instanceSpec); + } + + // Resource state + IdealState idealState + = clusterConnection.getClusterSetup().getClusterManagementTool().getResourceIdealState(cluster, resource); + ExternalView externalView + = clusterConnection.getClusterSetup().getClusterManagementTool().getResourceExternalView(cluster, resource); + ResourceStateSpec resourceStateSpec + = new ResourceStateSpec(resource, idealState, externalView, instanceSpecMap); + List<ResourceStateTableRow> resourceStateTable + = resourceStateSpec.getResourceStateTable(); + + // Resource config + List<ConfigTableRow> configTable = dataCache.getResourceConfigCache().get(new ResourceSpec(zkAddress, activeCluster, resource)); + + // Resource instances + Set<String> resourceInstances = new HashSet<String>(); + for (ResourceStateTableRow row : resourceStateTable) { + resourceInstances.add(row.getInstanceName()); + } + + return new ResourceView( + adminMode, + zkAddress, + clusters, + true, + activeCluster, + activeClusterResources, + resource, + resourceStateTable, + resourceInstances, + configTable, + IdealStateSpec.fromIdealState(idealState), + instanceSpecs); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/resource/VisualizerResource.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/resource/VisualizerResource.java b/helix-ui/src/main/java/org/apache/helix/ui/resource/VisualizerResource.java new file mode 100644 index 0000000..289d84e --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/resource/VisualizerResource.java @@ -0,0 +1,53 @@ +package org.apache.helix.ui.resource; + +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.tools.ClusterSetup; +import org.apache.helix.ui.api.*; +import org.apache.helix.ui.util.ClientCache; +import org.apache.helix.ui.util.DataCache; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Path("/visualizer") +public class VisualizerResource { + private final ClientCache clientCache; + private final DataCache dataCache; + + public VisualizerResource(ClientCache clientCache, DataCache dataCache) { + this.clientCache = clientCache; + this.dataCache = dataCache; + } + + @GET + @Path("/{zkAddress}/{clusterName}/{resourceName}") + @Produces(MediaType.APPLICATION_JSON) + public D3ResourceCoCentricCircle getD3HelixResource( + @PathParam("zkAddress") String zkAddress, + @PathParam("clusterName") String clusterName, + @PathParam("resourceName") String resourceName) throws Exception { + ClusterSetup clusterSetup = clientCache.get(zkAddress).getClusterSetup(); + + IdealState idealState + = clusterSetup.getClusterManagementTool().getResourceIdealState(clusterName, resourceName); + ExternalView externalView + = clusterSetup.getClusterManagementTool().getResourceExternalView(clusterName, resourceName); + if (idealState == null) { + throw new NotFoundException("No resource ideal state for " + resourceName); + } + + // Instances in active cluster + List<InstanceSpec> instanceSpecs = dataCache.getInstanceCache().get(new ClusterSpec(zkAddress, clusterName)); + Map<String, InstanceSpec> instanceSpecMap = new HashMap<String, InstanceSpec>(instanceSpecs.size()); + for (InstanceSpec instanceSpec : instanceSpecs) { + instanceSpecMap.put(instanceSpec.getInstanceName(), instanceSpec); + } + + return D3ResourceCoCentricCircle.fromResourceStateSpec( + new ResourceStateSpec(resourceName, idealState, externalView, instanceSpecMap)); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/task/ClearClientCache.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/task/ClearClientCache.java b/helix-ui/src/main/java/org/apache/helix/ui/task/ClearClientCache.java new file mode 100644 index 0000000..d30a479 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/task/ClearClientCache.java @@ -0,0 +1,25 @@ +package org.apache.helix.ui.task; + +import com.google.common.collect.ImmutableMultimap; +import io.dropwizard.servlets.tasks.Task; +import org.apache.helix.ui.util.ClientCache; + +import java.io.PrintWriter; + +public class ClearClientCache extends Task { + private final ClientCache clientCache; + + public ClearClientCache(ClientCache clientCache) { + super("clearClientCache"); + this.clientCache = clientCache; + } + + @Override + public void execute(ImmutableMultimap<String, String> params, PrintWriter printWriter) throws Exception { + printWriter.println("Clearing ZK connections ..."); + printWriter.flush(); + clientCache.invalidateAll(); + printWriter.println("Done!"); + printWriter.flush(); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/task/ClearDataCacheTask.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/task/ClearDataCacheTask.java b/helix-ui/src/main/java/org/apache/helix/ui/task/ClearDataCacheTask.java new file mode 100644 index 0000000..fd5661f --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/task/ClearDataCacheTask.java @@ -0,0 +1,25 @@ +package org.apache.helix.ui.task; + +import com.google.common.collect.ImmutableMultimap; +import io.dropwizard.servlets.tasks.Task; +import org.apache.helix.ui.util.DataCache; + +import java.io.PrintWriter; + +public class ClearDataCacheTask extends Task { + private final DataCache dataCache; + + public ClearDataCacheTask(DataCache dataCache) { + super("clearDataCache"); + this.dataCache = dataCache; + } + + @Override + public void execute(ImmutableMultimap<String, String> params, PrintWriter printWriter) throws Exception { + printWriter.println("Clearing data caches ..."); + printWriter.flush(); + dataCache.invalidate(); + printWriter.println("Done!"); + printWriter.flush(); + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/util/ClientCache.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/util/ClientCache.java b/helix-ui/src/main/java/org/apache/helix/ui/util/ClientCache.java new file mode 100644 index 0000000..968e26c --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/util/ClientCache.java @@ -0,0 +1,100 @@ +package org.apache.helix.ui.util; + +import com.google.common.cache.*; +import org.I0Itec.zkclient.exception.ZkTimeoutException; +import org.apache.helix.manager.zk.ZNRecordSerializer; +import org.apache.helix.manager.zk.ZkClient; +import org.apache.helix.ui.api.ClusterConnection; +import org.apache.zookeeper.ZooKeeper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import java.net.URLDecoder; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class ClientCache { + private static final Logger LOG = LoggerFactory.getLogger(ClientCache.class); + private static final int DEFAULT_SESSION_TIMEOUT_MILLIS = 5000; + private static final int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 5000; + + private final ZkAddressValidator zkAddressValidator; + + public ClientCache(ZkAddressValidator zkAddressValidator) { + this.zkAddressValidator = zkAddressValidator; + } + + // Manages and caches lifecycle of connections to ZK + final LoadingCache<String, ClusterConnection> clientCache = CacheBuilder.newBuilder() + .maximumSize(3) + .expireAfterAccess(5, TimeUnit.MINUTES) + .removalListener(new RemovalListener<String, ClusterConnection>() { + @Override + public void onRemoval(RemovalNotification<String, ClusterConnection> removalNotification) { + if (removalNotification.getValue() != null) { + ZkClient zkClient = removalNotification.getValue().getZkClient(); + if (zkClient != null) { + zkClient.close(); + LOG.info("Disconnected from {}", removalNotification.getKey()); + } + } + } + }) + .build(new CacheLoader<String, ClusterConnection>() { + @Override + public ClusterConnection load(String zkAddress) throws Exception { + ZkClient zkClient = new ZkClient( + zkAddress, + DEFAULT_SESSION_TIMEOUT_MILLIS, + DEFAULT_CONNECTION_TIMEOUT_MILLIS, + new ZNRecordSerializer()); + zkClient.waitUntilConnected(); + LOG.info("Connected to {}", zkAddress); + return new ClusterConnection(zkClient); + } + }); + + public ClusterConnection get(String zkAddress) { + try { + zkAddress = URLDecoder.decode(zkAddress, "UTF-8"); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + + if (!zkAddressValidator.validate(zkAddress)) { + throw new WebApplicationException("Cannot access " + zkAddress, Response.Status.UNAUTHORIZED); + } + + ClusterConnection clusterConnection; + try { + clusterConnection = clientCache.get(zkAddress); + } catch (Exception e) { + throw new WebApplicationException(e, Response.Status.GATEWAY_TIMEOUT); + } + + if (!clusterConnection.getZkClient().getConnection().getZookeeperState().equals(ZooKeeper.States.CONNECTED)) { + clientCache.invalidate(zkAddress); + throw new WebApplicationException("ZooKeeper connection was dead", Response.Status.GATEWAY_TIMEOUT); + } + + return clusterConnection; + } + + public void invalidateAll() { + clientCache.invalidateAll(); + } + + public Set<String> getDeadConnections() { + Set<String> deadConnections = new HashSet<String>(); + for (Map.Entry<String, ClusterConnection> entry : clientCache.asMap().entrySet()) { + if (!entry.getValue().getZkClient().getConnection().getZookeeperState().isAlive()) { + deadConnections.add(entry.getKey()); + } + } + return deadConnections; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/util/DataCache.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/util/DataCache.java b/helix-ui/src/main/java/org/apache/helix/ui/util/DataCache.java new file mode 100644 index 0000000..ff66a05 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/util/DataCache.java @@ -0,0 +1,188 @@ +package org.apache.helix.ui.util; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.apache.helix.manager.zk.ZkClient; +import org.apache.helix.model.HelixConfigScope; +import org.apache.helix.model.InstanceConfig; +import org.apache.helix.model.builder.HelixConfigScopeBuilder; +import org.apache.helix.tools.ClusterSetup; +import org.apache.helix.ui.api.*; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class DataCache { + private static final int CACHE_EXPIRY_TIME = 30; + private static final TimeUnit CACHE_EXPIRY_UNIT = TimeUnit.SECONDS; + + private final LoadingCache<String, List<String>> clusterCache; + private final LoadingCache<ClusterSpec, List<String>> resourceCache; + private final LoadingCache<ClusterSpec, List<ConfigTableRow>> configCache; + private final LoadingCache<ResourceSpec, List<ConfigTableRow>> resourceConfigCache; + private final LoadingCache<ClusterSpec, List<InstanceSpec>> instanceCache; + + public DataCache(final ClientCache clientCache) { + this.clusterCache = CacheBuilder.newBuilder() + .expireAfterWrite(CACHE_EXPIRY_TIME, CACHE_EXPIRY_UNIT) + .build(new CacheLoader<String, List<String>>() { + @Override + public List<String> load(String zkAddress) throws Exception { + ZkClient zkClient = clientCache.get(zkAddress).getZkClient(); + List<String> clusters = zkClient.getChildren("/"); + Collections.sort(clusters); + return clusters; + } + }); + + this.resourceCache = CacheBuilder.newBuilder() + .expireAfterWrite(CACHE_EXPIRY_TIME, CACHE_EXPIRY_UNIT) + .build(new CacheLoader<ClusterSpec, List<String>>() { + @Override + public List<String> load(ClusterSpec clusterSpec) throws Exception { + ClusterSetup clusterSetup = clientCache.get(clusterSpec.getZkAddress()).getClusterSetup(); + List<String> resources = new ArrayList<String>(clusterSetup.getClusterManagementTool().getResourcesInCluster(clusterSpec.getClusterName())); + Collections.sort(resources); + return resources; + } + }); + + this.configCache = CacheBuilder.newBuilder() + .expireAfterWrite(CACHE_EXPIRY_TIME, CACHE_EXPIRY_UNIT) + .build(new CacheLoader<ClusterSpec, List<ConfigTableRow>>() { + @Override + public List<ConfigTableRow> load(ClusterSpec clusterSpec) throws Exception { + ClusterSetup clusterSetup = clientCache.get(clusterSpec.getZkAddress()).getClusterSetup(); + List<ConfigTableRow> configTable = new ArrayList<ConfigTableRow>(); + + // Cluster config + HelixConfigScope configScope + = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.CLUSTER) + .forCluster(clusterSpec.getClusterName()).build(); + List<String> clusterConfigKeys + = clusterSetup.getClusterManagementTool().getConfigKeys(configScope); + Map<String, String> config + = clusterSetup.getClusterManagementTool().getConfig(configScope, clusterConfigKeys); + for (Map.Entry<String, String> entry : config.entrySet()) { + configTable.add(new ConfigTableRow( + HelixConfigScope.ConfigScopeProperty.CLUSTER.toString(), + clusterSpec.getClusterName(), + entry.getKey(), + entry.getValue())); + } + + Collections.sort(configTable); + + return configTable; + } + }); + + this.resourceConfigCache = CacheBuilder.newBuilder() + .expireAfterWrite(CACHE_EXPIRY_TIME, CACHE_EXPIRY_UNIT) + .build(new CacheLoader<ResourceSpec, List<ConfigTableRow>>() { + @Override + public List<ConfigTableRow> load(ResourceSpec resourceSpec) throws Exception { + ClusterSetup clusterSetup = clientCache.get(resourceSpec.getZkAddress()).getClusterSetup(); + List<ConfigTableRow> configTable = new ArrayList<ConfigTableRow>(); + + HelixConfigScope configScope = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.RESOURCE) + .forCluster(resourceSpec.getClusterName()) + .forResource(resourceSpec.getResourceName()) + .build(); + + List<String> clusterConfigKeys = clusterSetup.getClusterManagementTool().getConfigKeys(configScope); + + Map<String, String> config = clusterSetup.getClusterManagementTool().getConfig(configScope, clusterConfigKeys); + + if (config != null) { + for (Map.Entry<String, String> entry : config.entrySet()) { + configTable.add(new ConfigTableRow( + HelixConfigScope.ConfigScopeProperty.RESOURCE.toString(), + resourceSpec.getClusterName(), + entry.getKey(), + entry.getValue())); + } + } + + return configTable; + } + }); + + this.instanceCache = CacheBuilder.newBuilder() + .expireAfterWrite(CACHE_EXPIRY_TIME, CACHE_EXPIRY_UNIT) + .build(new CacheLoader<ClusterSpec, List<InstanceSpec>>() { + @Override + public List<InstanceSpec> load(ClusterSpec clusterSpec) throws Exception { + ClusterConnection clusterConnection = clientCache.get(clusterSpec.getZkAddress()); + + // Instances in the cluster + List<String> instances = + clusterConnection.getClusterSetup().getClusterManagementTool().getInstancesInCluster(clusterSpec.getClusterName()); + + // Live instances in the cluster + // TODO: should be able to use clusterSetup for this, but no method available + List<String> liveInstances + = clusterConnection.getZkClient().getChildren(String.format("/%s/LIVEINSTANCES", clusterSpec.getClusterName())); + Set<String> liveInstanceSet = new HashSet<String>(); + if (liveInstances != null) { + liveInstanceSet.addAll(liveInstances); + } + + // Enabled instances + Set<String> enabledInstances = new HashSet<String>(); + if (instances != null) { + for (String instance : instances) { + InstanceConfig instanceConfig = clusterConnection.getClusterSetup() + .getClusterManagementTool() + .getInstanceConfig(clusterSpec.getClusterName(), instance); + if (instanceConfig.getInstanceEnabled()) { + enabledInstances.add(instance); + } + } + } + + // Rows + List<InstanceSpec> instanceSpecs = new ArrayList<InstanceSpec>(); + if (instances != null) { + for (String instance : instances) { + instanceSpecs.add(new InstanceSpec( + instance, + enabledInstances.contains(instance), + liveInstanceSet.contains(instance))); + } + } + + return instanceSpecs; + } + }); + } + + public void invalidate() { + clusterCache.invalidateAll(); + resourceCache.invalidateAll(); + configCache.invalidateAll(); + resourceConfigCache.invalidateAll(); + instanceCache.invalidateAll(); + } + + public LoadingCache<String, List<String>> getClusterCache() { + return clusterCache; + } + + public LoadingCache<ClusterSpec, List<String>> getResourceCache() { + return resourceCache; + } + + public LoadingCache<ClusterSpec, List<ConfigTableRow>> getConfigCache() { + return configCache; + } + + public LoadingCache<ResourceSpec, List<ConfigTableRow>> getResourceConfigCache() { + return resourceConfigCache; + } + + public LoadingCache<ClusterSpec, List<InstanceSpec>> getInstanceCache() { + return instanceCache; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/util/DropWizardApplicationRunner.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/util/DropWizardApplicationRunner.java b/helix-ui/src/main/java/org/apache/helix/ui/util/DropWizardApplicationRunner.java new file mode 100644 index 0000000..fb27130 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/util/DropWizardApplicationRunner.java @@ -0,0 +1,86 @@ +package org.apache.helix.ui.util; + +import io.dropwizard.Application; +import io.dropwizard.Configuration; +import io.dropwizard.cli.ServerCommand; +import io.dropwizard.configuration.ConfigurationFactory; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; + +import java.io.File; + +/** + * A utility to run DropWizard (http://dropwizard.io/) applications in-process. + */ +public class DropWizardApplicationRunner { + /** + * Creates a Jetty server for an application that can be started / stopped in-process + * + * @param config An application configuration instance (with properties set) + * @param applicationClass The {@link io.dropwizard.Application} implementation class + * @param <T> The configuration class + * @return A Jetty server + */ + @SuppressWarnings("unchecked") + public static <T extends Configuration> + Server createServer(T config, Class<? extends Application<T>> applicationClass) throws Exception { + // Create application + final Application<T> application = applicationClass.getConstructor().newInstance(); + + // Create bootstrap + final ServerCommand<T> serverCommand = new ServerCommand<T>(application); + final Bootstrap<T> bootstrap = new Bootstrap<T>(application); + bootstrap.addCommand(serverCommand); + application.initialize(bootstrap); + + // Write a temporary config file + File tmpConfigFile = new File( + System.getProperty("java.io.tmpdir"), + config.getClass().getCanonicalName() + "_" + System.currentTimeMillis()); + tmpConfigFile.deleteOnExit(); + bootstrap.getObjectMapper().writeValue(tmpConfigFile, config); + + // Parse configuration + ConfigurationFactory<T> configurationFactory + = bootstrap.getConfigurationFactoryFactory() + .create((Class<T>) config.getClass(), + bootstrap.getValidatorFactory().getValidator(), + bootstrap.getObjectMapper(), + "dw"); + final T builtConfig = configurationFactory.build( + bootstrap.getConfigurationSourceProvider(), tmpConfigFile.getAbsolutePath()); + + // Configure logging + builtConfig.getLoggingFactory() + .configure(bootstrap.getMetricRegistry(), + bootstrap.getApplication().getName()); + + // Environment + final Environment environment = new Environment(bootstrap.getApplication().getName(), + bootstrap.getObjectMapper(), + bootstrap.getValidatorFactory().getValidator(), + bootstrap.getMetricRegistry(), + bootstrap.getClassLoader()); + + // Initialize environment + builtConfig.getMetricsFactory().configure(environment.lifecycle(), bootstrap.getMetricRegistry()); + bootstrap.run(builtConfig, environment); + application.run(builtConfig, environment); + + // Server + final Server server = builtConfig.getServerFactory().build(environment); + server.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() { + @Override + public void lifeCycleStopped(LifeCycle event) { + builtConfig.getLoggingFactory().stop(); + } + }); + + return server; + } +} + http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/util/ZkAddressValidator.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/util/ZkAddressValidator.java b/helix-ui/src/main/java/org/apache/helix/ui/util/ZkAddressValidator.java new file mode 100644 index 0000000..a77ee3b --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/util/ZkAddressValidator.java @@ -0,0 +1,37 @@ +package org.apache.helix.ui.util; + +import java.util.HashSet; +import java.util.Set; + +public class ZkAddressValidator { + + private final Set<String> zkMachines; + + public ZkAddressValidator(Set<String> zkAddresses) { + if (zkAddresses == null) { + this.zkMachines = null; + } else { + this.zkMachines = new HashSet<String>(); + for (String zkAddress : zkAddresses) { + for (String machine : zkAddress.split(",")) { + this.zkMachines.add(machine); + } + } + } + } + + public boolean validate(String zkAddress) { + if (zkMachines == null) { + return true; + } + + String[] machines = zkAddress.split(","); + for (String machine : machines) { + if (!zkMachines.contains(machine)) { + return false; + } + } + + return true; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/view/ClusterView.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/view/ClusterView.java b/helix-ui/src/main/java/org/apache/helix/ui/view/ClusterView.java new file mode 100644 index 0000000..b9fc0a6 --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/view/ClusterView.java @@ -0,0 +1,85 @@ +package org.apache.helix.ui.view; + +import io.dropwizard.views.View; +import org.apache.helix.ui.api.ConfigTableRow; +import org.apache.helix.ui.api.InstanceSpec; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; + +public class ClusterView extends View { + private final boolean adminMode; + private final String zkAddress; + private final List<String> clusters; + private final boolean activeValid; + private final String activeCluster; + private final List<String> activeClusterResources; + private final List<InstanceSpec> instanceSpecs; + private final List<ConfigTableRow> configTable; + private final List<String> stateModels; + private final List<String> rebalanceModes; + + public ClusterView(boolean adminMode, + String zkAddress, + List<String> clusters, + boolean activeValid, + String activeCluster, + List<String> activeClusterResources, + List<InstanceSpec> instanceSpecs, + List<ConfigTableRow> configTable, + List<String> stateModels, + List<String> rebalanceModes) { + super("cluster-view.ftl"); + this.adminMode = adminMode; + this.zkAddress = zkAddress; + this.clusters = clusters; + this.activeValid = activeValid; + this.activeCluster = activeCluster; + this.activeClusterResources = activeClusterResources; + this.instanceSpecs = instanceSpecs; + this.configTable = configTable; + this.stateModels = stateModels; + this.rebalanceModes = rebalanceModes; + } + + public boolean isAdminMode() { + return adminMode; + } + + public String getZkAddress() throws IOException { + return URLEncoder.encode(zkAddress, "UTF-8"); + } + + public List<String> getClusters() { + return clusters; + } + + public boolean isActiveValid() { + return activeValid; + } + + public String getActiveCluster() { + return activeCluster; + } + + public List<String> getActiveClusterResources() { + return activeClusterResources; + } + + public List<InstanceSpec> getInstanceSpecs() { + return instanceSpecs; + } + + public List<ConfigTableRow> getConfigTable() { + return configTable; + } + + public List<String> getStateModels() { + return stateModels; + } + + public List<String> getRebalanceModes() { + return rebalanceModes; + } +} http://git-wip-us.apache.org/repos/asf/helix/blob/6cbafef0/helix-ui/src/main/java/org/apache/helix/ui/view/LandingView.java ---------------------------------------------------------------------- diff --git a/helix-ui/src/main/java/org/apache/helix/ui/view/LandingView.java b/helix-ui/src/main/java/org/apache/helix/ui/view/LandingView.java new file mode 100644 index 0000000..330fa4b --- /dev/null +++ b/helix-ui/src/main/java/org/apache/helix/ui/view/LandingView.java @@ -0,0 +1,9 @@ +package org.apache.helix.ui.view; + +import io.dropwizard.views.View; + +public class LandingView extends View { + public LandingView() { + super("landing-view.ftl"); + } +}
