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 // ////////////