Initial integration of DC with admin network
Because the management network and its parameters are embedded in many parts of the system, having a separate admin network makes it much easier to change the parameters of this network (subnet, gateway, etc) after a subcloud has been provisioned. The admin network will take precedence over the existing management network for communication between the subcloud and system controller if it is defined. The management network will still exist on the subcloud, but will be a private network. This commit contains logic to choose the most appropriate keystone auth url and admin endpoint required for subcloud administration depending on whether the admin network is present or not. Note: Corresponding puppet review: https://review.opendev.org/c/starlingx/stx-puppet/+/865288 Test Plan: - Bootstrap and install DC subcloud with admin network defined. PASS: Ensure the openstack admin endpoints on both the subcloud and system controller for the affected services use the admin subnet of the subcloud PASS: Ensure the subcloud can become online and in-sync using the admin network. Regression: - AIO-SX: On a non-DC system, ensure the openstack endpoints for the various services are not impacted by the change. - Bootstrap and install DC subcloud with no admin network defined. PASS: Ensure the openstack admin endpoints on both the subcloud and system controller for the affected services use the management subnet of the subcloud (no impact) PASS: Ensure the subcloud can become online and in-sync with the management network (no impact). Depends-On: https://review.opendev.org/c/starlingx/config/+/863033 Story: 2010319 Task: 46910 Signed-off-by: Steven Webster <steven.webster@windriver.com> Change-Id: Icf4c7c97ed69c74e6827c63614cb44abca28e38a
This commit is contained in:
parent
1c056d966d
commit
d0c8907104
|
@ -16,7 +16,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -194,7 +194,10 @@ class InterfaceNetworkController(rest.RestController):
|
|||
ethernet_port_mac = tmp_interface['imac']
|
||||
_update_host_mgmt_mac(host, ethernet_port_mac)
|
||||
cutils.perform_distributed_cloud_config(pecan.request.dbapi,
|
||||
interface_id)
|
||||
interface_id)
|
||||
elif network_type == constants.NETWORK_TYPE_ADMIN:
|
||||
cutils.perform_distributed_cloud_config(pecan.request.dbapi,
|
||||
interface_id)
|
||||
elif network_type == constants.NETWORK_TYPE_OAM:
|
||||
pecan.request.rpcapi.initialize_oam_config(pecan.request.context, host)
|
||||
|
||||
|
@ -328,16 +331,17 @@ class InterfaceNetworkController(rest.RestController):
|
|||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
def _check_network_type_and_interface_type(self, interface, network_type):
|
||||
# Make sure network type 'mgmt', with if type 'ae',
|
||||
# Make sure network type 'mgmt' or 'admin', with if type 'ae',
|
||||
# can only be in ae mode 'active_standby' or '802.3ad'
|
||||
if (network_type == constants.NETWORK_TYPE_MGMT):
|
||||
valid_mgmt_aemode = [constants.AE_MODE_LACP,
|
||||
constants.AE_MODE_ACTIVE_STANDBY]
|
||||
if (network_type in [constants.NETWORK_TYPE_MGMT,
|
||||
constants.NETWORK_TYPE_ADMIN]):
|
||||
valid_aemode = [constants.AE_MODE_LACP,
|
||||
constants.AE_MODE_ACTIVE_STANDBY]
|
||||
if (interface.iftype == constants.INTERFACE_TYPE_AE and
|
||||
interface.aemode not in valid_mgmt_aemode):
|
||||
interface.aemode not in valid_aemode):
|
||||
msg = _("Device interface with network type {}, and interface "
|
||||
"type 'aggregated ethernet' must be in mode {}").format(
|
||||
network_type, ', '.join(valid_mgmt_aemode))
|
||||
network_type, ', '.join(valid_aemode))
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
# Make sure network type 'oam' or 'cluster-host', with if type 'ae',
|
||||
# can only be in ae mode 'active_standby' or 'balanced' or '802.3ad'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2015-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2015-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -165,6 +165,13 @@ class NetworkController(rest.RestController):
|
|||
networks = pecan.request.dbapi.networks_get_by_type(networktype)
|
||||
if networks:
|
||||
raise exception.NetworkAlreadyExists(type=networktype)
|
||||
if (networktype == constants.NETWORK_TYPE_ADMIN and
|
||||
utils.get_distributed_cloud_role() !=
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
msg = _("Network of type {} restricted to distributed cloud "
|
||||
"role of {}."
|
||||
.format(networktype, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD))
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
def _check_network_pool(self, pool):
|
||||
# ensure address pool exists and is not already inuse
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2023 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
|
||||
|
@ -1801,10 +1801,18 @@ def perform_distributed_cloud_config(dbapi, mgmt_iface_id):
|
|||
# for local & reachable gateway etc, as config_subcloud
|
||||
# will have already done these checks before allowing
|
||||
# the system controller gateway into the database.
|
||||
|
||||
cc_gtwy_addr_name = '%s-%s' % (
|
||||
constants.SYSTEM_CONTROLLER_GATEWAY_IP_NAME,
|
||||
constants.NETWORK_TYPE_MGMT)
|
||||
try:
|
||||
# Prefer admin network
|
||||
dbapi.network_get_by_type(
|
||||
constants.NETWORK_TYPE_ADMIN)
|
||||
cc_gtwy_addr_name = '%s-%s' % (
|
||||
constants.SYSTEM_CONTROLLER_GATEWAY_IP_NAME,
|
||||
constants.NETWORK_TYPE_ADMIN)
|
||||
except exception.NetworkTypeNotFound:
|
||||
cc_gtwy_addr_name = '%s-%s' % (
|
||||
constants.SYSTEM_CONTROLLER_GATEWAY_IP_NAME,
|
||||
constants.NETWORK_TYPE_MGMT)
|
||||
pass
|
||||
|
||||
try:
|
||||
cc_gtwy_addr = dbapi.address_get_by_name(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -89,7 +89,13 @@ class BarbicanPuppet(openstack.OpenstackBasePuppet):
|
|||
return self._format_private_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address())
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_region_name(self):
|
||||
return self._get_service_region_name(self.SERVICE_NAME)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2017-2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -196,6 +196,11 @@ class BasePuppet(object):
|
|||
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_OAM)
|
||||
return address.address
|
||||
|
||||
def _get_admin_address(self):
|
||||
address = self._get_address_by_name(
|
||||
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_ADMIN)
|
||||
return address.address
|
||||
|
||||
def _get_cluster_host_address(self):
|
||||
address = self._get_address_by_name(
|
||||
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_CLUSTER_HOST)
|
||||
|
@ -207,6 +212,14 @@ class BasePuppet(object):
|
|||
subnet = address.address + '/' + address.prefix
|
||||
return subnet
|
||||
|
||||
def _get_subcloud_endpoint_address(self):
|
||||
try:
|
||||
address = self._format_url_address(self._get_admin_address())
|
||||
except exception.AddressNotFoundByName:
|
||||
address = self._format_url_address(self._get_management_address())
|
||||
pass
|
||||
return address
|
||||
|
||||
def _get_host_cpu_list(self, host, function=None, threads=False):
|
||||
"""
|
||||
Retreive a list of CPUs for the host, filtered by function and thread
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2019-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2019-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -153,8 +153,15 @@ class DCDBsyncPuppet(openstack.OpenstackBasePuppet):
|
|||
path=self.SERVICE_PATH)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT,
|
||||
path=self.SERVICE_PATH)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address(),
|
||||
path=self.SERVICE_PATH)
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT,
|
||||
path=self.SERVICE_PATH)
|
||||
|
||||
def get_region_name(self):
|
||||
return self._get_service_region_name(self.SERVICE_NAME)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2018-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -114,7 +114,13 @@ class FmPuppet(openstack.OpenstackBasePuppet):
|
|||
return self._format_private_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address())
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_region_name(self):
|
||||
return self._get_service_region_name(self.SERVICE_NAME)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -120,8 +120,15 @@ class SystemInventoryPuppet(openstack.OpenstackBasePuppet):
|
|||
path=self.SERVICE_PATH)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT,
|
||||
path=self.SERVICE_PATH)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address(),
|
||||
path=self.SERVICE_PATH)
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT,
|
||||
path=self.SERVICE_PATH)
|
||||
|
||||
def get_region_name(self):
|
||||
return self._get_service_region_name(self.SERVICE_NAME)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -258,7 +258,13 @@ class KeystonePuppet(openstack.OpenstackBasePuppet):
|
|||
self.SERVICE_TYPE in self._get_shared_services()):
|
||||
return self._get_admin_url_from_service_config(self.SERVICE_NAME)
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address())
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_auth_address(self):
|
||||
if self._region_config():
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2017-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -323,4 +323,10 @@ class NfvPuppet(openstack.OpenstackBasePuppet):
|
|||
return self._format_private_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address())
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -88,7 +88,13 @@ class PatchingPuppet(openstack.OpenstackBasePuppet):
|
|||
return self._format_private_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_admin_url(self):
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
if (self._distributed_cloud_role() ==
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD):
|
||||
return self._format_admin_endpoint(
|
||||
self.SERVICE_PORT,
|
||||
address=self._get_subcloud_endpoint_address())
|
||||
else:
|
||||
return self._format_admin_endpoint(self.SERVICE_PORT)
|
||||
|
||||
def get_region_name(self):
|
||||
return self._get_service_region_name(self.SERVICE_NAME)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2017-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -208,6 +208,14 @@ class PlatformPuppet(base.BasePuppet):
|
|||
constants.CONTROLLER, constants.NETWORK_TYPE_OAM)
|
||||
private_address = self._get_address_by_name(
|
||||
constants.CONTROLLER, constants.NETWORK_TYPE_MGMT)
|
||||
try:
|
||||
private_dc_address = self._get_address_by_name(
|
||||
constants.CONTROLLER, constants.NETWORK_TYPE_ADMIN)
|
||||
except exception.AddressNotFoundByName:
|
||||
private_dc_address = self._get_address_by_name(
|
||||
constants.CONTROLLER, constants.NETWORK_TYPE_MGMT)
|
||||
pass
|
||||
|
||||
public_address_url = self._format_url_address(public_address.address)
|
||||
https_enabled = self._https_enabled()
|
||||
|
||||
|
@ -218,6 +226,8 @@ class PlatformPuppet(base.BasePuppet):
|
|||
public_address_url,
|
||||
'platform::haproxy::params::private_ip_address':
|
||||
private_address.address,
|
||||
'platform::haproxy::params::private_dc_ip_address':
|
||||
private_dc_address.address,
|
||||
'platform::haproxy::params::enable_https':
|
||||
https_enabled,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2022-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -152,12 +152,18 @@ class SssdPuppet(base.BasePuppet):
|
|||
% parameter_name)
|
||||
return None
|
||||
|
||||
def _get_network_type(self):
|
||||
return self.dbapi.network_get_by_type(constants.NETWORK_TYPE_MGMT)
|
||||
def _get_network_type(self, network_type):
|
||||
return self.dbapi.network_get_by_type(network_type)
|
||||
|
||||
def _is_host_address_ipv6(self):
|
||||
addr_pool = self.dbapi.address_pool_get(self._get_network_type().pool_uuid)
|
||||
try:
|
||||
# Subclouds may be using the optional admin network
|
||||
network = self._get_network_type(constants.NETWORK_TYPE_ADMIN)
|
||||
except exception.NetworkTypeNotFound:
|
||||
network = self._get_network_type(constants.NETWORK_TYPE_MGMT)
|
||||
pass
|
||||
|
||||
addr_pool = self.dbapi.address_pool_get(network.pool_uuid)
|
||||
if addr_pool.family == constants.IPV6_FAMILY:
|
||||
return True
|
||||
else:
|
||||
|
|
|
@ -107,6 +107,19 @@ class InterfaceNetworkTestCase(base.FunctionalTest):
|
|||
prefix=24,
|
||||
name='controller-0-pxeboot',
|
||||
address_pool_id=self.address_pool_pxeboot.id)
|
||||
self.address_pool_admin = dbutils.create_test_address_pool(
|
||||
id=5,
|
||||
network='192.168.208.0',
|
||||
name='admin',
|
||||
ranges=[['192.168.208.2', '192.168.208.254']],
|
||||
prefix=24)
|
||||
self.admin_network = dbutils.create_test_network(
|
||||
id=5,
|
||||
name='admin',
|
||||
type=constants.NETWORK_TYPE_ADMIN,
|
||||
link_capacity=10000,
|
||||
vlan_id=8,
|
||||
address_pool_id=self.address_pool_admin.id)
|
||||
|
||||
def _post_and_check(self, ndict, expect_errors=False):
|
||||
response = self.post_json('%s' % self._get_path(), ndict,
|
||||
|
@ -376,3 +389,18 @@ class InterfaceNetworkCreateTestCase(InterfaceNetworkTestCase):
|
|||
interface_uuid=controller_interface.uuid,
|
||||
network_uuid=self.mgmt_network.uuid)
|
||||
self._post_and_check(controller_interface_network, expect_errors=True)
|
||||
|
||||
# Expected error: Device interface with network type ___, and interface type
|
||||
# 'aggregated ethernet' must be in mode '802.3ad'
|
||||
def test_aemode_invalid_admin(self):
|
||||
controller_interface = dbutils.create_test_interface(
|
||||
ifname='name',
|
||||
forihostid=self.controller.id,
|
||||
ifclass=constants.INTERFACE_CLASS_PLATFORM,
|
||||
iftype=constants.INTERFACE_TYPE_AE,
|
||||
aemode='balanced',
|
||||
txhashpolicy='layer2')
|
||||
controller_interface_network = dbutils.post_get_test_interface_network(
|
||||
interface_uuid=controller_interface.uuid,
|
||||
network_uuid=self.admin_network.uuid)
|
||||
self._post_and_check(controller_interface_network, expect_errors=True)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2020-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2020-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -183,6 +183,22 @@ class TestPostMixin(NetworkTestCase):
|
|||
self.assertIn("Network of type %s already exists." % network_type,
|
||||
response.json['error_message'])
|
||||
|
||||
def _test_create_network_fail_subcloud_only(self, name, network_type, subnet):
|
||||
address_pool_id = self._create_test_address_pool(name, subnet)['uuid']
|
||||
|
||||
ndict = self.get_post_object(network_type, address_pool_id)
|
||||
response = self.post_json(self.API_PREFIX,
|
||||
ndict,
|
||||
headers=self.API_HEADERS,
|
||||
expect_errors=True)
|
||||
|
||||
# Check HTTP response is failed
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
|
||||
self.assertIn("Network of type %s restricted to distributed cloud "
|
||||
"role of subcloud." % network_type,
|
||||
response.json['error_message'])
|
||||
|
||||
def test_create_success_system_controller_oam(self):
|
||||
self._create_test_host(constants.CONTROLLER)
|
||||
m = mock.Mock()
|
||||
|
@ -272,11 +288,23 @@ class TestPostMixin(NetworkTestCase):
|
|||
self.storage_subnet)
|
||||
|
||||
def test_create_success_admin(self):
|
||||
p = mock.patch('sysinv.api.controllers.v1.utils.get_distributed_cloud_role')
|
||||
self.mock_utils_get_distributed_cloud_role = p.start()
|
||||
self.mock_utils_get_distributed_cloud_role.return_value = \
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
self._test_create_network_success(
|
||||
'admin',
|
||||
constants.NETWORK_TYPE_ADMIN,
|
||||
self.admin_subnet)
|
||||
|
||||
def test_create_failure_admin_non_subcloud(self):
|
||||
self._test_create_network_fail_subcloud_only(
|
||||
'admin',
|
||||
constants.NETWORK_TYPE_ADMIN,
|
||||
self.admin_subnet)
|
||||
|
||||
def test_create_fail_duplicate_pxeboot(self):
|
||||
self._test_create_network_fail_duplicate(
|
||||
'pxeboot',
|
||||
|
@ -320,6 +348,12 @@ class TestPostMixin(NetworkTestCase):
|
|||
self.storage_subnet)
|
||||
|
||||
def test_create_fail_duplicate_admin(self):
|
||||
p = mock.patch('sysinv.api.controllers.v1.utils.get_distributed_cloud_role')
|
||||
self.mock_utils_get_distributed_cloud_role = p.start()
|
||||
self.mock_utils_get_distributed_cloud_role.return_value = \
|
||||
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
self._test_create_network_fail_duplicate(
|
||||
'admin',
|
||||
constants.NETWORK_TYPE_ADMIN,
|
||||
|
|
Loading…
Reference in New Issue