Support "Get me a network" in launch instance
"Get-me-a-network" feature is supported in Nova API 2.37 or later. To support this in horizon, a dummy "auto_allocated_network" is shown in the launch instance form. I believe this approach fits the current way of the launch instance form. The dummy network is a special network ID and if specified the nova API wrapper converts 'nics' parameter properly. In addition, a dummy "auto_allocated_network" is now shown in the network table. This is because Neutron creates an actual auto allocated network once a server is created specifying nics=="auto". I believe this fake behavior brings consistency in the network table to some extent, while this is just a compromise. Note that this patch does not cover the network topology integration. "auto_allocated_network" is not shown until the actual auto allocated network is created. The network topology integration requires more work. For example, the link for the fake auto allocated network should be disabled in the flat view. The pop-up for the fake auto allocated network should be disabled in the graph view. Change-Id: I062fc1b7ed75dc771ddc7f13c8324ed4ffab6808 Closes-Bug: #1690433
This commit is contained in:
parent
2e0a3610bd
commit
d16ed45e06
@ -1552,6 +1552,7 @@ Default:
|
|||||||
|
|
||||||
{
|
{
|
||||||
'default_dns_nameservers': [],
|
'default_dns_nameservers': [],
|
||||||
|
'enable_auto_allocated_network': False,
|
||||||
'enable_distributed_router': False,
|
'enable_distributed_router': False,
|
||||||
'enable_fip_topology_check': True,
|
'enable_fip_topology_check': True,
|
||||||
'enable_ha_router': False,
|
'enable_ha_router': False,
|
||||||
@ -1581,6 +1582,22 @@ only a default. Users can still choose a different list of dns servers.
|
|||||||
|
|
||||||
Example: ``["8.8.8.8", "8.8.4.4", "208.67.222.222"]``
|
Example: ``["8.8.8.8", "8.8.4.4", "208.67.222.222"]``
|
||||||
|
|
||||||
|
enable_auto_allocated_network
|
||||||
|
#############################
|
||||||
|
|
||||||
|
.. versionadded:: 14.0.0(Rocky)
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
Enable or disable Nova and Neutron 'get-me-a-network' feature.
|
||||||
|
This sets up a neutron network topology for a project if there is no network
|
||||||
|
in the project. It simplifies the workflow when launching a server.
|
||||||
|
Horizon checks if both nova and neutron support the feature and enable it
|
||||||
|
only when supported. However, whether the feature works properly depends on
|
||||||
|
deployments, so this setting is disabled by default.
|
||||||
|
(The detail on the required preparation is described in `the Networking Guide
|
||||||
|
<https://docs.openstack.org/neutron/latest/admin/config-auto-allocation.html>`__.)
|
||||||
|
|
||||||
enable_distributed_router
|
enable_distributed_router
|
||||||
#########################
|
#########################
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ MICROVERSION_FEATURES = {
|
|||||||
"remote_console_mks": ["2.8", "2.53"],
|
"remote_console_mks": ["2.8", "2.53"],
|
||||||
"servergroup_soft_policies": ["2.15", "2.60"],
|
"servergroup_soft_policies": ["2.15", "2.60"],
|
||||||
"servergroup_user_info": ["2.13", "2.60"],
|
"servergroup_user_info": ["2.13", "2.60"],
|
||||||
"multiattach": ["2.60"]
|
"multiattach": ["2.60"],
|
||||||
|
"auto_allocated_network": ["2.37", "2.42"],
|
||||||
},
|
},
|
||||||
"cinder": {
|
"cinder": {
|
||||||
"consistency_groups": ["2.0", "3.10"],
|
"consistency_groups": ["2.0", "3.10"],
|
||||||
|
@ -117,6 +117,36 @@ class Subnet(NeutronAPIDictWrapper):
|
|||||||
super(Subnet, self).__init__(apidict)
|
super(Subnet, self).__init__(apidict)
|
||||||
|
|
||||||
|
|
||||||
|
AUTO_ALLOCATE_ID = '__auto_allocate__'
|
||||||
|
|
||||||
|
|
||||||
|
class PreAutoAllocateNetwork(Network):
|
||||||
|
def __init__(self, request):
|
||||||
|
tenant_id = request.user.tenant_id
|
||||||
|
auto_allocated_subnet = Subnet({
|
||||||
|
'name': 'auto_allocated_subnet',
|
||||||
|
'id': AUTO_ALLOCATE_ID,
|
||||||
|
'network_id': 'auto',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
# The following two fields are fake so that Subnet class
|
||||||
|
# and the network topology view work without errors.
|
||||||
|
'ip_version': 4,
|
||||||
|
'cidr': '0.0.0.0/0',
|
||||||
|
})
|
||||||
|
auto_allocated_network = {
|
||||||
|
'name': 'auto_allocated_network',
|
||||||
|
'description': 'Network to be allocated automatically',
|
||||||
|
'id': AUTO_ALLOCATE_ID,
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'admin_state_up': True,
|
||||||
|
'shared': False,
|
||||||
|
'router:external': False,
|
||||||
|
'subnets': [auto_allocated_subnet],
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
}
|
||||||
|
super(PreAutoAllocateNetwork, self).__init__(auto_allocated_network)
|
||||||
|
|
||||||
|
|
||||||
class Trunk(NeutronAPIDictWrapper):
|
class Trunk(NeutronAPIDictWrapper):
|
||||||
"""Wrapper for neutron trunks."""
|
"""Wrapper for neutron trunks."""
|
||||||
|
|
||||||
@ -989,8 +1019,35 @@ def network_list(request, **params):
|
|||||||
return [Network(n) for n in networks]
|
return [Network(n) for n in networks]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_auto_allocated_network_supported(request):
|
||||||
|
try:
|
||||||
|
neutron_auto_supported = is_service_enabled(
|
||||||
|
request, 'enable_auto_allocated_network',
|
||||||
|
'auto-allocated-topology', default=False)
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request, _('Failed to check if neutron supports '
|
||||||
|
'"auto_alloocated_network".'))
|
||||||
|
neutron_auto_supported = False
|
||||||
|
if not neutron_auto_supported:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# server_create needs to support both features,
|
||||||
|
# so we need to pass both features here.
|
||||||
|
nova_auto_supported = nova.is_feature_available(
|
||||||
|
request, ("instance_description",
|
||||||
|
"auto_allocated_network"))
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request, _('Failed to check if nova supports '
|
||||||
|
'"auto_alloocated_network".'))
|
||||||
|
nova_auto_supported = False
|
||||||
|
|
||||||
|
return nova_auto_supported
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
@profiler.trace
|
||||||
def network_list_for_tenant(request, tenant_id, include_external=False,
|
def network_list_for_tenant(request, tenant_id, include_external=False,
|
||||||
|
include_pre_auto_allocate=False,
|
||||||
**params):
|
**params):
|
||||||
"""Return a network list available for the tenant.
|
"""Return a network list available for the tenant.
|
||||||
|
|
||||||
@ -1016,6 +1073,12 @@ def network_list_for_tenant(request, tenant_id, include_external=False,
|
|||||||
# In the current Neutron API, there is no way to retrieve
|
# In the current Neutron API, there is no way to retrieve
|
||||||
# both owner networks and public networks in a single API call.
|
# both owner networks and public networks in a single API call.
|
||||||
networks += network_list(request, shared=True, **params)
|
networks += network_list(request, shared=True, **params)
|
||||||
|
|
||||||
|
# Hack for auto allocated network
|
||||||
|
if include_pre_auto_allocate and not networks:
|
||||||
|
if _is_auto_allocated_network_supported(request):
|
||||||
|
networks.append(PreAutoAllocateNetwork(request))
|
||||||
|
|
||||||
params['router:external'] = params.get('router:external', True)
|
params['router:external'] = params.get('router:external', True)
|
||||||
if params['router:external'] and include_external:
|
if params['router:external'] and include_external:
|
||||||
if shared is not None:
|
if shared is not None:
|
||||||
@ -1754,8 +1817,8 @@ def is_enabled_by_config(name, default=True):
|
|||||||
|
|
||||||
|
|
||||||
@memoized
|
@memoized
|
||||||
def is_service_enabled(request, config_name, ext_name):
|
def is_service_enabled(request, config_name, ext_name, default=True):
|
||||||
return (is_enabled_by_config(config_name) and
|
return (is_enabled_by_config(config_name, default) and
|
||||||
is_extension_supported(request, ext_name))
|
is_extension_supported(request, ext_name))
|
||||||
|
|
||||||
|
|
||||||
|
@ -512,10 +512,27 @@ def server_create(request, name, image, flavor, key_name, user_data,
|
|||||||
availability_zone=None, instance_count=1, admin_pass=None,
|
availability_zone=None, instance_count=1, admin_pass=None,
|
||||||
disk_config=None, config_drive=None, meta=None,
|
disk_config=None, config_drive=None, meta=None,
|
||||||
scheduler_hints=None, description=None):
|
scheduler_hints=None, description=None):
|
||||||
|
microversion = get_microversion(request, ("instance_description",
|
||||||
|
"auto_allocated_network"))
|
||||||
|
nova_client = novaclient(request, version=microversion)
|
||||||
|
|
||||||
|
# NOTE(amotoki): Handling auto allocated network
|
||||||
|
# Nova API 2.37 or later, it accepts a special string 'auto' for nics
|
||||||
|
# which means nova uses a network that is available for a current project
|
||||||
|
# if one exists and otherwise it creates a network automatically.
|
||||||
|
# This special handling is processed here as JS side assumes 'nics'
|
||||||
|
# is a list and it is easiest to handle it here.
|
||||||
|
if nics:
|
||||||
|
is_auto_allocate = any(nic.get('net-id') == '__auto_allocate__'
|
||||||
|
for nic in nics)
|
||||||
|
if is_auto_allocate:
|
||||||
|
nics = 'auto'
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if description is not None:
|
if description is not None:
|
||||||
kwargs['description'] = description
|
kwargs['description'] = description
|
||||||
return Server(get_novaclient_with_instance_desc(request).servers.create(
|
|
||||||
|
return Server(nova_client.servers.create(
|
||||||
name.strip(), image, flavor, userdata=user_data,
|
name.strip(), image, flavor, userdata=user_data,
|
||||||
security_groups=security_groups,
|
security_groups=security_groups,
|
||||||
key_name=key_name, block_device_mapping=block_device_mapping,
|
key_name=key_name, block_device_mapping=block_device_mapping,
|
||||||
|
@ -39,7 +39,13 @@ class Networks(generic.View):
|
|||||||
a network.
|
a network.
|
||||||
"""
|
"""
|
||||||
tenant_id = request.user.tenant_id
|
tenant_id = request.user.tenant_id
|
||||||
result = api.neutron.network_list_for_tenant(request, tenant_id)
|
# NOTE(amotoki): At now, this method is only for server create,
|
||||||
|
# so it is no problem to pass include_pre_auto_allocate=True always.
|
||||||
|
# We need to revisit the logic if we use this method for
|
||||||
|
# other operations other than server create.
|
||||||
|
result = api.neutron.network_list_for_tenant(
|
||||||
|
request, tenant_id,
|
||||||
|
include_pre_auto_allocate=True)
|
||||||
return{'items': [n.to_dict() for n in result]}
|
return{'items': [n.to_dict() for n in result]}
|
||||||
|
|
||||||
@rest_utils.ajax(data_required=True)
|
@rest_utils.ajax(data_required=True)
|
||||||
|
@ -86,7 +86,8 @@ def server_group_list(request):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def network_field_data(request, include_empty_option=False, with_cidr=False):
|
def network_field_data(request, include_empty_option=False, with_cidr=False,
|
||||||
|
for_launch=False):
|
||||||
"""Returns a list of tuples of all networks.
|
"""Returns a list of tuples of all networks.
|
||||||
|
|
||||||
Generates a list of networks available to the user (request). And returns
|
Generates a list of networks available to the user (request). And returns
|
||||||
@ -101,8 +102,12 @@ def network_field_data(request, include_empty_option=False, with_cidr=False):
|
|||||||
tenant_id = request.user.tenant_id
|
tenant_id = request.user.tenant_id
|
||||||
networks = []
|
networks = []
|
||||||
if api.base.is_service_enabled(request, 'network'):
|
if api.base.is_service_enabled(request, 'network'):
|
||||||
|
extra_params = {}
|
||||||
|
if for_launch:
|
||||||
|
extra_params['include_pre_auto_allocate'] = True
|
||||||
try:
|
try:
|
||||||
networks = api.neutron.network_list_for_tenant(request, tenant_id)
|
networks = api.neutron.network_list_for_tenant(
|
||||||
|
request, tenant_id, **extra_params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = _('Failed to get network list {0}').format(six.text_type(e))
|
msg = _('Failed to get network list {0}').format(six.text_type(e))
|
||||||
exceptions.handle(request, msg)
|
exceptions.handle(request, msg)
|
||||||
|
@ -738,7 +738,7 @@ class SetNetworkAction(workflows.Action):
|
|||||||
help_text = _("Select networks for your instance.")
|
help_text = _("Select networks for your instance.")
|
||||||
|
|
||||||
def populate_network_choices(self, request, context):
|
def populate_network_choices(self, request, context):
|
||||||
return instance_utils.network_field_data(request)
|
return instance_utils.network_field_data(request, for_launch=True)
|
||||||
|
|
||||||
|
|
||||||
class SetNetwork(workflows.Step):
|
class SetNetwork(workflows.Step):
|
||||||
|
@ -173,7 +173,8 @@ class NetworkTopologyTests(test.TestCase):
|
|||||||
self.mock_server_list.assert_called_once_with(
|
self.mock_server_list.assert_called_once_with(
|
||||||
test.IsHttpRequest())
|
test.IsHttpRequest())
|
||||||
self.mock_network_list_for_tenant.assert_called_once_with(
|
self.mock_network_list_for_tenant.assert_called_once_with(
|
||||||
test.IsHttpRequest(), self.tenant.id)
|
test.IsHttpRequest(), self.tenant.id,
|
||||||
|
include_pre_auto_allocate=False)
|
||||||
if router_enable:
|
if router_enable:
|
||||||
self.mock_router_list.assert_called_once_with(
|
self.mock_router_list.assert_called_once_with(
|
||||||
test.IsHttpRequest(), tenant_id=self.tenant.id)
|
test.IsHttpRequest(), tenant_id=self.tenant.id)
|
||||||
|
@ -264,9 +264,17 @@ class JSONView(View):
|
|||||||
# specify tenant_id for subnet. The subnet which belongs to the public
|
# specify tenant_id for subnet. The subnet which belongs to the public
|
||||||
# network is needed to draw subnet information on public network.
|
# network is needed to draw subnet information on public network.
|
||||||
try:
|
try:
|
||||||
|
# NOTE(amotoki):
|
||||||
|
# To support auto allocated network in the network topology view,
|
||||||
|
# we need to handle the auto allocated network which haven't been
|
||||||
|
# created yet. The current network topology logic cannot not handle
|
||||||
|
# fake network ID properly, so we temporarily exclude
|
||||||
|
# pre-auto-allocated-network from the network topology view.
|
||||||
|
# It would be nice if someone is interested in supporting it.
|
||||||
neutron_networks = api.neutron.network_list_for_tenant(
|
neutron_networks = api.neutron.network_list_for_tenant(
|
||||||
request,
|
request,
|
||||||
request.user.tenant_id)
|
request.user.tenant_id,
|
||||||
|
include_pre_auto_allocate=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
neutron_networks = []
|
neutron_networks = []
|
||||||
networks = []
|
networks = []
|
||||||
|
@ -15,6 +15,7 @@ import logging
|
|||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.template import defaultfilters as filters
|
from django.template import defaultfilters as filters
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.translation import pgettext_lazy
|
from django.utils.translation import pgettext_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
@ -53,6 +54,11 @@ class DeleteNetwork(policy.PolicyTargetMixin, tables.DeleteAction):
|
|||||||
|
|
||||||
policy_rules = (("network", "delete_network"),)
|
policy_rules = (("network", "delete_network"),)
|
||||||
|
|
||||||
|
def allowed(self, request, datum=None):
|
||||||
|
if datum and datum.id == api.neutron.AUTO_ALLOCATE_ID:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@actions.handle_exception_with_detail_message(
|
@actions.handle_exception_with_detail_message(
|
||||||
# normal_log_message
|
# normal_log_message
|
||||||
'Failed to delete network %(id)s: %(exc)s',
|
'Failed to delete network %(id)s: %(exc)s',
|
||||||
@ -104,6 +110,11 @@ class EditNetwork(policy.PolicyTargetMixin, tables.LinkAction):
|
|||||||
icon = "pencil"
|
icon = "pencil"
|
||||||
policy_rules = (("network", "update_network"),)
|
policy_rules = (("network", "update_network"),)
|
||||||
|
|
||||||
|
def allowed(self, request, datum=None):
|
||||||
|
if datum and datum.id == api.neutron.AUTO_ALLOCATE_ID:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class CreateSubnet(subnet_tables.SubnetPolicyTargetMixin, tables.LinkAction):
|
class CreateSubnet(subnet_tables.SubnetPolicyTargetMixin, tables.LinkAction):
|
||||||
name = "subnet"
|
name = "subnet"
|
||||||
@ -117,6 +128,8 @@ class CreateSubnet(subnet_tables.SubnetPolicyTargetMixin, tables.LinkAction):
|
|||||||
("network:project_id", "tenant_id"),)
|
("network:project_id", "tenant_id"),)
|
||||||
|
|
||||||
def allowed(self, request, datum=None):
|
def allowed(self, request, datum=None):
|
||||||
|
if datum and datum.id == api.neutron.AUTO_ALLOCATE_ID:
|
||||||
|
return False
|
||||||
usages = quotas.tenant_quota_usages(request, targets=('subnet', ))
|
usages = quotas.tenant_quota_usages(request, targets=('subnet', ))
|
||||||
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
|
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
|
||||||
# usages["subnet'] is empty
|
# usages["subnet'] is empty
|
||||||
@ -137,6 +150,12 @@ def get_subnets(network):
|
|||||||
return template.loader.render_to_string(template_name, context)
|
return template.loader.render_to_string(template_name, context)
|
||||||
|
|
||||||
|
|
||||||
|
def get_network_link(network):
|
||||||
|
if network.id == api.neutron.AUTO_ALLOCATE_ID:
|
||||||
|
return None
|
||||||
|
return reverse('horizon:project:networks:detail', args=[network.id])
|
||||||
|
|
||||||
|
|
||||||
DISPLAY_CHOICES = (
|
DISPLAY_CHOICES = (
|
||||||
("up", pgettext_lazy("Admin state of a Network", u"UP")),
|
("up", pgettext_lazy("Admin state of a Network", u"UP")),
|
||||||
("down", pgettext_lazy("Admin state of a Network", u"DOWN")),
|
("down", pgettext_lazy("Admin state of a Network", u"DOWN")),
|
||||||
@ -172,7 +191,7 @@ class ProjectNetworksFilterAction(tables.FilterAction):
|
|||||||
class NetworksTable(tables.DataTable):
|
class NetworksTable(tables.DataTable):
|
||||||
name = tables.WrappingColumn("name_or_id",
|
name = tables.WrappingColumn("name_or_id",
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
link='horizon:project:networks:detail')
|
link=get_network_link)
|
||||||
subnets = tables.Column(get_subnets,
|
subnets = tables.Column(get_subnets,
|
||||||
verbose_name=_("Subnets Associated"),)
|
verbose_name=_("Subnets Associated"),)
|
||||||
shared = tables.Column("shared", verbose_name=_("Shared"),
|
shared = tables.Column("shared", verbose_name=_("Shared"),
|
||||||
|
@ -52,7 +52,10 @@ class IndexView(tables.DataTableView):
|
|||||||
tenant_id = self.request.user.tenant_id
|
tenant_id = self.request.user.tenant_id
|
||||||
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
|
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
|
||||||
networks = api.neutron.network_list_for_tenant(
|
networks = api.neutron.network_list_for_tenant(
|
||||||
self.request, tenant_id, include_external=True, **search_opts)
|
self.request, tenant_id,
|
||||||
|
include_external=True,
|
||||||
|
include_pre_auto_allocate=True,
|
||||||
|
**search_opts)
|
||||||
except Exception:
|
except Exception:
|
||||||
networks = []
|
networks = []
|
||||||
msg = _('Network list can not be retrieved.')
|
msg = _('Network list can not be retrieved.')
|
||||||
|
@ -39,7 +39,8 @@ class NeutronNetworksTestCase(test.TestCase):
|
|||||||
exp_resp = [self._dictify_network(n) for n in self.networks.list()]
|
exp_resp = [self._dictify_network(n) for n in self.networks.list()]
|
||||||
self.assertItemsCollectionEqual(response, exp_resp)
|
self.assertItemsCollectionEqual(response, exp_resp)
|
||||||
mock_network_list_for_tenant.assert_called_once_with(
|
mock_network_list_for_tenant.assert_called_once_with(
|
||||||
request, request.user.tenant_id)
|
request, request.user.tenant_id,
|
||||||
|
include_pre_auto_allocate=True)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
self._test_create(
|
self._test_create(
|
||||||
|
@ -42,11 +42,13 @@ class NeutronApiTests(test.APIMockTestCase):
|
|||||||
neutronclient.list_networks.assert_called_once_with()
|
neutronclient.list_networks.assert_called_once_with()
|
||||||
neutronclient.list_subnets.assert_called_once_with()
|
neutronclient.list_subnets.assert_called_once_with()
|
||||||
|
|
||||||
|
@override_settings(OPENSTACK_NEUTRON_NETWORK={
|
||||||
|
'enable_auto_allocated_network': True})
|
||||||
@test.create_mocks({api.neutron: ('network_list',
|
@test.create_mocks({api.neutron: ('network_list',
|
||||||
'subnet_list')})
|
'subnet_list')})
|
||||||
def _test_network_list_for_tenant(
|
def _test_network_list_for_tenant(
|
||||||
self, include_external,
|
self, include_external,
|
||||||
filter_params, should_called):
|
filter_params, should_called, **extra_kwargs):
|
||||||
"""Convenient method to test network_list_for_tenant.
|
"""Convenient method to test network_list_for_tenant.
|
||||||
|
|
||||||
:param include_external: Passed to network_list_for_tenant.
|
:param include_external: Passed to network_list_for_tenant.
|
||||||
@ -58,55 +60,61 @@ class NeutronApiTests(test.APIMockTestCase):
|
|||||||
filter_params = filter_params or {}
|
filter_params = filter_params or {}
|
||||||
all_networks = self.networks.list()
|
all_networks = self.networks.list()
|
||||||
tenant_id = '1'
|
tenant_id = '1'
|
||||||
|
tenant_networks = [n for n in all_networks
|
||||||
|
if n['tenant_id'] == tenant_id]
|
||||||
|
shared_networks = [n for n in all_networks if n['shared']]
|
||||||
|
external_networks = [n for n in all_networks if n['router:external']]
|
||||||
|
|
||||||
return_values = []
|
return_values = []
|
||||||
expected_calls = []
|
expected_calls = []
|
||||||
if 'non_shared' in should_called:
|
if 'non_shared' in should_called:
|
||||||
params = filter_params.copy()
|
params = filter_params.copy()
|
||||||
params['shared'] = False
|
params['shared'] = False
|
||||||
return_values.append([
|
return_values.append(tenant_networks)
|
||||||
network for network in all_networks
|
|
||||||
if network['tenant_id'] == tenant_id
|
|
||||||
])
|
|
||||||
expected_calls.append(
|
expected_calls.append(
|
||||||
mock.call(test.IsHttpRequest(), tenant_id=tenant_id, **params),
|
mock.call(test.IsHttpRequest(), tenant_id=tenant_id, **params),
|
||||||
)
|
)
|
||||||
if 'shared' in should_called:
|
if 'shared' in should_called:
|
||||||
params = filter_params.copy()
|
params = filter_params.copy()
|
||||||
params['shared'] = True
|
params['shared'] = True
|
||||||
return_values.append([
|
return_values.append(shared_networks)
|
||||||
network for network in all_networks
|
|
||||||
if network.get('shared')
|
|
||||||
])
|
|
||||||
expected_calls.append(
|
expected_calls.append(
|
||||||
mock.call(test.IsHttpRequest(), **params),
|
mock.call(test.IsHttpRequest(), **params),
|
||||||
)
|
)
|
||||||
if 'external' in should_called:
|
if 'external' in should_called:
|
||||||
params = filter_params.copy()
|
params = filter_params.copy()
|
||||||
params['router:external'] = True
|
params['router:external'] = True
|
||||||
return_values.append([
|
return_values.append(external_networks)
|
||||||
network for network in all_networks
|
|
||||||
if network.get('router:external')
|
|
||||||
])
|
|
||||||
expected_calls.append(
|
expected_calls.append(
|
||||||
mock.call(test.IsHttpRequest(), **params),
|
mock.call(test.IsHttpRequest(), **params),
|
||||||
)
|
)
|
||||||
self.mock_network_list.side_effect = return_values
|
self.mock_network_list.side_effect = return_values
|
||||||
|
|
||||||
|
extra_kwargs.update(filter_params)
|
||||||
ret_val = api.neutron.network_list_for_tenant(
|
ret_val = api.neutron.network_list_for_tenant(
|
||||||
self.request, tenant_id,
|
self.request, tenant_id,
|
||||||
include_external=include_external,
|
include_external=include_external,
|
||||||
**filter_params)
|
**extra_kwargs)
|
||||||
|
|
||||||
expected = [n for n in all_networks
|
expected = []
|
||||||
if (('non_shared' in should_called and
|
if 'non_shared' in should_called:
|
||||||
n['tenant_id'] == tenant_id) or
|
expected += tenant_networks
|
||||||
('shared' in should_called and n['shared']) or
|
if 'shared' in should_called:
|
||||||
('external' in should_called and
|
expected += shared_networks
|
||||||
include_external and n['router:external']))]
|
if 'external' in should_called and include_external:
|
||||||
|
expected += external_networks
|
||||||
self.assertEqual(set(n.id for n in expected),
|
self.assertEqual(set(n.id for n in expected),
|
||||||
set(n.id for n in ret_val))
|
set(n.id for n in ret_val))
|
||||||
self.mock_network_list.assert_has_calls(expected_calls)
|
self.mock_network_list.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
# Ensure all three types of networks are not empty. This is required
|
||||||
|
# to check 'pre_auto_allocate' network is not included.
|
||||||
|
self.assertTrue(tenant_networks)
|
||||||
|
self.assertTrue(shared_networks)
|
||||||
|
self.assertTrue(external_networks)
|
||||||
|
self.assertNotIn(api.neutron.AUTO_ALLOCATE_ID,
|
||||||
|
[n.id for n in ret_val])
|
||||||
|
|
||||||
def test_network_list_for_tenant(self):
|
def test_network_list_for_tenant(self):
|
||||||
self._test_network_list_for_tenant(
|
self._test_network_list_for_tenant(
|
||||||
include_external=False, filter_params=None,
|
include_external=False, filter_params=None,
|
||||||
@ -164,6 +172,59 @@ class NeutronApiTests(test.APIMockTestCase):
|
|||||||
'foo': 'bar'},
|
'foo': 'bar'},
|
||||||
should_called=['non_shared', 'external'])
|
should_called=['non_shared', 'external'])
|
||||||
|
|
||||||
|
def test_network_list_for_tenant_no_pre_auto_allocate_if_net_exists(self):
|
||||||
|
self._test_network_list_for_tenant(
|
||||||
|
include_external=True, filter_params=None,
|
||||||
|
should_called=['non_shared', 'shared', 'external'],
|
||||||
|
include_pre_auto_allocate=True)
|
||||||
|
|
||||||
|
@override_settings(OPENSTACK_NEUTRON_NETWORK={
|
||||||
|
'enable_auto_allocated_network': True})
|
||||||
|
@test.create_mocks({api.neutron: ['network_list',
|
||||||
|
'is_extension_supported'],
|
||||||
|
api.nova: ['is_feature_available']})
|
||||||
|
def test_network_list_for_tenant_with_pre_auto_allocate(self):
|
||||||
|
tenant_id = '1'
|
||||||
|
self.mock_network_list.return_value = []
|
||||||
|
self.mock_is_extension_supported.return_value = True
|
||||||
|
self.mock_is_feature_available.return_value = True
|
||||||
|
|
||||||
|
ret_val = api.neutron.network_list_for_tenant(
|
||||||
|
self.request, tenant_id, include_pre_auto_allocate=True)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(ret_val))
|
||||||
|
self.assertIsInstance(ret_val[0], api.neutron.PreAutoAllocateNetwork)
|
||||||
|
self.assertEqual(api.neutron.AUTO_ALLOCATE_ID, ret_val[0].id)
|
||||||
|
|
||||||
|
self.assertEqual(2, self.mock_network_list.call_count)
|
||||||
|
self.mock_network_list.assert_has_calls([
|
||||||
|
mock.call(test.IsHttpRequest(), tenant_id=tenant_id,
|
||||||
|
shared=False),
|
||||||
|
mock.call(test.IsHttpRequest(), shared=True),
|
||||||
|
])
|
||||||
|
self.mock_is_extension_supported.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), 'auto-allocated-topology')
|
||||||
|
self.mock_is_feature_available.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(),
|
||||||
|
('instance_description', 'auto_allocated_network'))
|
||||||
|
|
||||||
|
@test.create_mocks({api.neutron: ['network_list']})
|
||||||
|
def test_network_list_for_tenant_no_pre_auto_allocate_if_disabled(self):
|
||||||
|
tenant_id = '1'
|
||||||
|
self.mock_network_list.return_value = []
|
||||||
|
|
||||||
|
ret_val = api.neutron.network_list_for_tenant(
|
||||||
|
self.request, tenant_id, include_pre_auto_allocate=True)
|
||||||
|
|
||||||
|
self.assertEqual(0, len(ret_val))
|
||||||
|
|
||||||
|
self.assertEqual(2, self.mock_network_list.call_count)
|
||||||
|
self.mock_network_list.assert_has_calls([
|
||||||
|
mock.call(test.IsHttpRequest(), tenant_id=tenant_id,
|
||||||
|
shared=False),
|
||||||
|
mock.call(test.IsHttpRequest(), shared=True),
|
||||||
|
])
|
||||||
|
|
||||||
@mock.patch.object(api.neutron, 'neutronclient')
|
@mock.patch.object(api.neutron, 'neutronclient')
|
||||||
def test_network_get(self, mock_neutronclient):
|
def test_network_get(self, mock_neutronclient):
|
||||||
network = {'network': self.api_networks.first()}
|
network = {'network': self.api_networks.first()}
|
||||||
|
@ -763,3 +763,70 @@ class ComputeApiTests(test.APIMockTestCase):
|
|||||||
['bob', 'john', 'sam'])
|
['bob', 'john', 'sam'])
|
||||||
novaclient.availability_zones.list.assert_called_once_with(
|
novaclient.availability_zones.list.assert_called_once_with(
|
||||||
detailed=detailed)
|
detailed=detailed)
|
||||||
|
|
||||||
|
@test.create_mocks({api.nova: ['get_microversion',
|
||||||
|
'novaclient']})
|
||||||
|
def _test_server_create(self, extra_kwargs=None, expected_kwargs=None):
|
||||||
|
extra_kwargs = extra_kwargs or {}
|
||||||
|
expected_kwargs = expected_kwargs or {}
|
||||||
|
expected_kwargs.setdefault('nics', None)
|
||||||
|
|
||||||
|
self.mock_get_microversion.return_value = mock.sentinel.microversion
|
||||||
|
novaclient = mock.Mock()
|
||||||
|
self.mock_novaclient.return_value = novaclient
|
||||||
|
|
||||||
|
ret = api.nova.server_create(
|
||||||
|
mock.sentinel.request,
|
||||||
|
'vm1', 'image1', 'flavor1', 'key1', 'userdata1', ['sg1'],
|
||||||
|
**extra_kwargs)
|
||||||
|
|
||||||
|
self.assertIsInstance(ret, api.nova.Server)
|
||||||
|
self.mock_get_microversion.assert_called_once_with(
|
||||||
|
mock.sentinel.request, ('instance_description',
|
||||||
|
'auto_allocated_network'))
|
||||||
|
self.mock_novaclient.assert_called_once_with(
|
||||||
|
mock.sentinel.request, version=mock.sentinel.microversion)
|
||||||
|
novaclient.servers.create.assert_called_once_with(
|
||||||
|
'vm1', 'image1', 'flavor1', userdata='userdata1',
|
||||||
|
security_groups=['sg1'], key_name='key1',
|
||||||
|
block_device_mapping=None, block_device_mapping_v2=None,
|
||||||
|
availability_zone=None, min_count=1, admin_pass=None,
|
||||||
|
disk_config=None, config_drive=None, meta=None,
|
||||||
|
scheduler_hints=None, **expected_kwargs)
|
||||||
|
|
||||||
|
def test_server_create(self):
|
||||||
|
self._test_server_create()
|
||||||
|
|
||||||
|
def test_server_create_with_description(self):
|
||||||
|
kwargs = {'description': 'desc1'}
|
||||||
|
self._test_server_create(extra_kwargs=kwargs, expected_kwargs=kwargs)
|
||||||
|
|
||||||
|
def test_server_create_with_normal_nics(self):
|
||||||
|
kwargs = {
|
||||||
|
'nics': [
|
||||||
|
{'net-id': 'net1'},
|
||||||
|
{'port-id': 'port1'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self._test_server_create(extra_kwargs=kwargs, expected_kwargs=kwargs)
|
||||||
|
|
||||||
|
def test_server_create_with_auto_nic(self):
|
||||||
|
kwargs = {
|
||||||
|
'nics': [
|
||||||
|
{'net-id': api.neutron.AUTO_ALLOCATE_ID},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self._test_server_create(extra_kwargs=kwargs,
|
||||||
|
expected_kwargs={'nics': 'auto'})
|
||||||
|
|
||||||
|
def test_server_create_with_auto_nic_with_others(self):
|
||||||
|
# This actually never happens. Just for checking the logic.
|
||||||
|
kwargs = {
|
||||||
|
'nics': [
|
||||||
|
{'net-id': 'net1'},
|
||||||
|
{'net-id': api.neutron.AUTO_ALLOCATE_ID},
|
||||||
|
{'port-id': 'port1'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self._test_server_create(extra_kwargs=kwargs,
|
||||||
|
expected_kwargs={'nics': 'auto'})
|
||||||
|
15
releasenotes/notes/get-me-a-network-c979c244fa038258.yaml
Normal file
15
releasenotes/notes/get-me-a-network-c979c244fa038258.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
[:bug:`1690433`] "Get me a network" feature provided by nova and neutron
|
||||||
|
is now exposed in the launch server form.
|
||||||
|
This feature will sets up a neutron network topology for a project
|
||||||
|
if there is no network in the project. It simplifies the workflow when
|
||||||
|
launching a server.
|
||||||
|
In the horizon support, when there is no network which can be used
|
||||||
|
for a server, a dummy network named 'auto_allocated_network' is shown
|
||||||
|
in the network choices.
|
||||||
|
The feature is disabled by default because it requires preparations
|
||||||
|
in your neutron deployment.
|
||||||
|
To enable it, set ``enable_auto_allocated_network`` in
|
||||||
|
``OPENSTACK_NEUTRON_NETWORK`` to ``True``.
|
Loading…
Reference in New Issue
Block a user