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:
parent
1d47c368fe
commit
3f01ef2f6e
@ -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.
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -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>
|
@ -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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -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,
|
||||||
|
@ -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]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 //
|
||||||
////////////
|
////////////
|
||||||
|
Loading…
Reference in New Issue
Block a user