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,