From d0c8907104633d318225b34e05a0946e632822a8 Mon Sep 17 00:00:00 2001 From: Steven Webster Date: Tue, 22 Nov 2022 14:48:35 +0000 Subject: [PATCH] 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 Change-Id: Icf4c7c97ed69c74e6827c63614cb44abca28e38a --- .../api/controllers/v1/interface_network.py | 20 ++++++----- .../sysinv/api/controllers/v1/network.py | 9 ++++- sysinv/sysinv/sysinv/sysinv/common/utils.py | 18 +++++++--- .../sysinv/sysinv/sysinv/puppet/barbican.py | 10 ++++-- sysinv/sysinv/sysinv/sysinv/puppet/base.py | 15 +++++++- .../sysinv/sysinv/sysinv/puppet/dcdbsync.py | 13 +++++-- sysinv/sysinv/sysinv/sysinv/puppet/fm.py | 10 ++++-- .../sysinv/sysinv/sysinv/puppet/inventory.py | 13 +++++-- .../sysinv/sysinv/sysinv/puppet/keystone.py | 10 ++++-- sysinv/sysinv/sysinv/sysinv/puppet/nfv.py | 10 ++++-- .../sysinv/sysinv/sysinv/puppet/patching.py | 10 ++++-- .../sysinv/sysinv/sysinv/puppet/platform.py | 12 ++++++- sysinv/sysinv/sysinv/sysinv/puppet/sssd.py | 14 +++++--- .../tests/api/test_interface_network.py | 28 +++++++++++++++ .../sysinv/sysinv/tests/api/test_network.py | 36 ++++++++++++++++++- 15 files changed, 191 insertions(+), 37 deletions(-) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_network.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_network.py index aaec2f3dd8..dac744c30c 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_network.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_network.py @@ -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' diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/network.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/network.py index 0758931ed3..a3b5df7ec6 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/network.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/network.py @@ -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 diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index 7696714a1a..33b2fb67b3 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -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( diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/barbican.py b/sysinv/sysinv/sysinv/sysinv/puppet/barbican.py index 0006bc4875..c83487680a 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/barbican.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/barbican.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/base.py b/sysinv/sysinv/sysinv/sysinv/puppet/base.py index 897bccfe2b..d5c938a943 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/base.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/base.py @@ -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 diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/dcdbsync.py b/sysinv/sysinv/sysinv/sysinv/puppet/dcdbsync.py index 5212584e8d..6dc60c92bf 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/dcdbsync.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/dcdbsync.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/fm.py b/sysinv/sysinv/sysinv/sysinv/puppet/fm.py index c4a9f12508..bf9cfa403b 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/fm.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/fm.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/inventory.py b/sysinv/sysinv/sysinv/sysinv/puppet/inventory.py index f12dab6c9e..7cb92d8a27 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/inventory.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/inventory.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/keystone.py b/sysinv/sysinv/sysinv/sysinv/puppet/keystone.py index 61927b8050..381f9e8909 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/keystone.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/keystone.py @@ -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(): diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py b/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py index 7732655986..0ffd656f7e 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/patching.py b/sysinv/sysinv/sysinv/sysinv/puppet/patching.py index 2a1ef799c0..421dcb734a 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/patching.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/patching.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py index 6fe2cc01af..821590ece6 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py @@ -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, } diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/sssd.py b/sysinv/sysinv/sysinv/sysinv/puppet/sssd.py index a263fee73a..b52d9ad593 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/sssd.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/sssd.py @@ -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: diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface_network.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface_network.py index 6f6c1c1975..14bc73c368 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface_network.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface_network.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_network.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_network.py index 9dd3dfa58d..411ac8edf6 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_network.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_network.py @@ -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,