From fe3ef48558f4ba58981572a73b876da06c89220d Mon Sep 17 00:00:00 2001 From: Jessica Castelino Date: Mon, 2 Mar 2020 16:20:27 -0500 Subject: [PATCH] Show OAM Floating IP for Subcloud Extends the dcmanager subcloud show command to display the floating OAM IP of the subcloud from the system controller. Change-Id: Ib421ea65660ed77a241798831c83cbaf1710f9e8 Story: 2007267 Task: 38896 Signed-off-by: Jessica Castelino --- api-ref/source/api-ref-dcmanager-v1.rst | 153 +++++++++++------- .../dcmanager/api/controllers/v1/subclouds.py | 44 ++++- .../dcmanager/common/exceptions.py | 4 + .../dcmanager/drivers/openstack/sysinv_v1.py | 16 +- .../dcmanager/manager/subcloud_install.py | 4 +- .../dcmanager/manager/subcloud_manager.py | 6 +- .../unit/api/v1/controllers/test_subclouds.py | 65 +++++++- 7 files changed, 212 insertions(+), 80 deletions(-) diff --git a/api-ref/source/api-ref-dcmanager-v1.rst b/api-ref/source/api-ref-dcmanager-v1.rst index 835a4d3dc..481225240 100644 --- a/api-ref/source/api-ref-dcmanager-v1.rst +++ b/api-ref/source/api-ref-dcmanager-v1.rst @@ -261,7 +261,7 @@ serviceUnavailable (503) } ****************************************************** -Shows detailed information about a specific subcloud +Shows information about a specific subcloud ****************************************************** .. rest_method:: GET /v1.0/subclouds/​{subcloud}​ @@ -350,6 +350,98 @@ internalServerError (500), serviceUnavailable (503) This operation does not accept a request body. +****************************************************** +Shows additional information about a specific subcloud +****************************************************** + +.. rest_method:: GET /v1.0/subclouds/<200b>{subcloud}<200b>/detail + +**Normal response codes** + +200 + +**Error response codes** + +itemNotFound (404), badRequest (400), unauthorized (401), forbidden +(403), badMethod (405), HTTPUnprocessableEntity (422), +internalServerError (500), serviceUnavailable (503) + +**Request parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "subcloud", "URI", "xsd:string", "The subcloud reference, name or id." + +**Response parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "id (Optional)", "plain", "xsd:int", "The unique identifier for this object." + "created_at (Optional)", "plain", "xsd:dateTime", "The time when the object was created." + "updated_at (Optional)", "plain", "xsd:dateTime", "The time when the object was last updated." + "name (Optional)", "plain", "xsd:string", "The name provisioned for the subcloud." + "management (Optional)", "plain", "xsd:string", "Management state of the subcloud." + "availability (Optional)", "plain", "xsd:string", "Availability status of the subcloud." + "management-subnet (Optional)", "plain", "xsd:string", "Management subnet for subcloud in CIDR format." + "management-start-ip (Optional)", "plain", "xsd:string", "Start of management IP address range for subcloud." + "management-end-ip (Optional)", "plain", "xsd:string", "End of management IP address range for subcloud." + "systemcontroller-gateway-ip (Optional)", "plain", "xsd:string", "Systemcontroller gateway IP Address." + "endpoint_sync_status (Optional)", "plain", "xsd:list", "The list of endpoint sync statuses." + "platform_sync_status (Optional)", "plain", "xsd:string", "The platform sync status of the subcloud." + "volume_sync_status (Optional)", "plain", "xsd:string", "The volume sync status of the subcloud." + "compute_sync_status (Optional)", "plain", "xsd:string", "The compute sync status of the subcloud." + "network_sync_status (Optional)", "plain", "xsd:string", "The network sync status of the subcloud." + "patching_sync_status (Optional)", "plain", "xsd:string", "The patching sync status of the subcloud." + "oam_floating_ip (Optional)", "plain", "xsd:string", "OAM Floating IP of the subcloud." + +:: + + { + "description": "test subcloud", + "management-start-ip": "192.168.204.50", + "created-at": "2018-02-25 19:06:35.208505", + "updated-at": "2018-02-25 21:35:59.771779", + "software-version": "18.01", + "management-state": "unmanaged", + "availability-status": "offline", + "management-subnet": "192.168.204.0/24", + "systemcontroller-gateway-ip": "192.168.204.101", + "location": "ottawa", + "endpoint_sync_status": [ + { + "sync_status": "in-sync", + "endpoint_type": "compute" + }, + { + "sync_status": "in-sync", + "endpoint_type": "network" + }, + { + "sync_status": "in-sync", + "endpoint_type": "patching" + }, + { + "sync_status": "in-sync", + "endpoint_type": "platform" + }, + { + "sync_status": "in-sync", + "endpoint_type": "volume" + } + ], + "management-gateway-ip": "192.168.204.1", + "management-end-ip": "192.168.204.100", + "id": 1, + "name": "subcloud6", + "oam_floating_ip" "10.10.10.12" + } + +This operation does not accept a request body. + ****************************** Modifies a specific subcloud ****************************** @@ -449,65 +541,6 @@ Deletes a specific subcloud This operation does not accept a request body. -**************************************************** -Generates the configuration of a specific subcloud -**************************************************** - -.. rest_method:: POST /v1.0/subclouds/​{subcloud}​/config - -**Normal response codes** - -200 - -**Error response codes** - -badRequest (400), unauthorized (401), forbidden (403), badMethod (405), -HTTPUnprocessableEntity (422), internalServerError (500), -serviceUnavailable (503) - -**Request parameters** - -.. csv-table:: - :header: "Parameter", "Style", "Type", "Description" - :widths: 20, 20, 20, 60 - - "subcloud", "URI", "xsd:string", "The subcloud reference, name or id." - "pxe-subnet (Optional)", "plain", "xsd:string", "PXE boot boot subnet for subcloud in CIDR format." - "management-vlan (Optional)", "plain", "xsd:string", "VLAN for subcloud management network." - "management-interface-port (Optional)", "plain", "xsd:string", "Subcloud management interface port." - "management-interface-mtu (Optional)", "plain", "xsd:string", "Subcloud management interface mtu." - "oam-subnet (Optional)", "plain", "xsd:string", "OAM subnet for subcloud in CIDR format." - "oam-gateway-ip (Optional)", "plain", "xsd:string", "OAM gateway IP for subcloud." - "oam-floating-ip (Optional)", "plain", "xsd:string", "OAM floating IP address for subcloud." - "oam-unit-0-ip (Optional)", "plain", "xsd:string", "OAM unit 0 IP address for subcloud." - "oam-unit-1-ip (Optional)", "plain", "xsd:string", "OAM unit 1 IP address for subcloud." - "oam-interface-port (Optional)", "plain", "xsd:string", "Subcloud OAM interface port." - "oam-interface-mtu (Optional)", "plain", "xsd:string", "Subcloud OAM interface mtu." - "system-mode (Optional)", "plain", "xsd:string", "System mode, ``simplex, duplex, or duplex-direct``." - -:: - - { - "oam-gateway-ip": "10.10.20.1", - "oam-interface-mtu": "1500", - "oam-subnet": "10.10.20.0/24", - "management-interface-port": "enp0s3", - "system-mode": "duplex", - "management-interface-mtu": "1500", - "oam-unit-1-ip": "10.10.20.4", - "oam-interface-port": "enp0s8", - "management-vlan": "10", - "pxe-subnet": "192.168.205.0/24", - "oam-unit-0-ip": "10.10.20.3", - "oam-floating-ip": "10.10.20.2" - } - -:: - - { - "config": "[SYSTEM]\nSYSTEM_MODE=duplex\n[REGION2_PXEBOOT_NETWORK]\nPXEBOOT_CIDR = 192.168.205.0/24\n[MGMT_NETWORK]\nVLAN = 10\nCIDR = 192.168.204.0/24\nGATEWAY = 192.168.204.1\nIP_START_ADDRESS = 192.168.204.50\nIP_END_ADDRESS = 192.168.204.100\nDYNAMIC_ALLOCATION = Y\nLOGICAL_INTERFACE = LOGICAL_INTERFACE_1\n[LOGICAL_INTERFACE_1]\nLAG_INTERFACE = N\nINTERFACE_MTU = 1500\nINTERFACE_PORTS = enp0s3\n[OAM_NETWORK]\nCIDR = 10.10.20.0/24\nGATEWAY = 10.10.20.1\nIP_FLOATING_ADDRESS = 10.10.20.2\nIP_UNIT_0_ADDRESS = 10.10.20.3\nIP_UNIT_1_ADDRESS = 10.10.20.4\nLOGICAL_INTERFACE = LOGICAL_INTERFACE_2\n[LOGICAL_INTERFACE_2]\nLAG_INTERFACE = N\nINTERFACE_MTU = 1500\nINTERFACE_PORTS = enp0s8\n[SHARED_SERVICES]\nSYSTEM_CONTROLLER_SUBNET = 192.168.204.0/24\nSYSTEM_CONTROLLER_FLOATING_ADDRESS = 192.168.204.2\nREGION_NAME = RegionOne\nADMIN_PROJECT_NAME = admin\nADMIN_USER_NAME = admin\nADMIN_PASSWORD = Li69nux*\nKEYSTONE_ADMINURL = http://192.168.204.2:5000/v3\nKEYSTONE_SERVICE_NAME = keystone\nKEYSTONE_SERVICE_TYPE = identity\nGLANCE_SERVICE_NAME = glance\nGLANCE_SERVICE_TYPE = image\nGLANCE_CACHED = True\n[REGION_2_SERVICES]\nREGION_NAME = subcloud6\n[VERSION]\nRELEASE = 18.01\n" - } - ---------------- Subcloud Alarms ---------------- diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index 5bcae9b77..fc4b173c5 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -37,6 +37,7 @@ from controllerconfig.utils import validate_address_str from controllerconfig.utils import validate_network_str from dcorch.drivers.openstack.keystone_v3 import KeystoneClient +from keystoneauth1 import exceptions as keystone_exceptions from dcmanager.api.controllers import restcomm from dcmanager.common import consts @@ -48,7 +49,6 @@ from dcmanager.db import api as db_api from dcmanager.drivers.openstack.sysinv_v1 import SysinvClient from dcmanager.rpc import client as rpc_client - CONF = cfg.CONF LOG = logging.getLogger(__name__) # System mode @@ -333,8 +333,37 @@ class SubcloudsController(object): sysinv_client = SysinvClient(consts.DEFAULT_REGION_NAME, session) return sysinv_client.get_management_address_pool() + @staticmethod + def get_ks_client(region_name=None): + """This will get a new keystone client (and new token)""" + try: + return KeystoneClient(region_name) + except Exception: + LOG.warn('Failure initializing KeystoneClient ' + 'for region %s' % region_name) + raise + + def _get_oam_addresses(self, context, subcloud_name): + """Get the subclouds oam addresses""" + + # First need to retrieve the Subcloud's Keystone session + try: + sc_ks_client = self.get_ks_client(subcloud_name) + sysinv_client = SysinvClient(subcloud_name, + sc_ks_client.session) + return sysinv_client.get_oam_addresses() + except (keystone_exceptions.EndpointNotFound, IndexError) as e: + message = ("Identity endpoint for subcloud: %s not found. %s" % + (subcloud_name, e)) + LOG.error(message) + except exceptions.OAMAddressesNotFound: + message = ("OAM addresses for subcloud: %s not found." % + (subcloud_name)) + LOG.error(message) + return None + @index.when(method='GET', template='json') - def get(self, subcloud_ref=None): + def get(self, subcloud_ref=None, detail=None): """Get details about subcloud. :param subcloud_ref: ID or name of subcloud @@ -437,6 +466,17 @@ class SubcloudsController(object): subcloud_status_list} subcloud_dict.update(endpoint_sync_dict) + if detail is not None: + oam_addresses = self._get_oam_addresses(context, + subcloud_ref) + if oam_addresses is not None: + oam_floating_ip = oam_addresses.oam_floating_ip + else: + oam_floating_ip = "unavailable" + floating_ip_dict = {"oam_floating_ip": + oam_floating_ip} + subcloud_dict.update(floating_ip_dict) + return subcloud_dict @utils.synchronized(LOCK_NAME) diff --git a/distributedcloud/dcmanager/common/exceptions.py b/distributedcloud/dcmanager/common/exceptions.py index fb603943d..1bde8edd8 100644 --- a/distributedcloud/dcmanager/common/exceptions.py +++ b/distributedcloud/dcmanager/common/exceptions.py @@ -136,6 +136,10 @@ class InternalError(DCManagerException): message = _("Error when performing operation") +class OAMAddressesNotFound(NotFound): + message = _("OAM Addresses Not Found") + + class InvalidInputError(DCManagerException): message = _("An invalid value was provided") diff --git a/distributedcloud/dcmanager/drivers/openstack/sysinv_v1.py b/distributedcloud/dcmanager/drivers/openstack/sysinv_v1.py index fa7e76e01..5909c3092 100644 --- a/distributedcloud/dcmanager/drivers/openstack/sysinv_v1.py +++ b/distributedcloud/dcmanager/drivers/openstack/sysinv_v1.py @@ -87,18 +87,14 @@ class SysinvClient(base.DriverBase): return self.sysinv_client.address_pool.get(address_pool_uuid) - def get_oam_address_pool(self): + def get_oam_addresses(self): """Get the oam address pool for a host.""" - networks = self.sysinv_client.network.list() - for network in networks: - if network.type == sysinv_constants.NETWORK_TYPE_OAM: - address_pool_uuid = network.pool_uuid - break + iextoam_object = self.sysinv_client.iextoam.list() + if iextoam_object is not None and len(iextoam_object) != 0: + return iextoam_object[0] else: - LOG.error("OAM address pool not found") - raise exceptions.InternalError() - - return self.sysinv_client.address_pool.get(address_pool_uuid) + LOG.error("OAM address not found") + raise exceptions.OAMAddressesNotFound() def create_route(self, interface_uuid, network, prefix, gateway, metric): """Create a static route on an interface.""" diff --git a/distributedcloud/dcmanager/manager/subcloud_install.py b/distributedcloud/dcmanager/manager/subcloud_install.py index 50a80ce50..98ad16b9d 100644 --- a/distributedcloud/dcmanager/manager/subcloud_install.py +++ b/distributedcloud/dcmanager/manager/subcloud_install.py @@ -153,8 +153,8 @@ class SubcloudInstall(object): raise e def get_oam_address(self): - oam_pool = self.sysinv_client.get_oam_address_pool() - return self.format_address(oam_pool.floating_address) + oam_addresses = self.sysinv_client.get_oam_addresses() + return self.format_address(oam_addresses.oam_floating_ip) def get_https_enabled(self): if self.https_enabled is None: diff --git a/distributedcloud/dcmanager/manager/subcloud_manager.py b/distributedcloud/dcmanager/manager/subcloud_manager.py index 5952484dc..031939438 100644 --- a/distributedcloud/dcmanager/manager/subcloud_manager.py +++ b/distributedcloud/dcmanager/manager/subcloud_manager.py @@ -487,9 +487,9 @@ class SubcloudManager(manager.Manager): mgmt_floating_ip = mgmt_pool.floating_address mgmt_subnet = "%s/%d" % (mgmt_pool.network, mgmt_pool.prefix) - oam_pool = sysinv_client.get_oam_address_pool() - oam_floating_ip = oam_pool.floating_address - oam_subnet = "%s/%d" % (oam_pool.network, oam_pool.prefix) + oam_addresses = sysinv_client.get_oam_addresses() + oam_floating_ip = oam_addresses.oam_floating_ip + oam_subnet = oam_addresses.oam_subnet with open(overrides_file, 'w') as f_out_overrides_file: f_out_overrides_file.write( diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py index 73e55a43b..6bebedbd7 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py @@ -23,6 +23,7 @@ import copy import mock import six +from six.moves import http_client import webtest from dcmanager.api.controllers.v1 import subclouds @@ -31,7 +32,6 @@ from dcmanager.rpc import client as rpc_client from dcmanager.tests.unit.api import test_root_controller as testroot from dcmanager.tests import utils - FAKE_TENANT = utils.UUID1 FAKE_ID = '1' FAKE_URL = '/v1.0/subclouds' @@ -84,6 +84,20 @@ class FakeAddressPool(object): self.ranges.append(range) +class FakeOAMAddressPool(object): + def __init__(self, oam_subnet, oam_start_ip, + oam_end_ip, oam_c1_ip, + oam_c0_ip, oam_gateway_ip, + oam_floating_ip): + self.oam_start_ip = oam_start_ip + self.oam_end_ip = oam_end_ip + self.oam_c1_ip = oam_c1_ip + self.oam_c0_ip = oam_c0_ip + self.oam_subnet = oam_subnet + self.oam_gateway_ip = oam_gateway_ip + self.oam_floating_ip = oam_floating_ip + + class TestSubclouds(testroot.DCManagerApiTest): def setUp(self): super(TestSubclouds, self).setUp() @@ -404,13 +418,58 @@ class TestSubclouds(testroot.DCManagerApiTest): self.app.delete_json, delete_url, headers=FAKE_HEADERS) + @mock.patch.object(subclouds.SubcloudsController, + '_get_oam_addresses') @mock.patch.object(rpc_client, 'ManagerClient') @mock.patch.object(subclouds, 'db_api') - def test_get_subcloud(self, mock_db_api, mock_rpc_client): + def test_get_subcloud(self, + mock_db_api, + mock_rpc_client, + mock_get_oam_addresses): get_url = FAKE_URL + '/' + FAKE_ID - self.app.get(get_url, headers=FAKE_HEADERS) + response = self.app.get(get_url, headers=FAKE_HEADERS) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + self.assertEqual(response.json.get('oam_floating_ip', None), None) self.assertEqual(1, mock_db_api.subcloud_get_with_status.call_count) + @mock.patch.object(subclouds.SubcloudsController, + '_get_oam_addresses') + @mock.patch.object(rpc_client, 'ManagerClient') + @mock.patch.object(subclouds, 'db_api') + def test_get_subcloud_with_additional_detail(self, + mock_db_api, + mock_rpc_client, + mock_get_oam_addresses): + get_url = FAKE_URL + '/' + FAKE_ID + '/detail' + oam_addresses = FakeOAMAddressPool('10.10.10.254', + '10.10.10.1', + '10.10.10.254', + '10.10.10.4', + '10.10.10.3', + '10.10.10.1', + '10.10.10.2') + mock_get_oam_addresses.return_value = oam_addresses + response = self.app.get(get_url, headers=FAKE_HEADERS) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + self.assertEqual('10.10.10.2', response.json['oam_floating_ip']) + + @mock.patch.object(subclouds.SubcloudsController, + '_get_oam_addresses') + @mock.patch.object(rpc_client, 'ManagerClient') + @mock.patch.object(subclouds, 'db_api') + def test_subcloud_oam_ip_unavailable(self, + mock_db_api, + mock_rpc_client, + mock_get_oam_addresses): + get_url = FAKE_URL + '/' + FAKE_ID + '/detail' + mock_get_oam_addresses.return_value = None + response = self.app.get(get_url, headers=FAKE_HEADERS) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + self.assertEqual('unavailable', response.json['oam_floating_ip']) + @mock.patch.object(rpc_client, 'ManagerClient') @mock.patch.object(subclouds, 'db_api') def test_get_wrong_request(self, mock_db_api, mock_rpc_client):