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() {