From 2aeb99a145b9fe01d6f51ceffbf872ba79bfd93a Mon Sep 17 00:00:00 2001 From: Itxaka Date: Fri, 4 Dec 2015 17:11:25 +0100 Subject: [PATCH] Add Network Port selection to new instance launch Adds a new step to the new instance launch with the available ports. The operator can add or remove ports to be attached to the instance on boot. This allows the operator to use ports at launch instead or together with a network. Adds tests and modifies other tests to take into account the new number of steps and the new addition to the model. Change-Id: I29fdce433a5a8a9e3ebedbd1a1d96399890e4c6c Implements: blueprint allow-launching-ports --- .../launch-instance-model.service.js | 55 ++++++- .../launch-instance-model.service.spec.js | 150 +++++++++++------ .../launch-instance-workflow.service.js | 8 + .../launch-instance-workflow.service.spec.js | 9 +- .../network/network.controller.js | 30 +++- .../network/network.controller.spec.js | 43 +++-- .../launch-instance/network/network.html | 2 +- .../networkports/ports.controller.js | 74 +++++++++ .../networkports/ports.help.html | 23 +++ .../launch-instance/networkports/ports.html | 154 ++++++++++++++++++ .../networkports/ports.spec.js | 87 ++++++++++ .../source/source.controller.spec.js | 1 + 12 files changed, 571 insertions(+), 65 deletions(-) create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.controller.js create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.help.html create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.html create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.spec.js diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js index d8000bb108..004a0e8daa 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js @@ -119,6 +119,7 @@ instance: null }, networks: [], + ports: [], neutronEnabled: false, novaLimits: {}, profiles: [], @@ -154,6 +155,7 @@ // REQUIRED name: null, networks: [], + ports: [], profile: {}, // REQUIRED Server Key. May be empty. security_groups: [], @@ -254,6 +256,7 @@ setFinalSpecBootsource(finalSpec); setFinalSpecFlavor(finalSpec); setFinalSpecNetworks(finalSpec); + setFinalSpecPorts(finalSpec); setFinalSpecKeyPairs(finalSpec); setFinalSpecSecurityGroups(finalSpec); setFinalSpecMetadata(finalSpec); @@ -359,13 +362,14 @@ // Networks function getNetworks() { - return neutronAPI.getNetworks().then(onGetNetworks, noop); + return neutronAPI.getNetworks().then(onGetNetworks, noop).then(getPorts, noop); } function onGetNetworks(data) { model.neutronEnabled = true; model.networks.length = 0; push.apply(model.networks, data.data.items); + return data; } function setFinalSpecNetworks(finalSpec) { @@ -380,6 +384,55 @@ delete finalSpec.networks; } + function getPorts(networks) { + model.ports.length = 0; + networks.data.items.forEach(function(network) { + return neutronAPI.getPorts({network_id: network.id}).then( + function(ports) { + onGetPorts(ports, network); + }, noop + ); + }); + } + + function onGetPorts(networkPorts, network) { + var ports = []; + networkPorts.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; + ports.push(port); + } + }); + push.apply(model.ports, ports); + } + + // 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; + } + + function setFinalSpecPorts(finalSpec) { + // nics should already be filled so we only append to it + finalSpec.ports.forEach(function (port) { + finalSpec.nics.push( + { + "port-id": port.id + }); + }); + delete finalSpec.ports; + } + // Boot Source function getImages() { diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js index 9ed92f01f9..3df402dcc9 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js @@ -23,6 +23,53 @@ var cinderEnabled = false; var neutronEnabled = false; var novaExtensionsEnabled = false; + var novaApi = { + createServer: function(finalSpec) { + return { + then: function () { + return finalSpec; + } + }; + }, + getAvailabilityZones: function() { + var zones = [ + { zoneName: 'zone-1', zoneState: { available: true } }, + { zoneName: 'zone-2', zoneState: { available: true } }, + { zoneName: 'invalid-zone-1' }, + { zoneName: 'invalid-zone-2' } + ]; + + var deferred = $q.defer(); + deferred.resolve({ data: { items: zones } }); + + return deferred.promise; + }, + getFlavors: function() { + var flavors = [ 'flavor-1', 'flavor-2' ]; + + var deferred = $q.defer(); + deferred.resolve({ data: { items: flavors } }); + + return deferred.promise; + }, + getKeypairs: function() { + var keypairs = [ { keypair: { name: 'key-1' } }, + { keypair: { name: 'key-2' } } ]; + + var deferred = $q.defer(); + deferred.resolve({ data: { items: keypairs } }); + + return deferred.promise; + }, + getLimits: function() { + var limits = { maxTotalInstances: 10, totalInstancesUsed: 0 }; + + var deferred = $q.defer(); + deferred.resolve({ data: limits }); + + return deferred.promise; + } + }; beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); @@ -61,53 +108,7 @@ }; }); - $provide.value('horizon.app.core.openstack-service-api.nova', { - createServer: function(finalSpec) { - return { - then: function () { - return finalSpec; - } - }; - }, - getAvailabilityZones: function() { - var zones = [ - { zoneName: 'zone-1', zoneState: { available: true } }, - { zoneName: 'zone-2', zoneState: { available: true } }, - { zoneName: 'invalid-zone-1' }, - { zoneName: 'invalid-zone-2' } - ]; - - var deferred = $q.defer(); - deferred.resolve({ data: { items: zones } }); - - return deferred.promise; - }, - getFlavors: function() { - var flavors = [ 'flavor-1', 'flavor-2' ]; - - var deferred = $q.defer(); - deferred.resolve({ data: { items: flavors } }); - - return deferred.promise; - }, - getKeypairs: function() { - var keypairs = [ { keypair: { name: 'key-1' } }, - { keypair: { name: 'key-2' } } ]; - - var deferred = $q.defer(); - deferred.resolve({ data: { items: keypairs } }); - - return deferred.promise; - }, - getLimits: function() { - var limits = { maxTotalInstances: 10, totalInstancesUsed: 0 }; - - var deferred = $q.defer(); - deferred.resolve({ data: limits }); - - return deferred.promise; - } - }); + $provide.value('horizon.app.core.openstack-service-api.nova', novaApi); $provide.value('horizon.app.core.openstack-service-api.security-group', { query: function() { @@ -130,6 +131,23 @@ var deferred = $q.defer(); deferred.resolve({ data: { items: networks } }); + return deferred.promise; + }, + getPorts: function(network) { + var ports = { + 'net-1': [ + { name: 'port-1', device_owner: '', fixed_ips: [], admin_state: 'UP' }, + { name: 'port-2', device_owner: '', fixed_ips: [], admin_state: 'DOWN' } + ], + 'net-2': [ + { name: 'port-3', device_owner: 'owner', fixed_ips: [], admin_state: 'DOWN' }, + { name: 'port-4', device_owner: '', fixed_ips: [], admin_state: 'DOWN' } + ] + }; + + var deferred = $q.defer(); + deferred.resolve({ data: { items: ports[network.network_id] } }); + return deferred.promise; } }); @@ -351,6 +369,24 @@ expect(model.newInstanceSpec.config_drive).toBe(true); }); + + it('should not set availability zone if the zone list is empty', function () { + spyOn(novaApi, 'getAvailabilityZones').and.callFake(function () { + var deferred = $q.defer(); + deferred.resolve({ data: { items: [] } }); + return deferred.promise; + }); + model.initialize(true); + scope.$apply(); + expect(model.availabilityZones.length).toBe(0); + expect(model.newInstanceSpec.availability_zone).toBe(null); + }); + + it('sets the ports properly based on device_owner', function () { + model.initialize(true); + scope.$apply(); + expect(model.ports.length).toBe(1); + }); }); describe('Post Initialization Model - Initializing', function() { @@ -363,7 +399,7 @@ // This is here to ensure that as people add/change items, they // don't forget to implement tests for them. it('has the right number of properties', function() { - expect(Object.keys(model.newInstanceSpec).length).toBe(18); + expect(Object.keys(model.newInstanceSpec).length).toBe(19); }); it('sets availability zone to null', function() { @@ -406,6 +442,10 @@ expect(model.newInstanceSpec.networks).toEqual([]); }); + it('sets ports to an empty array', function() { + expect(model.newInstanceSpec.ports).toEqual([]); + }); + it('sets profile to an empty object', function() { expect(model.newInstanceSpec.profile).toEqual({}); }); @@ -440,6 +480,7 @@ model.newInstanceSpec.source = [ { id: 'cirros' } ]; model.newInstanceSpec.flavor = { id: 'm1.tiny' }; model.newInstanceSpec.networks = [ { id: 'public' }, { id: 'private' } ]; + model.newInstanceSpec.ports = [ ]; model.newInstanceSpec.key_pair = [ { name: 'keypair1' } ]; model.newInstanceSpec.security_groups = [ { id: 'adminId', name: 'admin' }, { id: 'demoId', name: 'demo' } ]; @@ -569,6 +610,19 @@ expect(finalSpec.useless).toBeUndefined(); }); + it('should set final spec in format required if ports are used', function() { + model.newInstanceSpec.ports = [{id: 'port1'}]; + + var finalSpec = model.createInstance(); + var finalNetworks = [ + { 'net-id': 'public', 'v4-fixed-ip': '' }, + { 'net-id': 'private', 'v4-fixed-ip': '' }, + { 'port-id': 'port1' } + ]; + + expect(finalSpec.nics).toEqual(finalNetworks); + }); + it('provides null for device_name when falsy', function() { model.newInstanceSpec.source_type.type = 'image'; model.newInstanceSpec.vol_device_name = false; diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js index 1b0717e149..5ac0fc1c9a 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js @@ -59,6 +59,14 @@ formName: 'launchInstanceNetworkForm', requiredServiceTypes: ['network'] }, + { + id: 'ports', + title: gettext('Network Ports'), + templateUrl: basePath + 'networkports/ports.html', + helpUrl: basePath + 'networkports/ports.help.html', + formName: 'launchInstanceNetworkPortForm', + requiredServiceTypes: ['network'] + }, { id: 'secgroups', title: gettext('Security Groups'), diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js index d5334c7abc..a9aab4ac02 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js @@ -49,15 +49,16 @@ expect(launchInstanceWorkflow.title).toBeDefined(); }); - it('should have the eight steps defined', function () { + it('should have the nine steps defined', function () { expect(launchInstanceWorkflow.steps).toBeDefined(); - expect(launchInstanceWorkflow.steps.length).toBe(8); + expect(launchInstanceWorkflow.steps.length).toBe(9); var forms = [ 'launchInstanceDetailsForm', 'launchInstanceSourceForm', 'launchInstanceFlavorForm', 'launchInstanceNetworkForm', + 'launchInstanceNetworkPortForm', 'launchInstanceAccessAndSecurityForm', 'launchInstanceKeypairForm', 'launchInstanceConfigurationForm', @@ -72,6 +73,10 @@ it('specifies that the network step requires the network service type', function() { expect(launchInstanceWorkflow.steps[3].requiredServiceTypes).toEqual(['network']); }); + + it('specifies that the network port step requires the network service type', function() { + expect(launchInstanceWorkflow.steps[4].requiredServiceTypes).toEqual(['network']); + }); }); })(); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.js index 69d7750c38..6126c9099e 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.js @@ -28,10 +28,11 @@ LaunchInstanceNetworkController.$inject = [ '$scope', - 'horizon.framework.widgets.action-list.button-tooltip.row-warning.service' + 'horizon.framework.widgets.action-list.button-tooltip.row-warning.service', + 'launchInstanceModel' ]; - function LaunchInstanceNetworkController($scope, tooltipService) { + function LaunchInstanceNetworkController($scope, tooltipService, launchInstanceModel) { var ctrl = this; ctrl.networkStatuses = { @@ -48,7 +49,8 @@ available: $scope.model.networks, allocated: $scope.model.newInstanceSpec.networks, displayedAvailable: [], - displayedAllocated: [] + displayedAllocated: [], + minItems: 1 }; ctrl.tableLimits = { @@ -101,6 +103,28 @@ ] } ]; + + function getPorts() { + return launchInstanceModel.newInstanceSpec.ports; + } + + function toggleNetworksRequirement(newValue) { + // if there is a port selected, remove the validate-number-min + // for networks table + if (newValue.length > 0) { + ctrl.tableDataMulti.minItems = 0; + } + // if no port is selected restore the validate-number-min value + if (newValue.length === 0) { + ctrl.tableDataMulti.minItems = 1; + } + } + // If a port is selected, then networks are not required + var portWatcher = $scope.$watch(getPorts, toggleNetworksRequirement, true); + + $scope.$on('$destroy', function() { + portWatcher(); + }); } })(); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.spec.js index 7951fd7516..d2e962fc54 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.spec.js @@ -23,22 +23,44 @@ beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); describe('LaunchInstanceNetworkController', function() { - var scope, ctrl; + var scope, ctrl, model; - beforeEach(inject(function($controller) { - scope = { - model: { - newInstanceSpec: { - networks: ['net-a'] - }, - networks: ['net-a', 'net-b'] - } + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + + model = { + newInstanceSpec: { + networks: ['net-a'], + ports: [] + }, + networks: ['net-a', 'net-b'] }; + + scope.model = model; + + spyOn(scope, '$watch').and.callThrough(); + spyOn(scope, '$watchCollection').and.callThrough(); + ctrl = $controller('LaunchInstanceNetworkController', { - $scope: scope + $scope: scope, + launchInstanceModel: model }); })); + it("establishes one watch", function () { + expect(scope.$watch.calls.count()).toBe(1); + }); + + it("changes the network items required based on the ports", function() { + expect(ctrl.tableDataMulti.minItems).toEqual(1); + model.newInstanceSpec.ports = [{name: "1", id: "1"}]; + scope.$apply(); + expect(ctrl.tableDataMulti.minItems).toEqual(0); + model.newInstanceSpec.ports = []; + scope.$apply(); + expect(ctrl.tableDataMulti.minItems).toEqual(1); + }); + it('has correct network statuses', function() { expect(ctrl.networkStatuses).toBeDefined(); expect(ctrl.networkStatuses.ACTIVE).toBeDefined(); @@ -70,6 +92,7 @@ expect(ctrl.tableDataMulti.allocated).toEqual(['net-a']); expect(ctrl.tableDataMulti.displayedAllocated).toEqual([]); expect(ctrl.tableDataMulti.displayedAvailable).toEqual([]); + expect(ctrl.tableDataMulti.minItems).toEqual(1); }); it('should set facets for search', function() { diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.html index dde60f2e7f..f0ae45a6a2 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.html +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.html @@ -11,7 +11,7 @@ - + diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.controller.js new file mode 100644 index 0000000000..b237992b6c --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.controller.js @@ -0,0 +1,74 @@ +/* + * (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 LaunchInstanceNetworkPortController + * @description + * Controller for the Launch Instance - Network Step. + */ + angular + .module('horizon.dashboard.project.workflow.launch-instance') + .controller('LaunchInstanceNetworkPortController', LaunchInstanceNetworkPortController); + + LaunchInstanceNetworkPortController.$inject = [ + '$scope', + 'horizon.framework.widgets.action-list.button-tooltip.row-warning.service' + ]; + + function LaunchInstanceNetworkPortController($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'), + 'macvtap': gettext('MacVTap') + }; + + ctrl.tableDataMulti = { + available: $scope.model.ports, + allocated: $scope.model.newInstanceSpec.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; + }; + } +})(); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.help.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.help.html new file mode 100644 index 0000000000..11eaa8e7ad --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.help.html @@ -0,0 +1,23 @@ +

+ A port represents a virtual switch port on a logical network switch. +

+

+ Ports can be created under a network by administrators. +

+

+ Virtual instances attach their interfaces to ports. +

+

+ The logical port also defines the MAC address and the IP address(es) to be assigned to the interfaces plugged into them. +

+

+ 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. +

+

+ When the Admin State for a port is set to Up and it has no Device Owner, + then the port is available for use. You can set the Admin State to Down + if you are not ready for other users to use the port. +

+

+ The status indicates whether the port has an active connection. +

diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.html new file mode 100644 index 0000000000..d8801c487e --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.html @@ -0,0 +1,154 @@ +
+

+ Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both. +

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameIPAdmin StateStatus
+
+ Select an item from Available items below +
+
+ + {$ $index + 1 $} + + + {$ ctrl.nameOrID(item) $} +
+ {$ ip.ip_address $} on subnet: {$ item.subnet_names[ip.ip_address] $} +
+
{$ item.admin_state | decode:ctrl.portAdminStates $}{$ item.status | decode:ctrl.portStatuses $} + + + + + +
+
+
ID
+
{$ item.id $}
+
Project ID
+
{$ item.tenant_id $}
+
Network ID
+
{$ item.network_id $}
+
Network
+
{$ item.network_name $}
+
VNIC type
+
{$ item['binding:vnic_type'] | decode:ctrl.vnicTypes $}
+
+
Host ID
+
{$ item['binding:host_id'] $}
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
NameIPAdmin StateStatus
+
+ No available items +
+
+ + {$ ctrl.nameOrID(item) $} +
+ {$ ip.ip_address $} on subnet: {$ item.subnet_names[ip.ip_address] $} +
+
{$ item.admin_state | decode:ctrl.portAdminStates $}{$ item.status | decode:ctrl.portStatuses $} + + + + + +
+
+
ID
+
{$ item.id $}
+
Project ID
+
{$ item.tenant_id $}
+
Network ID
+
{$ item.network_id $}
+
Network
+
{$ item.network_name $}
+
VNIC type
+
{$ item['binding:vnic_type'] | decode:ctrl.vnicTypes $}
+
+
Host ID
+
{$ item['binding:host_id'] $}
+
+
+
+
+
+ diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.spec.js new file mode 100644 index 0000000000..0e25313c2b --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.spec.js @@ -0,0 +1,87 @@ +/* + * (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'; + + describe('Launch Instance Ports Step', function() { + + beforeEach(module('horizon.framework.widgets')); + beforeEach(module('horizon.framework.widgets.action-list')); + beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); + + describe('LaunchInstanceNetworkPortController', function() { + var scope, ctrl; + + beforeEach(inject(function($controller) { + scope = { + model: { + newInstanceSpec: { + ports: ['port-a'] + }, + ports: ['port-a', 'port-b'] + } + }; + ctrl = $controller('LaunchInstanceNetworkPortController', { + $scope: scope + }); + })); + + it('has correct ports statuses', function() { + expect(ctrl.portStatuses).toBeDefined(); + expect(ctrl.portStatuses.ACTIVE).toBeDefined(); + expect(ctrl.portStatuses.DOWN).toBeDefined(); + expect(Object.keys(ctrl.portStatuses).length).toBe(2); + }); + + it('has correct network admin states', function() { + expect(ctrl.portAdminStates).toBeDefined(); + expect(ctrl.portAdminStates.UP).toBeDefined(); + expect(ctrl.portAdminStates.DOWN).toBeDefined(); + expect(Object.keys(ctrl.portAdminStates).length).toBe(2); + }); + + it('defines a multiple-allocation table', function() { + expect(ctrl.tableLimits).toBeDefined(); + expect(ctrl.tableLimits.maxAllocation).toBe(-1); + }); + + it('contains help text for the table', function() { + expect(ctrl.tableHelpText).toBeDefined(); + expect(ctrl.tableHelpText.allocHelpText).toBeDefined(); + }); + + it('nameOrId return the name', function() { + var obj = {name: 'test_name', id: 'test_id'}; + expect(ctrl.nameOrID).toBeDefined(); + expect(ctrl.nameOrID(obj)).toBe('test_name'); + }); + + it('nameOrId return the id if the name is missing', function() { + expect(ctrl.nameOrID).toBeDefined(); + expect(ctrl.nameOrID({'id': 'testid'})).toBe('testid'); + }); + + it('uses scope to set table data', function() { + expect(ctrl.tableDataMulti).toBeDefined(); + expect(ctrl.tableDataMulti.available).toEqual(['port-a', 'port-b']); + expect(ctrl.tableDataMulti.allocated).toEqual(['port-a']); + expect(ctrl.tableDataMulti.displayedAllocated).toEqual([]); + expect(ctrl.tableDataMulti.displayedAvailable).toEqual([]); + }); + }); + + }); +})(); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js index b8d70d12d7..055928d48c 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js @@ -303,6 +303,7 @@ }); }); }); + }); describe('diskFormatFilter', function() {