From: Andrew Kim Date: Thu, 5 Sep 2013 20:15:18 +0000 (-0500) Subject: Add Cluster Menu X-Git-Tag: releasepom-0.1.0~124 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=c11dff1f548652d147d25966033a1cd31416afc9;hp=97fd2a787cccc23c9224d5aa3d2db1c0ee344b67;p=controller.git Add Cluster Menu Change-Id: Ie795953393493a41dd05a0a8a7de03ddb5278729 Signed-off-by: Andrew Kim --- diff --git a/opendaylight/web/root/pom.xml b/opendaylight/web/root/pom.xml index 6000efadc7..bc5c73a7d8 100644 --- a/opendaylight/web/root/pom.xml +++ b/opendaylight/web/root/pom.xml @@ -34,6 +34,9 @@ org.opendaylight.controller.sal.utils, org.opendaylight.controller.usermanager, org.opendaylight.controller.containermanager, + org.opendaylight.controller.clustering.services, + org.opendaylight.controller.connectionmanager, + org.opendaylight.controller.switchmanager, com.google.gson, javax.annotation, javax.naming, @@ -92,6 +95,16 @@ + + org.opendaylight.controller + clustering.services + 0.4.0-SNAPSHOT + + + org.opendaylight.controller + connectionmanager + 0.1.0-SNAPSHOT + org.opendaylight.controller configuration @@ -112,6 +125,11 @@ containermanager 0.4.0-SNAPSHOT + + org.opendaylight.controller + switchmanager + 0.5.0-SNAPSHOT + junit junit diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ClusterNodeBean.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ClusterNodeBean.java new file mode 100644 index 0000000000..5e4f22afe2 --- /dev/null +++ b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ClusterNodeBean.java @@ -0,0 +1,50 @@ +package org.opendaylight.controller.web; + +import java.net.InetAddress; + +/** + * Information about a clustered controller to send to the UI frontend + * @author andrekim + */ +public class ClusterNodeBean { + private final byte[] address; + private final String name; + private final Boolean me; + private final Boolean coordinator; + + public static class Builder { + // required params + private final byte[] address; + private final String name; + + // optional params + private Boolean me = null; + private Boolean coordinator = null; + + public Builder(InetAddress address) { + this.address = address.getAddress(); + this.name = address.getHostAddress(); + } + + public Builder highlightMe() { + this.me = true; + return this; + } + + public Builder iAmCoordinator() { + this.coordinator = true; + return this; + } + + public ClusterNodeBean build() { + return new ClusterNodeBean(this); + } + } + + private ClusterNodeBean(Builder builder) { + this.address = builder.address; + this.name = builder.name; + this.me = builder.me; + this.coordinator = builder.coordinator; + } +} \ No newline at end of file diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/DaylightWebAdmin.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/DaylightWebAdmin.java index 524cb62b3a..d9aa03ea62 100644 --- a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/DaylightWebAdmin.java +++ b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/DaylightWebAdmin.java @@ -8,14 +8,24 @@ package org.opendaylight.controller.web; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.servlet.http.HttpServletRequest; +import org.opendaylight.controller.clustering.services.IClusterGlobalServices; +import org.opendaylight.controller.connectionmanager.IConnectionManager; import org.opendaylight.controller.sal.authorization.UserLevel; +import org.opendaylight.controller.sal.core.Description; +import org.opendaylight.controller.sal.core.Node; +import org.opendaylight.controller.sal.utils.GlobalConstants; import org.opendaylight.controller.sal.utils.ServiceHelper; import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.sal.utils.StatusCode; +import org.opendaylight.controller.switchmanager.ISwitchManager; import org.opendaylight.controller.usermanager.IUserManager; import org.opendaylight.controller.usermanager.UserConfig; import org.springframework.stereotype.Controller; @@ -30,14 +40,95 @@ import com.google.gson.Gson; @Controller @RequestMapping("/admin") public class DaylightWebAdmin { + Gson gson = new Gson(); + /** + * Returns list of clustered controllers. Highlights "this" controller and + * if controller is coordinator + * @return List + */ + @RequestMapping("/cluster") + @ResponseBody + public String getClusteredControllers() { + IClusterGlobalServices clusterServices = (IClusterGlobalServices) ServiceHelper.getGlobalInstance( + IClusterGlobalServices.class, this); + if (clusterServices == null) { + return null; + } + + List clusters = new ArrayList(); + + List controllers = clusterServices.getClusteredControllers(); + for (InetAddress controller : controllers) { + ClusterNodeBean.Builder clusterBeanBuilder = new ClusterNodeBean.Builder(controller); + if (controller.equals(clusterServices.getMyAddress())) { + clusterBeanBuilder.highlightMe(); + } + if (clusterServices.amICoordinator()) { + clusterBeanBuilder.iAmCoordinator(); + } + + clusters.add(clusterBeanBuilder.build()); + } + + return gson.toJson(clusters); + } + + /** + * Return nodes connected to controller {controller} + * @param cluster + * - byte[] of the address of the controller + * @return List + */ + @RequestMapping("/cluster/controller/{controller}") + @ResponseBody + public String getNodesConnectedToController(@PathVariable("controller") String cluster) { + IClusterGlobalServices clusterServices = (IClusterGlobalServices) ServiceHelper.getGlobalInstance( + IClusterGlobalServices.class, this); + if (clusterServices == null) { + return null; + } + IConnectionManager connectionManager = (IConnectionManager) ServiceHelper.getGlobalInstance( + IConnectionManager.class, this); + if (connectionManager == null) { + return null; + } + ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, + GlobalConstants.DEFAULT.toString(), this); + if (switchManager == null) { + return null; + } + + byte[] address = gson.fromJson(cluster, byte[].class); + InetAddress clusterAddress = null; + try { + clusterAddress = InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + return null; + } + InetAddress thisCluster = clusterServices.getMyAddress(); + + List result = new ArrayList(); + + Set nodes = connectionManager.getNodes(thisCluster); + for (Node node : nodes) { + Description description = (Description) switchManager.getNodeProp(node, Description.propertyName); + NodeBean nodeBean; + if (description == null || description.getValue().equals("None")) { + nodeBean = new NodeBean(node); + } else { + nodeBean = new NodeBean(node, description.getValue()); + } + result.add(nodeBean); + } + return gson.toJson(result); + } @RequestMapping("/users") @ResponseBody public List getUsers() { - IUserManager userManager = (IUserManager) ServiceHelper - .getGlobalInstance(IUserManager.class, this); + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); if (userManager == null) { return null; } @@ -52,13 +143,10 @@ public class DaylightWebAdmin { */ @RequestMapping(value = "/users", method = RequestMethod.POST) @ResponseBody - public String saveLocalUserConfig( - @RequestParam(required = true) String json, - @RequestParam(required = true) String action, - HttpServletRequest request) { + public String saveLocalUserConfig(@RequestParam(required = true) String json, + @RequestParam(required = true) String action, HttpServletRequest request) { - IUserManager userManager = (IUserManager) ServiceHelper - .getGlobalInstance(IUserManager.class, this); + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); if (userManager == null) { return "Internal Error"; } @@ -70,10 +158,9 @@ public class DaylightWebAdmin { Gson gson = new Gson(); UserConfig config = gson.fromJson(json, UserConfig.class); - Status result = (action.equals("add")) ? userManager - .addLocalUser(config) : userManager.removeLocalUser(config); - if(result.getCode().equals(StatusCode.SUCCESS)) { - String userAction=(action.equals("add")) ? "added":"removed"; + Status result = (action.equals("add")) ? userManager.addLocalUser(config) : userManager.removeLocalUser(config); + if (result.isSuccess()) { + String userAction = (action.equals("add")) ? "added" : "removed"; DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), userAction, config.getUser()); return "Success"; } @@ -82,16 +169,14 @@ public class DaylightWebAdmin { @RequestMapping(value = "/users/{username}", method = RequestMethod.POST) @ResponseBody - public String removeLocalUser(@PathVariable("username") String userName, - HttpServletRequest request) { + public String removeLocalUser(@PathVariable("username") String userName, HttpServletRequest request) { String username = request.getUserPrincipal().getName(); if (username.equals(userName)) { return "Invalid Request: User cannot delete itself"; } - IUserManager userManager = (IUserManager) ServiceHelper - .getGlobalInstance(IUserManager.class, this); + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); if (userManager == null) { return "Internal Error"; } @@ -101,7 +186,7 @@ public class DaylightWebAdmin { } Status result = userManager.removeLocalUser(userName); - if(result.getCode().equals(StatusCode.SUCCESS)) { + if (result.isSuccess()) { DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "removed", userName); return "Success"; } @@ -112,8 +197,7 @@ public class DaylightWebAdmin { @ResponseBody public Status changePassword(@PathVariable("username") String username, HttpServletRequest request, @RequestParam("currentPassword") String currentPassword, @RequestParam("newPassword") String newPassword) { - IUserManager userManager = (IUserManager) ServiceHelper - .getGlobalInstance(IUserManager.class, this); + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); if (userManager == null) { return new Status(StatusCode.GONE, "User Manager not found"); } @@ -127,7 +211,7 @@ public class DaylightWebAdmin { } Status status = userManager.changeLocalUserPassword(username, currentPassword, newPassword); - if(status.isSuccess()){ + if (status.isSuccess()) { DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "changed password for", username); } return status; @@ -135,11 +219,9 @@ public class DaylightWebAdmin { /** * Is the operation permitted for the given level - * * @param level */ - private boolean authorize(IUserManager userManager, UserLevel level, - HttpServletRequest request) { + private boolean authorize(IUserManager userManager, UserLevel level, HttpServletRequest request) { String username = request.getUserPrincipal().getName(); UserLevel userLevel = userManager.getUserLevel(username); return userLevel.toNumber() <= level.toNumber(); diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/NodeBean.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/NodeBean.java new file mode 100644 index 0000000000..21d931051b --- /dev/null +++ b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/NodeBean.java @@ -0,0 +1,21 @@ +package org.opendaylight.controller.web; + +import org.opendaylight.controller.sal.core.Node; + +/** + * Information about a node connected to a controller to send to the UI frontend + * @author andrekim + */ +public class NodeBean { + private final String node; + private final String description; + + public NodeBean(Node node) { + this(node, node.toString()); + } + + public NodeBean(Node node, String description) { + this.node = node.toString(); + this.description = description; + } +} diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp b/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp index c7c3ef16c3..c795a5d56b 100644 --- a/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp +++ b/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp @@ -71,12 +71,10 @@
${username} diff --git a/opendaylight/web/root/src/main/resources/css/one.less b/opendaylight/web/root/src/main/resources/css/one.less index 6ec818c18c..db9d7632d5 100644 --- a/opendaylight/web/root/src/main/resources/css/one.less +++ b/opendaylight/web/root/src/main/resources/css/one.less @@ -88,6 +88,10 @@ .icon; background-image: url('../img/user_group_0107_16.png'); } + .icon-cluster { + .icon; + background-image: url('../img/topology_view_1033_16.png'); + } .icon-save { .icon; background-image: url('../img/save_as_0106_16.png'); diff --git a/opendaylight/web/root/src/main/resources/img/topology_view_1033_16.png b/opendaylight/web/root/src/main/resources/img/topology_view_1033_16.png new file mode 100644 index 0000000000..1a132542c9 Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/topology_view_1033_16.png differ diff --git a/opendaylight/web/root/src/main/resources/js/lib.js b/opendaylight/web/root/src/main/resources/js/lib.js index 811d35e12b..64dc09890d 100644 --- a/opendaylight/web/root/src/main/resources/js/lib.js +++ b/opendaylight/web/root/src/main/resources/js/lib.js @@ -26,6 +26,17 @@ one.lib.dashlet = { $h4.text(header); return $h4; }, + label : function(name, type) { + var $span = $(document.createElement('span')); + $span.addClass('label'); + if (type !== undefined) { + $span.addClass(type); + } else if (type !== null) { + $span.addClass('label-info'); + } + $span.append(name); + return $span; + }, list : function(list) { var $ul = $(document.createElement('ul')); $(list).each(function(index, value) { diff --git a/opendaylight/web/root/src/main/resources/js/open.js b/opendaylight/web/root/src/main/resources/js/open.js index b82a85a74a..599922d391 100644 --- a/opendaylight/web/root/src/main/resources/js/open.js +++ b/opendaylight/web/root/src/main/resources/js/open.js @@ -175,12 +175,9 @@ one.main.admin = { }, footer : function() { var footer = []; - - var closeButton = one.lib.dashlet.button.single("Close", - one.main.admin.id.modal.close, "", ""); + var closeButton = one.lib.dashlet.button.single('Close', one.main.admin.id.modal.close, '', ''); var $closeButton = one.lib.dashlet.button.button(closeButton); footer.push($closeButton); - return footer; } }, @@ -257,76 +254,53 @@ one.main.admin = { var $body = one.main.admin.remove.body(); var $modal = one.lib.modal.spawn(one.main.admin.id.modal.user, h3, $body, footer); - // close binding - $('#' + one.main.admin.id.modal.remove.close, $modal).click( - function() { - $modal.modal('hide'); - }); - + $('#'+one.main.admin.id.modal.remove.close, $modal).click(function() { + $modal.modal('hide'); + }); // remove binding - $('#' + one.main.admin.id.modal.remove.user, $modal) - .click( - function() { - one.main.admin.remove.modal - .ajax( - id, - function(result) { - if (result == 'Success') { - $modal - .modal('hide'); - // body inject - var $admin = $('#' - + one.main.admin.id.modal.main); - one.main.admin.ajax - .users(function($body) { - one.lib.modal.inject - .body( - $admin, - $body); - }); - } else - alert("Failed to remove user: " - + result); - }); - }); - + $('#' + one.main.admin.id.modal.remove.user, $modal).click(function() { + one.main.admin.remove.modal.ajax(id, function(result) { + if (result == 'Success') { + $modal.modal('hide'); + // body inject + var $admin = $('#'+one.main.admin.id.modal.main); + one.main.admin.ajax.users(function($body) { + one.lib.modal.inject.body($admin, $body); + }); + } else { + alert("Failed to remove user: " + result); + } + }); + }); // change password binding $('#' + one.main.admin.id.modal.remove.password, $modal).click(function() { one.main.admin.password.initialize(id, function() { $modal.modal('hide'); }); }); - $modal.modal(); }, ajax : function(id, callback) { - $.post(one.main.admin.address.root - + one.main.admin.address.users + '/' + id, - function(data) { - callback(data); - }); + $.post(one.main.admin.address.root + one.main.admin.address.users + '/' + id, function(data) { + callback(data); + }); }, }, - footer : function() { var footer = []; - var removeButton = one.lib.dashlet.button.single("Remove User", one.main.admin.id.modal.remove.user, "btn-danger", ""); var $removeButton = one.lib.dashlet.button.button(removeButton); footer.push($removeButton); - var change = one.lib.dashlet.button.single('Change Password', one.main.admin.id.modal.remove.password, 'btn-success', ''); var $change = one.lib.dashlet.button.button(change); footer.push($change); - var closeButton = one.lib.dashlet.button.single("Close", one.main.admin.id.modal.remove.close, "", ""); var $closeButton = one.lib.dashlet.button.button(closeButton); footer.push($closeButton); - return footer; }, body : function() { @@ -343,40 +317,25 @@ one.main.admin = { var $body = one.main.admin.add.body(); var $modal = one.lib.modal.spawn(one.main.admin.id.modal.user, h3, $body, footer); - // close binding - $('#' + one.main.admin.id.modal.add.close, $modal).click( - function() { - $modal.modal('hide'); - }); - + $('#' + one.main.admin.id.modal.add.close, $modal).click(function() { + $modal.modal('hide'); + }); // add binding - $('#' + one.main.admin.id.modal.add.user, $modal) - .click( - function() { - one.main.admin.add.modal - .add( - $modal, - function(result) { - if (result == 'Success') { - $modal - .modal('hide'); - // body inject - var $admin = $('#' - + one.main.admin.id.modal.main); - one.main.admin.ajax - .users(function($body) { - one.lib.modal.inject - .body( - $admin, - $body); - }); - } else - alert("Failed to add user: " - + result); - }); - }); - + $('#' + one.main.admin.id.modal.add.user, $modal).click(function() { + one.main.admin.add.modal.add($modal, function(result) { + if (result == 'Success') { + $modal.modal('hide'); + // body inject + var $admin = $('#'+one.main.admin.id.modal.main); + one.main.admin.ajax.users(function($body) { + one.lib.modal.inject.body($admin, $body); + }); + } else { + alert("Failed to add user: "+result); + } + }); + }); $modal.modal(); }, add : function($modal, callback) { @@ -550,6 +509,153 @@ one.main.admin = { } } +one.main.cluster = { + id : { // one.main.cluster.id + modal : 'one-main-cluster-id-modal', + close : 'one-main-cluster-id-close', + datagrid : 'one-main-cluster-id-datagrid' + }, + registry : { // one.main.cluster.registry + cluster : undefined + }, + initialize : function() { + var h3 = 'Cluster Management'; + var footer = one.main.cluster.footer(); + var $body = ''; + var $modal = one.lib.modal.spawn(one.main.cluster.id.modal, h3, $body, footer); + + // close + $('#'+one.main.cluster.id.close, $modal).click(function() { + $modal.modal('hide'); + }); + + // body + $.getJSON('/admin/cluster', function(data) { + var $gridHTML = one.lib.dashlet.datagrid.init(one.main.cluster.id.datagrid, { + searchable: true, + filterable: false, + pagination: true, + flexibleRowsPerPage: true + }, 'table-striped table-condensed table-cursor'); + var source = one.main.cluster.data(data); + $gridHTML.datagrid({dataSource : source}).on('loaded', function() { + $(this).find('tbody tr').click(function() { + var $tr = $(this); + if ($tr.find('td:nth-child(1)').attr('colspan') === '1') { + return false; + } + var address = one.main.cluster.registry.cluster[$tr.index()]; + one.main.cluster.nodes.initialize(address); + }); + }); + one.lib.modal.inject.body($modal, $gridHTML); + }); + + $modal.modal(); + }, + data : function(data) { + var tdata = []; + var registry = []; + $(data).each(function(idx, val) { + var name = val.name; + name = one.lib.dashlet.label(name, null)[0].outerHTML; + if (val.me === true) { + var me = one.lib.dashlet.label('*', 'label-inverse')[0].outerHTML; + name += ' '+me; + } + if (val.coordinator === true) { + var coord = one.lib.dashlet.label('C')[0].outerHTML; + name += ' '+coord; + } + tdata.push({ + 'controller' : name + }); + registry.push(val.address); + }); + one.main.cluster.registry.cluster = registry; + var source = new StaticDataSource({ + columns : [ + { + property : 'controller', + label : 'Controller', + sortable : true + } + ], + data : tdata, + delay : 0 + }); + return source; + }, + footer : function() { + var footer = []; + var close = one.lib.dashlet.button.single('Close', one.main.cluster.id.close, '', ''); + var $close = one.lib.dashlet.button.button(close); + footer.push($close); + return footer; + } +} + +one.main.cluster.nodes = { + id : { // one.main.cluster.nodes.id + modal : 'one-main-cluster-nodes-id-modal', + close : 'one-main-cluster-nodes-id-close', + datagrid : 'one-main-cluser-nodes-id-datagrid' + }, + initialize : function(address) { // one.main.cluster.nodes.initialize + var h3 = 'Connected Nodes'; + var footer = one.main.cluster.nodes.footer(); + var $body = ''; + var $modal = one.lib.modal.spawn(one.main.cluster.nodes.id.modal, h3, $body, footer); + + // close + $('#'+one.main.cluster.nodes.id.close, $modal).click(function() { + $modal.modal('hide'); + }); + + // body + $.getJSON('/admin/cluster/controller/'+JSON.stringify(address), function(data) { + var $gridHTML = one.lib.dashlet.datagrid.init(one.main.cluster.nodes.id.datagrid, { + searchable: true, + filterable: false, + pagination: true, + flexibleRowsPerPage: true + }, 'table-striped table-condensed'); + var source = one.main.cluster.nodes.data(data); + $gridHTML.datagrid({dataSource : source}); + one.lib.modal.inject.body($modal, $gridHTML); + }); + + $modal.modal(); + }, + data : function(data) { + var tdata = []; + $(data).each(function(idx, val) { + tdata.push({ + 'node' : val.description + }); + }); + var source = new StaticDataSource({ + columns : [ + { + property : 'node', + label : 'Node', + sortable : true + } + ], + data : tdata, + delay : 0 + }); + return source; + }, + footer : function() { // one.main.cluster.nodes.footer + var footer = []; + var close = one.lib.dashlet.button.single('Close', one.main.cluster.nodes.id.close, '', ''); + var $close = one.lib.dashlet.button.button(close); + footer.push($close); + return footer; + } +} + one.main.dashlet = { left : { top : $("#left-top .dashlet"), @@ -581,6 +687,11 @@ $("#admin").click(function() { }); }); +// cluster +$('#cluster').click(function() { + one.main.cluster.initialize(); +}); + // save $("#save").click(function() { $.post(one.main.constants.address.save, function(data) {