From 3f01ef2f6ed7782b1d269fd7708bc798ed8aef45 Mon Sep 17 00:00:00 2001 From: Shu Muto Date: Fri, 6 Apr 2018 15:12:32 +0900 Subject: [PATCH] Add Manage Security Groups action for container This patch adds "Manage Security Groups" action as item action for the container. This action manages the associations between port and security group. Change-Id: Ic39a985c7daffdb7f5e5616a0eb3c07d57c4db22 Implements: blueprint manage-security-groups --- ...nage-security-groups-c17e4a0febe6dd4c.yaml | 6 + zun_ui/api/client.py | 42 ++- zun_ui/api/rest_api.py | 2 + .../container/containers/actions.module.js | 9 + .../containers/actions/create.service.js | 2 + .../delete-security-group.service.js | 60 ++++ .../manage-security-groups.controller.js | 261 ++++++++++++++++++ .../manage-security-groups.html | 43 +++ .../manage-security-groups.service.js | 151 ++++++++++ .../containers/actions/update.service.js | 72 +---- .../security-groups.controller.js | 23 +- .../actions/workflow/workflow.service.js | 120 +++++++- .../container/containers/containers.module.js | 3 +- .../static/dashboard/container/zun.service.js | 10 + 14 files changed, 707 insertions(+), 97 deletions(-) create mode 100644 releasenotes/notes/manage-security-groups-c17e4a0febe6dd4c.yaml create mode 100644 zun_ui/static/dashboard/container/containers/actions/manage-security-groups/delete-security-group.service.js create mode 100644 zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.controller.js create mode 100644 zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.html create mode 100644 zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.service.js diff --git a/releasenotes/notes/manage-security-groups-c17e4a0febe6dd4c.yaml b/releasenotes/notes/manage-security-groups-c17e4a0febe6dd4c.yaml new file mode 100644 index 0000000..d572c2d --- /dev/null +++ b/releasenotes/notes/manage-security-groups-c17e4a0febe6dd4c.yaml @@ -0,0 +1,6 @@ +--- +features: + - > + [`manage-security-groups `_] + Added Manage Security Groups action to manage associations between + security groups and ports on container. diff --git a/zun_ui/api/client.py b/zun_ui/api/client.py index 98a54c9..538aa9f 100644 --- a/zun_ui/api/client.py +++ b/zun_ui/api/client.py @@ -10,16 +10,19 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + +from django.conf import settings from horizon import exceptions from horizon.utils.memoized import memoized_with_request -import logging from openstack_dashboard.api import base + +from neutronclient.v2_0 import client as neutron_client from zunclient import api_versions from zunclient.common import utils from zunclient.v1 import client as zun_client - LOG = logging.getLogger(__name__) CONTAINER_CREATE_ATTRS = zun_client.containers.CREATION_ATTRIBUTES @@ -65,6 +68,33 @@ def zunclient(request_auth_params): return c +def get_auth_params_from_request_neutron(request): + """Extracts properties needed by neutronclient call from the request object. + + These will be used to memoize the calls to neutronclient. + """ + return ( + request.user.token.id, + base.url_for(request, 'network'), + base.url_for(request, 'identity') + ) + + +@memoized_with_request(get_auth_params_from_request_neutron) +def neutronclient(request_auth_params): + token_id, neutron_url, auth_url = request_auth_params + insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) + cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None) + + LOG.debug('neutronclient connection created using the token "%s" and url' + ' "%s"' % (token_id, neutron_url)) + c = neutron_client.Client(token=token_id, + auth_url=auth_url, + endpoint_url=neutron_url, + insecure=insecure, ca_cert=cacert) + return c + + def _cleanup_params(attrs, check, **params): args = {} run = False @@ -229,6 +259,14 @@ def container_network_detach(request, id): return {"container": id, "network": network} +def port_update_security_groups(request): + port = request.DATA.get("port") or None + security_groups = request.DATA.get("security_groups") or None + kwargs = {"security_groups": security_groups} + neutronclient(request).update_port(port, body={"port": kwargs}) + return {"port": port, "security_group": security_groups} + + def image_list(request, limit=None, marker=None, sort_key=None, sort_dir=None, detail=True): return zunclient(request).images.list(limit, marker, sort_key, diff --git a/zun_ui/api/rest_api.py b/zun_ui/api/rest_api.py index f249cc8..98564f9 100644 --- a/zun_ui/api/rest_api.py +++ b/zun_ui/api/rest_api.py @@ -90,6 +90,8 @@ class ContainerActions(generic.View): return client.container_network_attach(request, id) elif action == 'network_detach': return client.container_network_detach(request, id) + elif action == 'port_update_security_groups': + return client.port_update_security_groups(request) @rest_utils.ajax(data_required=True) def delete(self, request, id, action): diff --git a/zun_ui/static/dashboard/container/containers/actions.module.js b/zun_ui/static/dashboard/container/containers/actions.module.js index 408cadc..8fefffc 100644 --- a/zun_ui/static/dashboard/container/containers/actions.module.js +++ b/zun_ui/static/dashboard/container/containers/actions.module.js @@ -45,6 +45,7 @@ 'horizon.dashboard.container.containers.execute.service', 'horizon.dashboard.container.containers.kill.service', 'horizon.dashboard.container.containers.refresh.service', + 'horizon.dashboard.container.containers.manage-security-groups.service', 'horizon.dashboard.container.containers.resourceType' ]; @@ -64,6 +65,7 @@ executeContainerService, killContainerService, refreshContainerService, + manageSecurityGroupService, resourceType ) { var containersResourceType = registry.getResourceType(resourceType); @@ -103,6 +105,13 @@ text: gettext('Update Container') } }) + .append({ + id: 'manageSecurityGroupService', + service: manageSecurityGroupService, + template: { + text: gettext('Manage Security Groups') + } + }) .append({ id: 'startContainerAction', service: startContainerService, diff --git a/zun_ui/static/dashboard/container/containers/actions/create.service.js b/zun_ui/static/dashboard/container/containers/actions/create.service.js index 11de7c9..c152a3f 100644 --- a/zun_ui/static/dashboard/container/containers/actions/create.service.js +++ b/zun_ui/static/dashboard/container/containers/actions/create.service.js @@ -89,6 +89,8 @@ delete context.model.ports; delete context.model.availableNetworks; delete context.model.allocatedNetworks; + delete context.model.availableSecurityGroups; + delete context.model.allocatedSecurityGroups; delete context.model.availablePorts; context.model.hints = setSchedulerHints(context.model); delete context.model.availableHints; diff --git a/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/delete-security-group.service.js b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/delete-security-group.service.js new file mode 100644 index 0000000..032887b --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/delete-security-group.service.js @@ -0,0 +1,60 @@ +/* + * Licensed 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. + */ +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container.containers.manage-security-groups.delete.service + * @description Service for the security group deletion for manage-security-groups modal + */ + angular + .module('horizon.dashboard.container.containers') + .constant('horizon.dashboard.container.containers.manage-security-groups.delete.events', + events()) + .factory('horizon.dashboard.container.containers.manage-security-groups.delete.service', + service); + + function events() { + return { + DELETE_SUCCESS: 'horizon.dashboard.container.containers.manage-security-groups.DELETE_SUCCESS' + }; + } + + service.$inject = [ + 'horizon.framework.util.q.extensions', + 'horizon.dashboard.container.containers.manage-security-groups.delete.events' + ]; + + function service( + $qExtensions, + events + ) { + var service = { + allowed: allowed, + perform: perform + }; + + return service; + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected, scope) { + scope.$emit(events.DELETE_SUCCESS, selected); + return $qExtensions.booleanAsPromise(true); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.controller.js b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.controller.js new file mode 100644 index 0000000..60dfaf0 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.controller.js @@ -0,0 +1,261 @@ +/* + * Licensed 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. + */ +(function () { + 'use strict'; + + /** + * @ngdoc controller + * @name horizon.dashboard.container.containers.manage-security-groups + * @description + * Controller for the Manage Security Groups dialog. + */ + angular + .module('horizon.dashboard.container.containers') + .controller('horizon.dashboard.container.containers.manage-security-groups', + ManageSecurityGroupsController); + + ManageSecurityGroupsController.$inject = [ + '$scope', + 'horizon.app.core.openstack-service-api.neutron', + 'horizon.app.core.openstack-service-api.security-group', + 'horizon.dashboard.container.containers.manage-security-groups.delete.service', + 'horizon.dashboard.container.containers.manage-security-groups.delete.events' + ]; + + function ManageSecurityGroupsController( + $scope, neutron, securityGroup, deleteSecurityGroupService, events + ) { + var ctrl = this; + + // form settings to add association of port and security group into table /////////// + // model template + ctrl.modelTemplate = { + id: "", + port: "", + port_name: "", + security_group: "", + security_group_name: "" + }; + + // initiate model + ctrl.model = angular.copy(ctrl.modelTemplate); + + // for port selection + ctrl.availablePorts = [ + {id: "", name: gettext("Select port.")} + ]; + + // for security group selection + var message = { + portNotSelected: gettext("Select port first."), + portSelected: gettext("Select security group.") + }; + ctrl.availableSecurityGroups = [ + {id: "", name: message.portNotSelected, selected: false} + ]; + ctrl.refreshAvailableSecurityGroups = refreshAvailableSecurityGroups; + + // add association into table + ctrl.addSecurityGroup = function(event) { + ctrl.model.id = ctrl.model.port + " " + ctrl.model.security_group; + ctrl.model.port_name = getPortName(ctrl.model.port); + ctrl.model.security_group_name = getSecurityGroupName(ctrl.model.security_group); + var model = angular.copy(ctrl.model); + $scope.model.port_security_groups.push(model); + + // clean up form + ctrl.model = angular.copy(ctrl.modelTemplate); + ctrl.disabledSecurityGroup = true; + event.stopPropagation(); + event.preventDefault(); + refreshAvailableSecurityGroups(); + }; + + // get port name from available ports. + function getPortName(port) { + var result = ""; + ctrl.availablePorts.forEach(function (ap) { + if (port === ap.id) { + result = ap.name; + } + }); + return result; + } + + // get security group name from available security groups. + function getSecurityGroupName(sg) { + var result = ""; + ctrl.availableSecurityGroups.forEach(function (asg) { + if (sg === asg.id) { + result = asg.name; + } + }); + return result; + } + + // refresh available security group selection, according to addition/deletion of associations. + ctrl.disabledSecurityGroup = true; + function refreshAvailableSecurityGroups() { + if (ctrl.model.port) { + // if port is selected, enable port selection. + ctrl.disabledSecurityGroup = false; + } else { + // otherwise disable port selection. + ctrl.disabledSecurityGroup = true; + } + // set "selected" to true, if the security group already added into table. + ctrl.availableSecurityGroups.forEach(function (sg) { + sg.selected = false; + ctrl.items.forEach(function (item) { + if (sg.id === item.security_group && ctrl.model.port === item.port) { + // mark already selected + sg.selected = true; + } + }); + }); + } + + // enable "Add Security Group" button, if both of port and security group are selected. + ctrl.validateSecurityGroup = function () { + return !(ctrl.model.port && ctrl.model.security_group); + }; + + // retrieve available ports and security groups /////////////////////////// + // get security groups first, then get networks + securityGroup.query().then(onGetSecurityGroups).then(getNetworks); + function onGetSecurityGroups(response) { + angular.forEach(response.data.items, function (item) { + ctrl.availableSecurityGroups.push({id: item.id, name: item.name, selected: false}); + // if association of port and security group in $scope.model.port_security_groups, + // push it into table for update. + if ($scope.model.port_security_groups.includes(item.id)) { + ctrl.security_groups.push(item); + } + + }); + return response; + } + + // get available neutron networks and ports + function getNetworks() { + return neutron.getNetworks().then(onGetNetworks).then(getPorts); + } + + function onGetNetworks(response) { + return response; + } + + function getPorts(networks) { + networks.data.items.forEach(function(network) { + return neutron.getPorts({network_id: network.id}).then( + function(ports) { + onGetPorts(ports, network); + } + ); + }); + return networks; + } + + function onGetPorts(ports, network) { + ports.data.items.forEach(function(port) { + // no device_owner or compute:kuryr means that the port can be associated + // with security group + if ((port.device_owner === "" || port.device_owner === "compute:kuryr") && + port.admin_state === "UP") { + port.subnet_names = getPortSubnets(port, network.subnets); + port.network_name = network.name; + if ($scope.model.ports.includes(port.id)) { + var portName = port.network_name + " - " + port.subnet_names + " - " + port.name; + ctrl.availablePorts.push({ + id: port.id, + name: portName}); + port.security_groups.forEach(function (sgId) { + var sgName; + ctrl.availableSecurityGroups.forEach(function (sg) { + if (sgId === sg.id) { + sgName = sg.name; + } + }); + $scope.model.port_security_groups.push({ + id: port.id + " " + sgId, + port: port.id, + port_name: portName, + security_group: sgId, + security_group_name: sgName + }); + }); + } + } + }); + } + + // helper function to return an object of IP:NAME pairs for subnet mapping + function getPortSubnets(port, subnets) { + //var subnetNames = {}; + var subnetNames = ""; + port.fixed_ips.forEach(function (ip) { + subnets.forEach(function (subnet) { + if (ip.subnet_id === subnet.id) { + //subnetNames[ip.ip_address] = subnet.name; + if (subnetNames) { + subnetNames += ", "; + } + subnetNames += ip.ip_address + ": " + subnet.name; + } + }); + }); + return subnetNames; + } + + // settings for table of added security groups //////////////////////////// + ctrl.items = $scope.model.port_security_groups; + ctrl.config = { + selectAll: false, + expand: false, + trackId: 'id', + columns: [ + {id: 'port_name', title: gettext('Port')}, + {id: 'security_group_name', title: gettext('Security Group')} + ] + }; + ctrl.itemActions = [ + { + id: 'deleteSecurityGroupAction', + service: deleteSecurityGroupService, + template: { + type: "delete", + text: gettext("Delete") + } + } + ]; + + // register watcher for security group deletion from table + var deleteWatcher = $scope.$on(events.DELETE_SUCCESS, deleteSecurityGroup); + $scope.$on('$destroy', function destroy() { + deleteWatcher(); + }); + + // on delete security group from table + function deleteSecurityGroup(event, deleted) { + // delete security group from table + ctrl.items.forEach(function (sg, index) { + if (sg.id === deleted.port + " " + deleted.security_group) { + delete ctrl.items.splice(index, 1); + } + }); + // enable deleted security group in selection + refreshAvailableSecurityGroups(); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.html b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.html new file mode 100644 index 0000000..bd6929e --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.html @@ -0,0 +1,43 @@ +
+

+ Manage security group for ports. +

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ + + + +
\ No newline at end of file diff --git a/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.service.js b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.service.js new file mode 100644 index 0000000..a5694d5 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/actions/manage-security-groups/manage-security-groups.service.js @@ -0,0 +1,151 @@ +/** + * Licensed 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. + */ + +(function() { + "use strict"; + + angular + .module("horizon.dashboard.container.containers") + .factory("horizon.dashboard.container.containers.manage-security-groups.service", + manageSecurityGroup); + + manageSecurityGroup.$inject = [ + "horizon.app.core.openstack-service-api.neutron", + "horizon.app.core.openstack-service-api.security-group", + "horizon.app.core.openstack-service-api.zun", + "horizon.dashboard.container.basePath", + 'horizon.dashboard.container.containers.resourceType', + 'horizon.dashboard.container.containers.validStates', + 'horizon.framework.util.actions.action-result.service', + "horizon.framework.util.i18n.gettext", + "horizon.framework.util.q.extensions", + "horizon.framework.widgets.form.ModalFormService", + "horizon.framework.widgets.toast.service" + ]; + + function manageSecurityGroup( + neutron, securityGroup, zun, basePath, resourceType, validStates, actionResult, + gettext, $qExtensions, modal, toast + ) { + // title for dialog + var title = gettext("Manage Security Groups: container %s"); + // schema + var schema = { + type: "object", + properties: { + signal: { + title: gettext("Manage Security Groups"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: "section", + htmlClass: "col-xs-12", + items: [ + { + type: "template", + /* eslint-disable max-len */ + templateUrl: basePath + "containers/actions/manage-security-groups/manage-security-groups.html" + } + ] + } + ] + } + ]; + + // model + var model = {}; + + var message = { + success: gettext("Changes security groups %(sgs)s for port %(port)s was successfully submitted.") + }; + + var service = { + initAction: initAction, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initAction() { + } + + function allowed(container) { + return $qExtensions.booleanAsPromise( + validStates.manage_security_groups.indexOf(container.status) >= 0 + ); + } + + function perform(selected) { + model.id = selected.id; + model.name = selected.name; + model.ports = []; + Object.keys(selected.addresses).map(function(key) { + selected.addresses[key].forEach(function (addr) { + model.ports.push(addr.port); + }); + }); + model.port_security_groups = []; + + // modal config + var config = { + "title": interpolate(title, [model.name]), + "submitText": gettext("Submit"), + "schema": schema, + "form": form, + "model": model + }; + return modal.open(config).then(submit); + } + + function submit(context) { + var id = context.model.id; + var portSgs = context.model.port_security_groups; + var aggregatedPortSgs = {}; + // initialize port list + model.ports.forEach(function (port) { + aggregatedPortSgs[port] = []; + }); + // add security groups for each port + portSgs.forEach(function (portSg) { + aggregatedPortSgs[portSg.port].push(portSg.security_group); + }); + + Object.keys(aggregatedPortSgs).map(function (port) { + return zun.updatePortSecurityGroup(id, port, aggregatedPortSgs[port]).then(function() { + var sgs = gettext("(empty)"); + if (aggregatedPortSgs[port].length) { + sgs = aggregatedPortSgs[port]; + } + toast.add( + 'success', + interpolate(message.success, {port: port, sgs: sgs}, true)); + var result = actionResult.getActionResult().updated(resourceType, id); + return result.result; + }); + }); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/actions/update.service.js b/zun_ui/static/dashboard/container/containers/actions/update.service.js index 6ddf41c..67b4fe2 100644 --- a/zun_ui/static/dashboard/container/containers/actions/update.service.js +++ b/zun_ui/static/dashboard/container/containers/actions/update.service.js @@ -65,52 +65,7 @@ var title, submitText; title = gettext('Update Container'); submitText = gettext('Update'); - var config = workflow.init('update', title, submitText); - config.model.id = selected.id; - - // load current data - zun.getContainer(selected.id).then(onLoad); - function onLoad(response) { - config.model.name = response.data.name - ? response.data.name : ""; - config.model.image = response.data.image - ? response.data.image : ""; - config.model.image_driver = response.data.image_driver - ? response.data.image_driver : "docker"; - config.model.image_pull_policy = response.data.image_pull_policy - ? response.data.image_pull_policy : ""; - config.model.command = response.data.command - ? response.data.command : ""; - config.model.hostname = response.data.hostname - ? response.data.hostname : ""; - config.model.auto_remove = response.data.auto_remove - ? response.data.auto_remove : false; - config.model.cpu = response.data.cpu - ? response.data.cpu : ""; - config.model.memory = response.data.memory - ? parseInt(response.data.memory, 10) : ""; - config.model.restart_policy = response.data.restart_policy.Name - ? response.data.restart_policy.Name : ""; - config.model.restart_policy_max_retry = response.data.restart_policy.MaximumRetryCount - ? parseInt(response.data.restart_policy.MaximumRetryCount, 10) : null; - if (config.model.auto_remove) { - config.model.exit_policy = "remove"; - } else { - config.model.exit_policy = config.model.restart_policy; - } - config.model.runtime = response.data.runtime - ? response.data.runtime : ""; - config.model.allocatedNetworks = getAllocatedNetworks(response.data.addresses); - config.model.workdir = response.data.workdir - ? response.data.workdir : ""; - config.model.environment = response.data.environment - ? hashToString(response.data.environment) : ""; - config.model.interactive = response.data.interactive - ? response.data.interactive : false; - config.model.labels = response.data.labels - ? hashToString(response.data.labels) : ""; - } - + var config = workflow.init('update', title, submitText, selected.id); return modal.open(config).then(submit); } @@ -183,37 +138,16 @@ function successAttach(response) { toast.add('success', interpolate(message.successAttach, - [response.data.container, response.data.network])); + [response.data.network, response.data.container])); var result = actionResult.getActionResult().updated(resourceType, response.data.container); return result.result; } function successDetach(response) { toast.add('success', interpolate(message.successDetach, - [response.data.container, response.data.network])); + [response.data.network, response.data.container])); var result = actionResult.getActionResult().updated(resourceType, response.data.container); return result.result; } - - function getAllocatedNetworks(addresses) { - var allocated = []; - Object.keys(addresses).forEach(function (id) { - allocated.push(id); - }); - return allocated; - } - - function hashToString(hash) { - var str = ""; - for (var key in hash) { - if (hash.hasOwnProperty(key)) { - if (str.length > 0) { - str += ","; - } - str += key + "=" + hash[key]; - } - } - return str; - } } })(); diff --git a/zun_ui/static/dashboard/container/containers/actions/workflow/security-groups/security-groups.controller.js b/zun_ui/static/dashboard/container/containers/actions/workflow/security-groups/security-groups.controller.js index 2ef2a2d..d43b248 100644 --- a/zun_ui/static/dashboard/container/containers/actions/workflow/security-groups/security-groups.controller.js +++ b/zun_ui/static/dashboard/container/containers/actions/workflow/security-groups/security-groups.controller.js @@ -21,7 +21,6 @@ SecurityGroupsController.$inject = [ '$scope', - 'horizon.app.core.openstack-service-api.security-group', 'horizon.dashboard.container.basePath' ]; @@ -34,15 +33,11 @@ * Allows selection of security groups. * @returns {undefined} No return value */ - function SecurityGroupsController($scope, securityGroup, basePath) { - var push = Array.prototype.push; - + function SecurityGroupsController($scope, basePath) { var ctrl = this; - var availableSecurityGroups = []; - securityGroup.query().then(onGetSecurityGroups); ctrl.tableData = { - available: availableSecurityGroups, + available: $scope.model.availableSecurityGroups, allocated: $scope.model.security_groups, displayedAvailable: [], displayedAllocated: [] @@ -75,19 +70,5 @@ singleton: true } ]; - - // Security Groups - function onGetSecurityGroups(data) { - angular.forEach(data.data.items, function addDefault(item) { - // 'default' is a special security group in neutron. It can not be - // deleted and is guaranteed to exist. It by default contains all - // of the rules needed for an instance to reach out to the network - // so the instance can provision itself. - if (item.name === 'default') { - $scope.model.security_groups.push(item); - } - }); - push.apply(availableSecurityGroups, data.data.items); - } } })(); diff --git a/zun_ui/static/dashboard/container/containers/actions/workflow/workflow.service.js b/zun_ui/static/dashboard/container/containers/actions/workflow/workflow.service.js index 1b9cfc0..35795df 100644 --- a/zun_ui/static/dashboard/container/containers/actions/workflow/workflow.service.js +++ b/zun_ui/static/dashboard/container/containers/actions/workflow/workflow.service.js @@ -22,17 +22,23 @@ workflow.$inject = [ "horizon.app.core.openstack-service-api.cinder", "horizon.app.core.openstack-service-api.neutron", + "horizon.app.core.openstack-service-api.security-group", + 'horizon.app.core.openstack-service-api.zun', "horizon.dashboard.container.basePath", "horizon.framework.util.i18n.gettext", + 'horizon.framework.util.q.extensions', "horizon.framework.widgets.metadata.tree.service" ]; - function workflow(cinder, neutron, basePath, gettext, treeService) { + function workflow( + cinder, neutron, securityGroup, zun, basePath, gettext, + $qExtensions, treeService + ) { var workflow = { init: init }; - function init(action, title, submitText) { + function init(action, title, submitText, id) { var push = Array.prototype.push; var schema, form, model; var imageDrivers = [ @@ -495,8 +501,93 @@ // available ports model.availablePorts = []; + // security groups + model.availableSecurityGroups = []; + model.allocatedSecurityGroups = []; + + // get resources + getContainer(action, id).then(function () { + getVolumes(); + getNetworks(); + securityGroup.query().then(onGetSecurityGroups); + }); + + // get container when action equals "update" + function getContainer (action, id) { + if (action === 'create') { + return $qExtensions.booleanAsPromise(true); + } else { + return zun.getContainer(id).then(onGetContainer); + } + } + + // get container for update + function onGetContainer(response) { + model.id = id; + model.name = response.data.name + ? response.data.name : ""; + model.image = response.data.image + ? response.data.image : ""; + model.image_driver = response.data.image_driver + ? response.data.image_driver : "docker"; + model.image_pull_policy = response.data.image_pull_policy + ? response.data.image_pull_policy : ""; + model.command = response.data.command + ? response.data.command : ""; + model.hostname = response.data.hostname + ? response.data.hostname : ""; + model.runtime = response.data.runtime + ? response.data.runtime : ""; + model.cpu = response.data.cpu + ? response.data.cpu : ""; + model.memory = response.data.memory + ? parseInt(response.data.memory, 10) : ""; + model.restart_policy = response.data.restart_policy.Name + ? response.data.restart_policy.Name : ""; + model.restart_policy_max_retry = response.data.restart_policy.MaximumRetryCount + ? parseInt(response.data.restart_policy.MaximumRetryCount, 10) : null; + if (config.model.auto_remove) { + config.model.exit_policy = "remove"; + } else { + config.model.exit_policy = config.model.restart_policy; + } + model.allocatedNetworks = getAllocatedNetworks(response.data.addresses); + model.allocatedSecurityGroups = response.data.security_groups; + model.workdir = response.data.workdir + ? response.data.workdir : ""; + model.environment = response.data.environment + ? hashToString(response.data.environment) : ""; + model.interactive = response.data.interactive + ? response.data.interactive : false; + model.auto_remove = response.data.auto_remove + ? response.data.auto_remove : false; + model.labels = response.data.labels + ? hashToString(response.data.labels) : ""; + return response; + } + + function getAllocatedNetworks(addresses) { + var allocated = []; + Object.keys(addresses).forEach(function (id) { + allocated.push(id); + }); + return allocated; + } + + function hashToString(hash) { + var str = ""; + for (var key in hash) { + if (hash.hasOwnProperty(key)) { + if (str.length > 0) { + str += ","; + } + str += key + "=" + hash[key]; + } + } + return str; + } + // get available cinder volumes - getVolumes(); function getVolumes() { return cinder.getVolumes().then(onGetVolumes); } @@ -514,7 +605,6 @@ } // get available neutron networks and ports - getNetworks(); function getNetworks() { return neutron.getNetworks().then(onGetNetworks).then(getPorts); } @@ -543,6 +633,7 @@ } ); }); + return networks; } function onGetPorts(ports, network) { @@ -569,6 +660,27 @@ return subnetNames; } + // get security groups + function onGetSecurityGroups(response) { + angular.forEach(response.data.items, function (item) { + // 'default' is a special security group in neutron. It can not be + // deleted and is guaranteed to exist. It by default contains all + // of the rules needed for an instance to reach out to the network + // so the instance can provision itself. + if (item.name === 'default' && action === "create") { + model.security_groups.push(item); + } + // if network in model.allocatedSecurityGroups, + // push it to mode.security_groups for update + else if (model.allocatedSecurityGroups.includes(item.id)) { + model.security_groups.push(item); + } + + }); + push.apply(model.availableSecurityGroups, response.data.items); + return response; + } + var config = { title: title, submitText: submitText, diff --git a/zun_ui/static/dashboard/container/containers/containers.module.js b/zun_ui/static/dashboard/container/containers/containers.module.js index e44c8d3..e49c8ee 100644 --- a/zun_ui/static/dashboard/container/containers/containers.module.js +++ b/zun_ui/static/dashboard/container/containers/containers.module.js @@ -68,7 +68,8 @@ states.CREATED, states.CREATING, states.ERROR, states.RUNNING, states.STOPPED, states.UNKNOWN, states.DELETED ], - delete_stop: [states.RUNNING] + delete_stop: [states.RUNNING], + manage_security_groups: [states.CREATED, states.RUNNING, states.STOPPED, states.PAUSED] }; } diff --git a/zun_ui/static/dashboard/container/zun.service.js b/zun_ui/static/dashboard/container/zun.service.js index cdfe936..51e5647 100644 --- a/zun_ui/static/dashboard/container/zun.service.js +++ b/zun_ui/static/dashboard/container/zun.service.js @@ -47,6 +47,7 @@ resizeContainer: resizeContainer, attachNetwork: attachNetwork, detachNetwork: detachNetwork, + updatePortSecurityGroup: updatePortSecurityGroup, pullImage: pullImage, getImages: getImages }; @@ -167,6 +168,15 @@ .error(error(msg)); } + function updatePortSecurityGroup(id, port, sgs) { + var msg = interpolate( + gettext('Unable to update security groups %(sgs)s for port %(port)s.'), + {port: port, sgs: sgs}, true); + return apiService.post(containersPath + id + '/port_update_security_groups', + {port: port, security_groups: sgs}) + .error(error(msg)); + } + //////////// // Images // ////////////