Merge "Support "Get me a network" in launch instance"
This commit is contained in:
commit
4fff2894ad
@ -1552,6 +1552,7 @@ Default:
|
||||
|
||||
{
|
||||
'default_dns_nameservers': [],
|
||||
'enable_auto_allocated_network': False,
|
||||
'enable_distributed_router': False,
|
||||
'enable_fip_topology_check': True,
|
||||
'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"]``
|
||||
|
||||
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
|
||||
#########################
|
||||
|
||||
|
@ -33,7 +33,8 @@ MICROVERSION_FEATURES = {
|
||||
"remote_console_mks": ["2.8", "2.53"],
|
||||
"servergroup_soft_policies": ["2.15", "2.60"],
|
||||
"servergroup_user_info": ["2.13", "2.60"],
|
||||
"multiattach": ["2.60"]
|
||||
"multiattach": ["2.60"],
|
||||
"auto_allocated_network": ["2.37", "2.42"],
|
||||
},
|
||||
"cinder": {
|
||||
"consistency_groups": ["2.0", "3.10"],
|
||||
|
@ -117,6 +117,36 @@ class Subnet(NeutronAPIDictWrapper):
|
||||
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):
|
||||
"""Wrapper for neutron trunks."""
|
||||
|
||||
@ -989,8 +1019,35 @@ def network_list(request, **params):
|
||||
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
|
||||
def network_list_for_tenant(request, tenant_id, include_external=False,
|
||||
include_pre_auto_allocate=False,
|
||||
**params):
|
||||
"""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
|
||||
# both owner networks and public networks in a single API call.
|
||||
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)
|
||||
if params['router:external'] and include_external:
|
||||
if shared is not None:
|
||||
@ -1754,8 +1817,8 @@ def is_enabled_by_config(name, default=True):
|
||||
|
||||
|
||||
@memoized
|
||||
def is_service_enabled(request, config_name, ext_name):
|
||||
return (is_enabled_by_config(config_name) and
|
||||
def is_service_enabled(request, config_name, ext_name, default=True):
|
||||
return (is_enabled_by_config(config_name, default) and
|
||||
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,
|
||||
disk_config=None, config_drive=None, meta=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 = {}
|
||||
if description is not None:
|
||||
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,
|
||||
security_groups=security_groups,
|
||||
key_name=key_name, block_device_mapping=block_device_mapping,
|
||||
|
@ -39,7 +39,13 @@ class Networks(generic.View):
|
||||
a network.
|
||||
"""
|
||||
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]}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
|
@ -86,7 +86,8 @@ def server_group_list(request):
|
||||
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.
|
||||
|
||||
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
|
||||
networks = []
|
||||
if api.base.is_service_enabled(request, 'network'):
|
||||
extra_params = {}
|
||||
if for_launch:
|
||||
extra_params['include_pre_auto_allocate'] = True
|
||||
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:
|
||||
msg = _('Failed to get network list {0}').format(six.text_type(e))
|
||||
exceptions.handle(request, msg)
|
||||
|
@ -738,7 +738,7 @@ class SetNetworkAction(workflows.Action):
|
||||
help_text = _("Select networks for your instance.")
|
||||
|
||||
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):
|
||||
|
@ -173,7 +173,8 @@ class NetworkTopologyTests(test.TestCase):
|
||||
self.mock_server_list.assert_called_once_with(
|
||||
test.IsHttpRequest())
|
||||
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:
|
||||
self.mock_router_list.assert_called_once_with(
|
||||
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
|
||||
# network is needed to draw subnet information on public network.
|
||||
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(
|
||||
request,
|
||||
request.user.tenant_id)
|
||||
request.user.tenant_id,
|
||||
include_pre_auto_allocate=False)
|
||||
except Exception:
|
||||
neutron_networks = []
|
||||
networks = []
|
||||
|
@ -15,6 +15,7 @@ import logging
|
||||
|
||||
from django import template
|
||||
from django.template import defaultfilters as filters
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
@ -53,6 +54,11 @@ class DeleteNetwork(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
|
||||
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(
|
||||
# normal_log_message
|
||||
'Failed to delete network %(id)s: %(exc)s',
|
||||
@ -104,6 +110,11 @@ class EditNetwork(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
icon = "pencil"
|
||||
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):
|
||||
name = "subnet"
|
||||
@ -117,6 +128,8 @@ class CreateSubnet(subnet_tables.SubnetPolicyTargetMixin, tables.LinkAction):
|
||||
("network:project_id", "tenant_id"),)
|
||||
|
||||
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', ))
|
||||
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
|
||||
# usages["subnet'] is empty
|
||||
@ -137,6 +150,12 @@ def get_subnets(network):
|
||||
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 = (
|
||||
("up", pgettext_lazy("Admin state of a Network", u"UP")),
|
||||
("down", pgettext_lazy("Admin state of a Network", u"DOWN")),
|
||||
@ -172,7 +191,7 @@ class ProjectNetworksFilterAction(tables.FilterAction):
|
||||
class NetworksTable(tables.DataTable):
|
||||
name = tables.WrappingColumn("name_or_id",
|
||||
verbose_name=_("Name"),
|
||||
link='horizon:project:networks:detail')
|
||||
link=get_network_link)
|
||||
subnets = tables.Column(get_subnets,
|
||||
verbose_name=_("Subnets Associated"),)
|
||||
shared = tables.Column("shared", verbose_name=_("Shared"),
|
||||
|
@ -52,7 +52,10 @@ class IndexView(tables.DataTableView):
|
||||
tenant_id = self.request.user.tenant_id
|
||||
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
|
||||
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:
|
||||
networks = []
|
||||
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()]
|
||||
self.assertItemsCollectionEqual(response, exp_resp)
|
||||
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):
|
||||
self._test_create(
|
||||
|
@ -42,11 +42,13 @@ class NeutronApiTests(test.APIMockTestCase):
|
||||
neutronclient.list_networks.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',
|
||||
'subnet_list')})
|
||||
def _test_network_list_for_tenant(
|
||||
self, include_external,
|
||||
filter_params, should_called):
|
||||
filter_params, should_called, **extra_kwargs):
|
||||
"""Convenient method to test 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 {}
|
||||
all_networks = self.networks.list()
|
||||
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 = []
|
||||
expected_calls = []
|
||||
if 'non_shared' in should_called:
|
||||
params = filter_params.copy()
|
||||
params['shared'] = False
|
||||
return_values.append([
|
||||
network for network in all_networks
|
||||
if network['tenant_id'] == tenant_id
|
||||
])
|
||||
return_values.append(tenant_networks)
|
||||
expected_calls.append(
|
||||
mock.call(test.IsHttpRequest(), tenant_id=tenant_id, **params),
|
||||
)
|
||||
if 'shared' in should_called:
|
||||
params = filter_params.copy()
|
||||
params['shared'] = True
|
||||
return_values.append([
|
||||
network for network in all_networks
|
||||
if network.get('shared')
|
||||
])
|
||||
return_values.append(shared_networks)
|
||||
expected_calls.append(
|
||||
mock.call(test.IsHttpRequest(), **params),
|
||||
)
|
||||
if 'external' in should_called:
|
||||
params = filter_params.copy()
|
||||
params['router:external'] = True
|
||||
return_values.append([
|
||||
network for network in all_networks
|
||||
if network.get('router:external')
|
||||
])
|
||||
return_values.append(external_networks)
|
||||
expected_calls.append(
|
||||
mock.call(test.IsHttpRequest(), **params),
|
||||
)
|
||||
self.mock_network_list.side_effect = return_values
|
||||
|
||||
extra_kwargs.update(filter_params)
|
||||
ret_val = api.neutron.network_list_for_tenant(
|
||||
self.request, tenant_id,
|
||||
include_external=include_external,
|
||||
**filter_params)
|
||||
**extra_kwargs)
|
||||
|
||||
expected = [n for n in all_networks
|
||||
if (('non_shared' in should_called and
|
||||
n['tenant_id'] == tenant_id) or
|
||||
('shared' in should_called and n['shared']) or
|
||||
('external' in should_called and
|
||||
include_external and n['router:external']))]
|
||||
expected = []
|
||||
if 'non_shared' in should_called:
|
||||
expected += tenant_networks
|
||||
if 'shared' in should_called:
|
||||
expected += shared_networks
|
||||
if 'external' in should_called and include_external:
|
||||
expected += external_networks
|
||||
self.assertEqual(set(n.id for n in expected),
|
||||
set(n.id for n in ret_val))
|
||||
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):
|
||||
self._test_network_list_for_tenant(
|
||||
include_external=False, filter_params=None,
|
||||
@ -164,6 +172,59 @@ class NeutronApiTests(test.APIMockTestCase):
|
||||
'foo': 'bar'},
|
||||
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')
|
||||
def test_network_get(self, mock_neutronclient):
|
||||
network = {'network': self.api_networks.first()}
|
||||
|
@ -763,3 +763,70 @@ class ComputeApiTests(test.APIMockTestCase):
|
||||
['bob', 'john', 'sam'])
|
||||
novaclient.availability_zones.list.assert_called_once_with(
|
||||
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