Add Networks and Ports step for creation dialog

To specify nets option when create container, this patch adds
Networks and Ports steps into the creation dialog.

The Networks and Ports steps are not shown in update dialog.

Change-Id: I686e603d43b67648df08af398a550069f5087415
Implements: bluepring add-network-step
This commit is contained in:
Shu Muto
2017-07-26 16:06:27 +09:00
parent 88cca7d35d
commit 45750bc889
9 changed files with 680 additions and 3 deletions

View File

@@ -54,8 +54,8 @@ def _cleanup_params(attrs, check, **params):
args[key] = float(value)
elif key == "memory":
args[key] = int(value)
elif key == "interactive" or key == "security_groups" \
or key == "hints":
elif key == "interactive" or key == "nets" \
or key == "security_groups" or key == "hints":
args[key] = value
elif key == "restart_policy":
args[key] = utils.check_restart_policy(value)

View File

@@ -79,7 +79,12 @@
context.model.restart_policy_max_retry;
}
delete context.model.restart_policy_max_retry;
context.model.nets = setNetworksAndPorts(context.model);
context.model.security_groups = setSecurityGroups(context.model);
delete context.model.networks;
delete context.model.ports;
delete context.model.availableNetworks;
delete context.model.availablePorts;
context.model.hints = setSchedulerHints(context.model);
delete context.model.availableHints;
delete context.model.hintsTree;
@@ -106,6 +111,18 @@
return model;
}
function setNetworksAndPorts(model) {
// pull out the ids from the security groups objects
var nets = [];
model.networks.forEach(function(network) {
nets.push({network: network.id});
});
model.ports.forEach(function(port) {
nets.push({port: port.id});
});
return nets;
}
function setSecurityGroups(model) {
// pull out the ids from the security groups objects
var securityGroups = [];

View File

@@ -0,0 +1,106 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.workflow.networks
* @description
* Controller for the Create Container - Networks Step.
*/
angular
.module('horizon.dashboard.container.containers')
.controller('horizon.dashboard.container.containers.workflow.networks',
NetworksController);
NetworksController.$inject = [
'$scope',
'horizon.framework.widgets.action-list.button-tooltip.row-warning.service'
];
function NetworksController($scope, tooltipService) {
var ctrl = this;
ctrl.networkStatuses = {
'ACTIVE': gettext('Active'),
'DOWN': gettext('Down')
};
ctrl.networkAdminStates = {
'UP': gettext('Up'),
'DOWN': gettext('Down')
};
ctrl.tableDataMulti = {
available: $scope.model.availableNetworks,
allocated: $scope.model.networks,
displayedAvailable: [],
displayedAllocated: []
};
ctrl.tableLimits = {
maxAllocation: -1
};
ctrl.tableHelpText = {
allocHelpText: gettext('Select networks from those listed below.')
};
ctrl.tooltipModel = tooltipService;
/**
* Filtering - client-side MagicSearch
*/
// All facets for network step
ctrl.networkFacets = [
{
label: gettext('Name'),
name: 'name',
singleton: true
},
{
label: gettext('Shared'),
name: 'shared',
singleton: true,
options: [
{ label: gettext('No'), key: false },
{ label: gettext('Yes'), key: true }
]
},
{
label: gettext('Admin State'),
name: 'admin_state',
singleton: true,
options: [
{ label: gettext('Up'), key: "UP" },
{ label: gettext('Down'), key: "DOWN" }
]
},
{
label: gettext('Status'),
name: 'status',
singleton: true,
options: [
{ label: gettext('Active'), key: "ACTIVE"},
{ label: gettext('Down'), key: "DOWN" }
]
}
];
}
})();

View File

@@ -0,0 +1,33 @@
<div>
<p translate>
Provider networks are created by administrators.
These networks map to an existing physical network in the data center.
</p>
<p translate>
Project networks are created by users.
These networks are fully isolated and are project-specific.
</p>
<p translate>
An <b>External</b> network is set up by an administrator.
If you want a container to communicate outside of the data center,
then attach a router between your <b>Project</b> network and the <b>External</b> network.
You can use the <b>Network Topology</b> view to connect the router to the two networks.
</p>
<h4 translate>Network characteristics</h4>
<p translate>
The subnet identifies a sub-section of a network. A subnet is specified in CIDR format.
A typical CIDR format looks like <samp>192.xxx.x.x/24</samp>.
</p>
<p translate>
If a network is shared, then all users in the project can access the network.
</p>
<p translate>
When the <b>Admin State</b> for a network is set to <b>Up</b>,
then the network is available for use. You can set the <b>Admin State</b> to <b>Down</b>
if you are not ready for other users to access the network.
</p>
<p translate>
The status indicates whether the network has an active connection.
</p>
</div>

View File

@@ -0,0 +1,169 @@
<div ng-controller="horizon.dashboard.container.containers.workflow.networks as ctrl">
<p class="step-description" translate>
Networks provide the communication channels for containers in the cloud.
</p>
<transfer-table tr-model="ctrl.tableDataMulti" help-text="ctrl.tableHelpText" limits="ctrl.tableLimits">
<allocated validate-number-min="{$ ctrl.tableDataMulti.minItems $}" ng-model="ctrl.tableDataMulti.allocated.length">
<table st-table="ctrl.tableDataMulti.displayedAllocated" st-safe-src="ctrl.tableDataMulti.allocated" hz-table
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="reorder"></th>
<th class="expander"></th>
<th class="rsp-p1" translate>Network</th>
<th class="rsp-p2" translate>Subnets Associated</th>
<th class="rsp-p1" translate>Shared</th>
<th class="rsp-p1" translate>Admin State</th>
<th class="rsp-p1" translate>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="ctrl.tableDataMulti.allocated.length === 0">
<td colspan="8">
<div class="no-rows-help" translate>
Select an item from Available items below
</div>
</td>
</tr>
<tr ng-repeat-start="row in ctrl.tableDataMulti.displayedAllocated track by row.id"
lr-drag-data="ctrl.tableDataMulti.displayedAllocated" lr-drag-src="reorder"
lr-drop-target="reorder" lr-drop-success="trCtrl.updateAllocated(e, item, collection)">
<td class="reorder">
<span class="fa fa-sort" title="{$ 'Re-order items using drag and drop'|translate $}"></span>
{$ $index + 1 $}
</td>
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ 'Click to see more details'|translate $}"></span>
</td>
<td class="rsp-p1 word-break">{$ row.name $}</td>
<td class="rsp-p2">
<div ng-repeat="subnet in row.subnets">{$ subnet.name $}</div>
</td>
<td class="rsp-p1">{$ row.shared | yesno $}</td>
<td class="rsp-p1">{$ row.admin_state | decode:ctrl.networkAdminStates $}</td>
<td class="rsp-p1">{$ row.status | decode:ctrl.networkStatuses $}</td>
<td class="actions_column">
<action-list>
<action action-classes="'btn btn-default'"
callback="trCtrl.deallocate" item="row">
<span class="fa fa-arrow-down"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<td colspan="9" class="detail">
<dl class="dl-horizontal">
<dt translate>ID</dt>
<dd>{$ row.id $}</dd>
<dt translate>Project</dt>
<dd>{$ row.tenant_id $}</dd>
<dt translate>External Network</dt>
<dd>{$ row['router:external'] | yesno $}</dd>
</dl>
<span class="h5" translate>Provider Network</span>
<div class="row" class="detail">
<dl class="col-sm-4">
<dt translate>Type</dt>
<dd>{$ row['provider:network_type'] $}</dd>
</dl>
<dl class="col-sm-4">
<dt translate>Segmentation ID</dt>
<dd>{$ row['provider:segmentation_id'] $}</dd>
</dl>
<dl class="col-sm-4">
<dt translate>Physical Network</dt>
<dd>{$ row['provider:physical_network'] $}</dd>
</dl>
</div>
</td>
</tr>
</tbody>
</table>
</allocated>
<available>
<hz-magic-search-context filter-facets="ctrl.networkFacets">
<hz-magic-search-bar></hz-magic-search-bar>
<table st-magic-search st-table="ctrl.tableDataMulti.displayedAvailable" st-safe-src="ctrl.tableDataMulti.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Network</th>
<th class="rsp-p2" translate>Subnets Associated</th>
<th st-sort="shared" class="rsp-p1" translate>Shared</th>
<th st-sort="admin_state" class="rsp-p1" translate>Admin State</th>
<th st-sort="status" class="rsp-p1" translate>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="7">
<div class="no-rows-help" translate>
No available items
</div>
</td>
</tr>
<tr ng-repeat-start="row in ctrl.tableDataMulti.displayedAvailable track by row.id" ng-if="!trCtrl.allocatedIds[row.id]">
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ 'Click to see more details'|translate $}"></span>
</td>
<td class="rsp-p1 word-break">{$ row.name$}</td>
<td class="rsp-p2">
<div ng-repeat="subnet in row.subnets">{$ subnet.name $}</div>
</td>
<td class="rsp-p1">{$ row.shared | yesno $}</td>
<td class="rsp-p1">{$ row.admin_state | decode:ctrl.networkAdminStates $}</td>
<td class="rsp-p1">{$ row.status | decode:ctrl.networkStatuses $}</td>
<td class="actions_column">
<action-list button-tooltip="row.warningMessage"
bt-model="ctrl.tooltipModel" bt-disabled="!row.disabled"
warning-classes="'invalid'">
<notifications>
<span class="fa fa-exclamation-circle invalid" ng-show="row.disabled"></span>
</notifications>
<action action-classes="'btn btn-default'"
callback="trCtrl.allocate" item="row" disabled="row.disabled">
<span class="fa fa-arrow-up"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td colspan="9" class="detail">
<dl class="dl-horizontal">
<dt translate>ID</dt>
<dd>{$ row.id $}</dd>
<dt translate>Project</dt>
<dd>{$ row.tenant_id $}</dd>
<dt translate>External Network</dt>
<dd>{$ row['router:external'] | yesno $}</dd>
</dl>
<span class="h5" translate>Provider Network</span>
<div class="row">
<dl class="col-sm-4">
<dt translate>Type</dt>
<dd>{$ row['provider:network_type'] $}</dd>
</dl>
<dl class="col-sm-4">
<dt translate>Segmentation ID</dt>
<dd>{$ row['provider:segmentation_id'] $}</dd>
</dl>
<dl class="col-sm-4">
<dt translate>Physical Network</dt>
<dd>{$ row['provider:physical_network'] $}</dd>
</dl>
</div>
</td>
</tr>
</tbody>
</table>
</hz-magic-search-context>
</available>
</transfer-table>
</div>

View File

@@ -0,0 +1,77 @@
/*
* (c) Copyright 2016 Red Hat, Inc.
*
* 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.workflow.ports
* @description
* Controller for the Create Container - Ports Step.
*/
angular
.module('horizon.dashboard.container.containers')
.controller('horizon.dashboard.container.containers.workflow.ports',
PortsController);
PortsController.$inject = [
'$scope',
'horizon.framework.widgets.action-list.button-tooltip.row-warning.service'
];
function PortsController($scope, tooltipService) {
var ctrl = this;
ctrl.portStatuses = {
'ACTIVE': gettext('Active'),
'DOWN': gettext('Down')
};
ctrl.portAdminStates = {
'UP': gettext('Up'),
'DOWN': gettext('Down')
};
ctrl.vnicTypes = {
'normal': gettext('Normal'),
'direct': gettext('Direct'),
'direct-physical': gettext('Direct Physical'),
'macvtap': gettext('MacVTap'),
'baremetal': gettext('Bare Metal')
};
ctrl.tableDataMulti = {
available: $scope.model.availablePorts,
allocated: $scope.model.ports,
displayedAvailable: [],
displayedAllocated: []
};
ctrl.tableLimits = {
maxAllocation: -1
};
ctrl.tableHelpText = {
allocHelpText: gettext('Select ports from those listed below.')
};
ctrl.tooltipModel = tooltipService;
ctrl.nameOrID = function nameOrId(data) {
return angular.isDefined(data.name) && data.name !== '' ? data.name : data.id;
};
}
})();

View File

@@ -0,0 +1,23 @@
<p translate>
A port represents a virtual switch port on a logical network switch.
</p>
<p translate>
Ports can be created under a network by administrators.
</p>
<p translate>
Containers attach their interfaces to ports.
</p>
<p translate>
The logical port also defines the MAC address and the IP address(es) to be assigned to the interfaces plugged into them.
</p>
<p translate>
When IP addresses are associated to a port, this also implies the port is associated with a subnet, as the IP address was taken from the allocation pool for a specific subnet.
</p>
<p translate>
When the <b>Admin State</b> for a port is set to <b>Up</b> and it has no <b>Device Owner</b>,
then the port is available for use. You can set the <b>Admin State</b> to <b>Down</b>
if you are not ready for other users to use the port.
</p>
<p translate>
The status indicates whether the port has an active connection.
</p>

View File

@@ -0,0 +1,154 @@
<div ng-controller="horizon.dashboard.container.containers.workflow.ports as ctrl">
<p class="step-description" translate>
Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both.
</p>
<transfer-table tr-model="ctrl.tableDataMulti" help-text="ctrl.tableHelpText" limits="ctrl.tableLimits">
<allocated>
<table st-table="ctrl.tableDataMulti.displayedAllocated" st-safe-src="ctrl.tableDataMulti.allocated"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="reorder"></th>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th class="rsp-p2" translate>IP</th>
<th st-sort="admin_state" class="rsp-p1" translate>Admin State</th>
<th st-sort="status" class="rsp-p1" translate>Status</th>
<th class="actions_column"></th>
</tr>
</thead>
<tbody>
<tr ng-if="ctrl.tableDataMulti.allocated.length === 0">
<td colspan="7">
<div class="no-rows-help" translate>
Select an item from Available items below
</div>
</td>
</tr>
<tr ng-repeat-start="item in ctrl.tableDataMulti.displayedAllocated track by item.id"
lr-drag-data="ctrl.tableDataMulti.displayedAllocated" lr-drag-src="reorder"
lr-drop-target="reorder" lr-drop-success="trCtrl.updateAllocated(e, item, collection)">
<td class="reorder">
<span class="fa fa-sort" title="{$ 'Re-order items using drag and drop'|translate $}"></span>
{$ $index + 1 $}
</td>
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ 'Click to see more details'|translate $}"></span>
</td>
<td class="rsp-p1 word-break">{$ ctrl.nameOrID(item) $}</td>
<td class="rsp-p2">
<div ng-repeat="ip in item.fixed_ips">
{$ ip.ip_address $} on subnet: {$ item.subnet_names[ip.ip_address] $}
</div>
</td>
<td class="rsp-p1">{$ item.admin_state | decode:ctrl.portAdminStates $}</td>
<td class="rsp-p1">{$ item.status | decode:ctrl.portStatuses $}</td>
<td class="actions_column">
<action-list>
<action action-classes="'btn btn-default'"
callback="trCtrl.deallocate" item="item">
<span class="fa fa-arrow-down"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<td colspan="7" class="detail">
<dl class="dl-horizontal">
<dt translate>ID</dt>
<dd>{$ item.id $}</dd>
<dt translate>Project ID</dt>
<dd>{$ item.tenant_id $}</dd>
<dt translate>Network ID</dt>
<dd>{$ item.network_id $}</dd>
<dt translate>Network</dt>
<dd>{$ item.network_name $}</dd>
<dt translate>VNIC type</dt>
<dd>{$ item['binding:vnic_type'] | decode:ctrl.vnicTypes $}</dd>
<div ng-if="item['binding:host_id']">
<dt translate>Host ID</dt>
<dd>{$ item['binding:host_id'] $}</dd>
</div>
</dl>
</td>
</tr>
</tbody>
</table>
</allocated>
<available>
<table st-table="ctrl.tableDataMulti.displayedAvailable" st-safe-src="ctrl.tableDataMulti.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="search-header" colspan="6">
<hz-search-bar icon-classes="fa-search"></hz-search-bar>
</th>
</tr>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th class="rsp-p2" translate>IP</th>
<th st-sort="admin_state" class="rsp-p1" translate>Admin State</th>
<th st-sort="status" class="rsp-p1" translate>Status</th>
<th class="actions_column"></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="6">
<div class="no-rows-help" translate>
No available items
</div>
</td>
</tr>
<tr ng-repeat-start="item in ctrl.tableDataMulti.displayedAvailable track by item.id"
ng-if="!trCtrl.allocatedIds[item.id]">
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ 'Click to see more details'|translate $}"></span>
</td>
<td class="rsp-p1 word-break">{$ ctrl.nameOrID(item) $}</td>
<td class="rsp-p2">
<div ng-repeat="ip in item.fixed_ips">
{$ ip.ip_address $} on subnet: {$ item.subnet_names[ip.ip_address] $}
</div>
</td>
<td class="rsp-p1">{$ item.admin_state | decode:ctrl.portAdminStates $}</td>
<td class="rsp-p1">{$ item.status | decode:ctrl.portStatuses $}</td>
<td class="actions_column">
<action-list>
<action action-classes="'btn btn-default'"
callback="trCtrl.allocate" item="item">
<span class="fa fa-arrow-up"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<td colspan="6" class="detail">
<dl class="dl-horizontal">
<dt translate>ID</dt>
<dd>{$ item.id $}</dd>
<dt translate>Project ID</dt>
<dd>{$ item.tenant_id $}</dd>
<dt translate>Network ID</dt>
<dd>{$ item.network_id $}</dd>
<dt translate>Network</dt>
<dd>{$ item.network_name $}</dd>
<dt translate>VNIC type</dt>
<dd>{$ item['binding:vnic_type'] | decode:ctrl.vnicTypes $}</dd>
<div ng-if="item['binding:host_id']">
<dt translate>Host ID</dt>
<dd>{$ item['binding:host_id'] $}</dd>
</div>
</dl>
</td>
</tr>
</tbody>
</table>
</available>
</transfer-table>
</div>

View File

@@ -20,17 +20,19 @@
.factory("horizon.dashboard.container.containers.workflow", workflow);
workflow.$inject = [
"horizon.app.core.openstack-service-api.neutron",
"horizon.dashboard.container.basePath",
"horizon.framework.util.i18n.gettext",
"horizon.framework.widgets.metadata.tree.service"
];
function workflow(basePath, gettext, treeService) {
function workflow(neutron, basePath, gettext, treeService) {
var workflow = {
init: init
};
function init(action, title, submitText) {
var push = Array.prototype.push;
var schema, form, model;
var imageDrivers = [
{value: "docker", name: gettext("Docker Hub")},
@@ -241,6 +243,44 @@
}
]
},
{
"title": gettext("Networks"),
help: basePath + "containers/actions/workflow/networks/networks.help.html",
type: "section",
htmlClass: "row",
items: [
{
type: "section",
htmlClass: "col-xs-12",
items: [
{
type: "template",
templateUrl: basePath + "containers/actions/workflow/networks/networks.html"
}
]
}
],
condition: action === "update"
},
{
"title": gettext("Ports"),
help: basePath + "containers/actions/workflow/ports/ports.help.html",
type: "section",
htmlClass: "row",
items: [
{
type: "section",
htmlClass: "col-xs-12",
items: [
{
type: "template",
templateUrl: basePath + "containers/actions/workflow/ports/ports.html"
}
]
}
],
condition: action === "update"
},
{
"title": gettext("Security Groups"),
/* eslint-disable max-len */
@@ -351,6 +391,10 @@
memory: "",
restart_policy: "",
restart_policy_max_retry: "",
// networks
networks: [],
// ports
ports: [],
// security groups
security_groups: [],
// misc
@@ -368,6 +412,60 @@
// initialize tree object for scheduler hints.
model.hintsTree = new treeService.Tree(model.availableHints, {});
// available networks
model.availableNetworks = [];
// available ports
model.availablePorts = [];
// get available neutron networks and ports
getNetworks();
function getNetworks() {
return neutron.getNetworks().then(onGetNetworks).then(getPorts);
}
function onGetNetworks(response) {
push.apply(model.availableNetworks,
response.data.items.filter(function(network) {
return network.subnets.length > 0;
}));
return response;
}
function getPorts(networks) {
networks.data.items.forEach(function(network) {
return neutron.getPorts({network_id: network.id}).then(
function(ports) {
onGetPorts(ports, network);
}
);
});
}
function onGetPorts(ports, network) {
ports.data.items.forEach(function(port) {
// no device_owner means that the port can be attached
if (port.device_owner === "" && port.admin_state === "UP") {
port.subnet_names = getPortSubnets(port, network.subnets);
port.network_name = network.name;
model.availablePorts.push(port);
}
});
}
// helper function to return an object of IP:NAME pairs for subnet mapping
function getPortSubnets(port, subnets) {
var subnetNames = {};
port.fixed_ips.forEach(function (ip) {
subnets.forEach(function (subnet) {
if (ip.subnet_id === subnet.id) {
subnetNames[ip.ip_address] = subnet.name;
}
});
});
return subnetNames;
}
var config = {
title: title,
submitText: submitText,