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
This commit is contained in:
Shu Muto 2018-04-06 15:12:32 +09:00
parent 1d47c368fe
commit 3f01ef2f6e
14 changed files with 707 additions and 97 deletions

View File

@ -0,0 +1,6 @@
---
features:
- >
[`manage-security-groups <https://blueprints.launchpad.net/zun-ui/+spec/manage-security-groups>`_]
Added Manage Security Groups action to manage associations between
security groups and ports on container.

View File

@ -10,16 +10,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from django.conf import settings
from horizon import exceptions from horizon import exceptions
from horizon.utils.memoized import memoized_with_request from horizon.utils.memoized import memoized_with_request
import logging
from openstack_dashboard.api import base from openstack_dashboard.api import base
from neutronclient.v2_0 import client as neutron_client
from zunclient import api_versions from zunclient import api_versions
from zunclient.common import utils from zunclient.common import utils
from zunclient.v1 import client as zun_client from zunclient.v1 import client as zun_client
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONTAINER_CREATE_ATTRS = zun_client.containers.CREATION_ATTRIBUTES CONTAINER_CREATE_ATTRS = zun_client.containers.CREATION_ATTRIBUTES
@ -65,6 +68,33 @@ def zunclient(request_auth_params):
return c 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): def _cleanup_params(attrs, check, **params):
args = {} args = {}
run = False run = False
@ -229,6 +259,14 @@ def container_network_detach(request, id):
return {"container": id, "network": network} 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, def image_list(request, limit=None, marker=None, sort_key=None,
sort_dir=None, detail=True): sort_dir=None, detail=True):
return zunclient(request).images.list(limit, marker, sort_key, return zunclient(request).images.list(limit, marker, sort_key,

View File

@ -90,6 +90,8 @@ class ContainerActions(generic.View):
return client.container_network_attach(request, id) return client.container_network_attach(request, id)
elif action == 'network_detach': elif action == 'network_detach':
return client.container_network_detach(request, id) 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) @rest_utils.ajax(data_required=True)
def delete(self, request, id, action): def delete(self, request, id, action):

View File

@ -45,6 +45,7 @@
'horizon.dashboard.container.containers.execute.service', 'horizon.dashboard.container.containers.execute.service',
'horizon.dashboard.container.containers.kill.service', 'horizon.dashboard.container.containers.kill.service',
'horizon.dashboard.container.containers.refresh.service', 'horizon.dashboard.container.containers.refresh.service',
'horizon.dashboard.container.containers.manage-security-groups.service',
'horizon.dashboard.container.containers.resourceType' 'horizon.dashboard.container.containers.resourceType'
]; ];
@ -64,6 +65,7 @@
executeContainerService, executeContainerService,
killContainerService, killContainerService,
refreshContainerService, refreshContainerService,
manageSecurityGroupService,
resourceType resourceType
) { ) {
var containersResourceType = registry.getResourceType(resourceType); var containersResourceType = registry.getResourceType(resourceType);
@ -103,6 +105,13 @@
text: gettext('Update Container') text: gettext('Update Container')
} }
}) })
.append({
id: 'manageSecurityGroupService',
service: manageSecurityGroupService,
template: {
text: gettext('Manage Security Groups')
}
})
.append({ .append({
id: 'startContainerAction', id: 'startContainerAction',
service: startContainerService, service: startContainerService,

View File

@ -89,6 +89,8 @@
delete context.model.ports; delete context.model.ports;
delete context.model.availableNetworks; delete context.model.availableNetworks;
delete context.model.allocatedNetworks; delete context.model.allocatedNetworks;
delete context.model.availableSecurityGroups;
delete context.model.allocatedSecurityGroups;
delete context.model.availablePorts; delete context.model.availablePorts;
context.model.hints = setSchedulerHints(context.model); context.model.hints = setSchedulerHints(context.model);
delete context.model.availableHints; delete context.model.availableHints;

View File

@ -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);
}
}
})();

View File

@ -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();
}
}
})();

View File

@ -0,0 +1,43 @@
<div ng-controller="horizon.dashboard.container.containers.manage-security-groups as ctrl">
<p class="step-description" translate>
Manage security group for ports.
</p>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="control-label" for="port" translate>Port</label>
<select class="form-control" id="port" name="port"
ng-model="ctrl.model.port"
ng-options="port.id as port.name for port in ctrl.availablePorts"
ng-change="ctrl.refreshAvailableSecurityGroups()">
</select>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="control-label" for="security_group" translate>Security Group</label>
<select
class="form-control" id="security_group" name="security_group"
ng-model="ctrl.model.security_group"
ng-options="sg.id as sg.name for sg in ctrl.availableSecurityGroups | filter:{selected: false}"
ng-disabled="ctrl.disabledSecurityGroup">
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary"
ng-disabled="ctrl.validateSecurityGroup()"
ng-click="ctrl.addSecurityGroup($event)">
<span class="fa fa-arrow-down"></span>
<translate>Add Security Group</translate>
</button>
</div>
<hz-dynamic-table
config="ctrl.config"
items="ctrl.items"
item-actions="ctrl.itemActions">
</hz-dynamic-table>
</div>

View File

@ -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;
});
});
}
}
})();

View File

@ -65,52 +65,7 @@
var title, submitText; var title, submitText;
title = gettext('Update Container'); title = gettext('Update Container');
submitText = gettext('Update'); submitText = gettext('Update');
var config = workflow.init('update', title, submitText); var config = workflow.init('update', title, submitText, selected.id);
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) : "";
}
return modal.open(config).then(submit); return modal.open(config).then(submit);
} }
@ -183,37 +138,16 @@
function successAttach(response) { function successAttach(response) {
toast.add('success', interpolate(message.successAttach, 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); var result = actionResult.getActionResult().updated(resourceType, response.data.container);
return result.result; return result.result;
} }
function successDetach(response) { function successDetach(response) {
toast.add('success', interpolate(message.successDetach, 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); var result = actionResult.getActionResult().updated(resourceType, response.data.container);
return result.result; 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;
}
} }
})(); })();

View File

@ -21,7 +21,6 @@
SecurityGroupsController.$inject = [ SecurityGroupsController.$inject = [
'$scope', '$scope',
'horizon.app.core.openstack-service-api.security-group',
'horizon.dashboard.container.basePath' 'horizon.dashboard.container.basePath'
]; ];
@ -34,15 +33,11 @@
* Allows selection of security groups. * Allows selection of security groups.
* @returns {undefined} No return value * @returns {undefined} No return value
*/ */
function SecurityGroupsController($scope, securityGroup, basePath) { function SecurityGroupsController($scope, basePath) {
var push = Array.prototype.push;
var ctrl = this; var ctrl = this;
var availableSecurityGroups = [];
securityGroup.query().then(onGetSecurityGroups);
ctrl.tableData = { ctrl.tableData = {
available: availableSecurityGroups, available: $scope.model.availableSecurityGroups,
allocated: $scope.model.security_groups, allocated: $scope.model.security_groups,
displayedAvailable: [], displayedAvailable: [],
displayedAllocated: [] displayedAllocated: []
@ -75,19 +70,5 @@
singleton: true 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);
}
} }
})(); })();

View File

@ -22,17 +22,23 @@
workflow.$inject = [ workflow.$inject = [
"horizon.app.core.openstack-service-api.cinder", "horizon.app.core.openstack-service-api.cinder",
"horizon.app.core.openstack-service-api.neutron", "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.basePath",
"horizon.framework.util.i18n.gettext", "horizon.framework.util.i18n.gettext",
'horizon.framework.util.q.extensions',
"horizon.framework.widgets.metadata.tree.service" "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 = { var workflow = {
init: init init: init
}; };
function init(action, title, submitText) { function init(action, title, submitText, id) {
var push = Array.prototype.push; var push = Array.prototype.push;
var schema, form, model; var schema, form, model;
var imageDrivers = [ var imageDrivers = [
@ -495,8 +501,93 @@
// available ports // available ports
model.availablePorts = []; model.availablePorts = [];
// get available cinder volumes // security groups
model.availableSecurityGroups = [];
model.allocatedSecurityGroups = [];
// get resources
getContainer(action, id).then(function () {
getVolumes(); 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
function getVolumes() { function getVolumes() {
return cinder.getVolumes().then(onGetVolumes); return cinder.getVolumes().then(onGetVolumes);
} }
@ -514,7 +605,6 @@
} }
// get available neutron networks and ports // get available neutron networks and ports
getNetworks();
function getNetworks() { function getNetworks() {
return neutron.getNetworks().then(onGetNetworks).then(getPorts); return neutron.getNetworks().then(onGetNetworks).then(getPorts);
} }
@ -543,6 +633,7 @@
} }
); );
}); });
return networks;
} }
function onGetPorts(ports, network) { function onGetPorts(ports, network) {
@ -569,6 +660,27 @@
return subnetNames; 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 = { var config = {
title: title, title: title,
submitText: submitText, submitText: submitText,

View File

@ -68,7 +68,8 @@
states.CREATED, states.CREATING, states.ERROR, states.RUNNING, states.CREATED, states.CREATING, states.ERROR, states.RUNNING,
states.STOPPED, states.UNKNOWN, states.DELETED 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]
}; };
} }

View File

@ -47,6 +47,7 @@
resizeContainer: resizeContainer, resizeContainer: resizeContainer,
attachNetwork: attachNetwork, attachNetwork: attachNetwork,
detachNetwork: detachNetwork, detachNetwork: detachNetwork,
updatePortSecurityGroup: updatePortSecurityGroup,
pullImage: pullImage, pullImage: pullImage,
getImages: getImages getImages: getImages
}; };
@ -167,6 +168,15 @@
.error(error(msg)); .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 // // Images //
//////////// ////////////