diff --git a/openstack_dashboard/dashboards/project/instances/templates/instances/_launch_network_ports_help.html b/openstack_dashboard/dashboards/project/instances/templates/instances/_launch_network_ports_help.html new file mode 100644 index 000000000..7f91c2054 --- /dev/null +++ b/openstack_dashboard/dashboards/project/instances/templates/instances/_launch_network_ports_help.html @@ -0,0 +1,7 @@ +{% load i18n %} + +{% block help_message %} +

{% blocktrans %}A port is a connection point for attaching a single device, such as the NIC of a virtual server, to a virtual network.{% endblocktrans %}

+

{% blocktrans %}The port also describes the associated network configuration, such as the MAC and IP addresses to be used on that port.{% endblocktrans %}

+

{% blocktrans %}Ports are optional and can be used with networks to add extra IP addresses to your instances or select specific types of ports.{% endblocktrans %}

+{% endblock %} diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 48aa7541a..55809b0f7 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -1468,7 +1468,8 @@ class InstanceTests(helpers.TestCase): cinder: ('volume_snapshot_list', 'volume_list',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.glance: ('image_list_detailed',)}) def test_launch_instance_get(self, expect_password_fields=True, @@ -1509,6 +1510,19 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) + if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -1549,6 +1563,7 @@ class InstanceTests(helpers.TestCase): ['', '', '', + '', '', '']) @@ -1693,7 +1708,8 @@ class InstanceTests(helpers.TestCase): cinder: ('volume_snapshot_list', 'volume_list',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.glance: ('image_list_detailed',)}) def test_launch_instance_get_bootable_volumes(self, block_device_mapping_v2=True, @@ -1732,7 +1748,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) - + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -1785,7 +1811,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', 'profile_list', - 'port_create',), + 'port_create', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -1837,6 +1864,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() policy_profile_id = self.policy_profiles.first().id @@ -1985,7 +2023,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) - + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) policy_profiles = self.policy_profiles.list() policy_profile_id = self.policy_profiles.first().id port_one = self.ports.first() @@ -2060,7 +2108,8 @@ class InstanceTests(helpers.TestCase): api.neutron: ('network_list', 'profile_list', 'port_create', - 'port_delete',), + 'port_delete', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2078,7 +2127,8 @@ class InstanceTests(helpers.TestCase): api.neutron: ('network_list', 'profile_list', 'port_create', - 'port_delete',), + 'port_delete', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2094,7 +2144,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', 'profile_list', - 'port_create',), + 'port_create', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2165,6 +2216,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) if test_with_profile: @@ -2253,7 +2315,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', 'profile_list', - 'port_create'), + 'port_create', + 'port_list'), api.nova: ('server_create', 'extension_supported', 'flavor_list', @@ -2308,6 +2371,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) if test_with_profile: @@ -2393,7 +2467,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2436,6 +2511,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) if test_with_profile: @@ -2495,7 +2581,8 @@ class InstanceTests(helpers.TestCase): api.glance: ('image_list_detailed',), api.neutron: ('network_list', 'profile_list', - 'port_create',), + 'port_create', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2567,6 +2654,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) if test_with_profile: @@ -2657,7 +2755,8 @@ class InstanceTests(helpers.TestCase): api.glance: ('image_list_detailed',), api.neutron: ('network_list', 'profile_list', - 'port_create',), + 'port_create', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2698,6 +2797,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.nova.keypair_list(IsA(http.HttpRequest)) \ @@ -2749,7 +2859,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), cinder: ('volume_list', 'volume_snapshot_list',), api.network: ('security_group_list',), @@ -2785,6 +2896,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -2823,7 +2945,8 @@ class InstanceTests(helpers.TestCase): api.neutron: ('network_list', 'profile_list', 'port_create', - 'port_delete'), + 'port_delete', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -2880,6 +3003,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() policy_profile_id = self.policy_profiles.first().id @@ -2956,7 +3090,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3007,6 +3142,18 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -3058,7 +3205,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3114,6 +3262,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -3189,7 +3348,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3239,6 +3399,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -3330,7 +3501,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3380,6 +3552,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list( IsA(http.HttpRequest), shared=True).AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.extension_supported( 'DiskConfig', IsA(http.HttpRequest)).AndReturn(True) api.nova.extension_supported( @@ -3449,7 +3632,8 @@ class InstanceTests(helpers.TestCase): @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list', - 'profile_list',), + 'profile_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3502,6 +3686,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -3681,7 +3876,8 @@ class InstanceTests(helpers.TestCase): six.text_type(launch_action.verbose_name)) @helpers.create_stubs({api.glance: ('image_list_detailed',), - api.neutron: ('network_list',), + api.neutron: ('network_list', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -3739,6 +3935,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) api.nova.extension_supported('DiskConfig', IsA(http.HttpRequest)) \ .AndReturn(True) @@ -3845,7 +4052,8 @@ class InstanceTests(helpers.TestCase): cinder: ('volume_snapshot_list', 'volume_list',), api.neutron: ('network_list', - 'profile_list'), + 'profile_list', + 'port_list'), api.glance: ('image_list_detailed',)}) def test_select_default_keypair_if_only_one(self, test_with_profile=False): @@ -3873,6 +4081,17 @@ class InstanceTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) if test_with_profile: policy_profiles = self.policy_profiles.list() api.neutron.profile_list(IsA(http.HttpRequest), @@ -4846,7 +5065,8 @@ class ConsoleManagerTests(helpers.TestCase): api.neutron: ('network_list', 'profile_list', 'port_create', - 'port_delete'), + 'port_delete', + 'port_list'), api.nova: ('extension_supported', 'flavor_list', 'keypair_list', @@ -4901,6 +5121,17 @@ class ConsoleManagerTests(helpers.TestCase): api.neutron.network_list(IsA(http.HttpRequest), shared=True) \ .AndReturn(self.networks.list()[1:]) + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.tenant.id, + shared=False) \ + .AndReturn(self.networks.list()[:1]) + api.neutron.network_list(IsA(http.HttpRequest), + shared=True) \ + .AndReturn(self.networks.list()[1:]) + for net in self.networks.list(): + api.neutron.port_list(IsA(http.HttpRequest), + network_id=net.id) \ + .AndReturn(self.ports.list()) policy_profiles = self.policy_profiles.list() policy_profile_id = self.policy_profiles.first().id port = self.ports.first() diff --git a/openstack_dashboard/dashboards/project/instances/utils.py b/openstack_dashboard/dashboards/project/instances/utils.py index 81458ea1c..e85ae80ab 100644 --- a/openstack_dashboard/dashboards/project/instances/utils.py +++ b/openstack_dashboard/dashboards/project/instances/utils.py @@ -155,3 +155,33 @@ def flavor_field_data(request, include_empty_option=False): if include_empty_option: return [("", _("No flavors available")), ] return [] + + +def port_field_data(request): + """Returns a list of tuples of all ports available for the tenant. + + Generates a list of ports that have no device_owner based on the networks + available to the tenant doing the request. + + :param request: django http request object + :return: list of (id, name) tuples + """ + + def add_more_info_port_name(port): + # add more info to the port for the display + return "{} ({})".format(port.name_or_id, + ",".join([ip['ip_address'] + for ip in port['fixed_ips']])) + + ports = [] + if api.base.is_service_enabled(request, 'network'): + network_list = api.neutron.network_list_for_tenant( + request, request.user.tenant_id) + for network in network_list: + ports.extend( + [(port.id, add_more_info_port_name(port)) + for port in api.neutron.port_list(request, + network_id=network.id) + if port.device_owner == '']) + ports.sort(key=lambda obj: obj[1]) + return ports diff --git a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py index f2b7698e3..00e7481a7 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py @@ -779,6 +779,39 @@ class SetNetwork(workflows.Step): return context +class SetNetworkPortsAction(workflows.Action): + ports = forms.MultipleChoiceField(label=_("Ports"), + widget=forms.CheckboxSelectMultiple(), + required=False, + help_text=_("Launch instance with" + " these ports")) + + class Meta(object): + name = _("Network Ports") + permissions = ('openstack.services.network',) + help_text_template = ("project/instances/" + "_launch_network_ports_help.html") + + def populate_ports_choices(self, request, context): + ports = instance_utils.port_field_data(request) + if not ports: + self.fields['ports'].label = _("No ports available") + self.fields['ports'].help_text = _("No ports available") + return ports + + +class SetNetworkPorts(workflows.Step): + action_class = SetNetworkPortsAction + contributes = ("ports",) + + def contribute(self, data, context): + if data: + ports = self.workflow.request.POST.getlist("ports") + if ports: + context['ports'] = ports + return context + + class SetAdvancedAction(workflows.Action): disk_config = forms.ChoiceField( label=_("Disk Partition"), required=False, @@ -844,6 +877,7 @@ class LaunchInstance(workflows.Workflow): SetInstanceDetails, SetAccessControls, SetNetwork, + SetNetworkPorts, PostCreationStep, SetAdvanced) @@ -932,6 +966,12 @@ class LaunchInstance(workflows.Workflow): context['network_id'], context['profile_id']) + ports = context.get('ports') + if ports: + if nics is None: + nics = [] + nics.extend([{'port-id': port} for port in ports]) + try: api.nova.server_create(request, context['name'], diff --git a/releasenotes/notes/bp-allow-launching-ports-b1fcc495777b7f4c.yaml b/releasenotes/notes/bp-allow-launching-ports-b1fcc495777b7f4c.yaml new file mode 100644 index 000000000..af5741f47 --- /dev/null +++ b/releasenotes/notes/bp-allow-launching-ports-b1fcc495777b7f4c.yaml @@ -0,0 +1,3 @@ +--- +features: + - Allows to attach ports during instance launch