From 280f22364c5dd8b5b87ddcbc4eab191b7cdeda4c Mon Sep 17 00:00:00 2001 From: Salman Rana Date: Thu, 17 Aug 2023 17:19:46 -0400 Subject: [PATCH] Add release version parameter validation Introduce validate_release_version_supported, a dcmanager util method to check whether a release version is supported by the current active version. This is necessary as many dcmanager operations/commands now allow the user to specify a release version (e.g., subcloud add, deploy, restore). That is, this check is intended to validate the release version parameter provided to the various operations that support it. This implementation is based on sysinv-conductor's import_load check, which parses the upgrades metadata.xml file to check for the supported versions. This validation check will be iteratively added to all the API endpoints that consume a release parameter. Currently, it is only used for the subcloud deploy upload endpoint. Test Plan: Test the release parameter using deploy upload - "subcloud-deploy upload ... --release ": 1. PASS: Verify that the current active version is accepted as a valid parameter. 2. PASS: Verify that all the supported upgrade versions in /usr/rootdirs/opt/upgrades/metadata.xml are accepted as a valid release parameter. 3. PASS: Verify that any upgrade version that's not included in metadata.xml is rejected with an error " is not a supported release version". The only exception to this is the current active version, it must always be valid. 4. PASS: Delete all the "supported_upgrades" elements in metadata.xml and verify the error "Unable to validate the release version" is printed. 5. PASS: Delete metadata.xml and verify that "Unable to validate the release version" error is printed. 6. PASS: Verify that the current active version is valid regardless of metadata.xml file and its contents. 7. PASS: Exclude the release parameter and verify that the deploy upload is successful (with an upload completed for the current active version). Closes-Bug: 2031557 Change-Id: Id8b5670b8cfcdf86d36bbf9180abe064c4279d1a Signed-off-by: Salman Rana --- .../api/controllers/v1/subcloud_deploy.py | 11 +++- distributedcloud/dcmanager/common/consts.py | 2 + distributedcloud/dcmanager/common/utils.py | 55 +++++++++++++++++++ .../v1/controllers/test_subcloud_deploy.py | 15 ++++- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py index 2fc96e7fc..4f051fad0 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py @@ -32,6 +32,7 @@ from dcmanager.api.controllers import restcomm from dcmanager.api.policies import subcloud_deploy as subcloud_deploy_policy from dcmanager.api import policy from dcmanager.common import consts +from dcmanager.common import exceptions from dcmanager.common.i18n import _ from dcmanager.common import utils @@ -107,7 +108,15 @@ class SubcloudDeployController(object): software_version = tsc.SW_VERSION if request.POST.get('release'): - software_version = request.POST.get('release') + try: + utils.validate_release_version_supported(request.POST.get('release')) + software_version = request.POST.get('release') + except exceptions.ValidateFail as e: + pecan.abort(httpclient.BAD_REQUEST, + _("Error: invalid release version parameter. %s" % e)) + except Exception: + pecan.abort(httpclient.INTERNAL_SERVER_ERROR, + _('Error: unable to validate the release version.')) deploy_dicts['software_version'] = software_version dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version) diff --git a/distributedcloud/dcmanager/common/consts.py b/distributedcloud/dcmanager/common/consts.py index 116e94cab..0b5daa8e0 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -413,3 +413,5 @@ HELM_CHART_POSTFIX = 'deployment-manager' ALTERNATE_DEPLOY_PLAYBOOK_DIR = ALTERNATE_DEPLOY_FILES_DIR + '/playbooks' DEPLOY_PLAYBOOK_POSTFIX = 'deployment-manager.yaml' + +SUPPORTED_UPGRADES_METADATA_FILE_PATH = '/usr/rootdirs/opt/upgrades/metadata.xml' diff --git a/distributedcloud/dcmanager/common/utils.py b/distributedcloud/dcmanager/common/utils.py index 9b15237b0..224997bdc 100644 --- a/distributedcloud/dcmanager/common/utils.py +++ b/distributedcloud/dcmanager/common/utils.py @@ -28,6 +28,7 @@ import six.moves import string import subprocess import tsconfig.tsconfig as tsc +import xml.etree.ElementTree as ElementTree import yaml from keystoneauth1 import exceptions as keystone_exceptions @@ -1106,3 +1107,57 @@ def subcloud_is_secondary_state(deploy_state): def create_subcloud_rehome_data_template(): """Create a subcloud rehome data template""" return {'saved_payload': {}} + + +def validate_release_version_supported(release_version_to_check): + """Given a release version, check whether it's supported by the current active version. + + :param release_version_to_check: version string to validate + + returns True to indicate that the version is valid + raise ValidateFail for an invalid/unsupported release version + """ + + current_version = tsc.SW_VERSION + + if current_version == release_version_to_check: + return True + + supported_versions = get_current_supported_upgrade_versions() + + if release_version_to_check not in supported_versions: + msg = "%s is not a supported release version" % release_version_to_check + raise exceptions.ValidateFail(msg) + + return True + + +def get_current_supported_upgrade_versions(): + """Parse the upgrades metadata file to build a list of supported versions. + + returns a list of supported upgrade versions + raise InternalError exception for a missing/invalid metadata file + """ + + supported_versions = [] + + try: + with open(consts.SUPPORTED_UPGRADES_METADATA_FILE_PATH) as file: + root = ElementTree.fromstring(file.read()) + except Exception: + LOG.exception("Error reading the supported upgrades metadata file") + raise exceptions.InternalError() + + supported_upgrades = root.find('supported_upgrades') + + if not supported_upgrades: + LOG.error("Missing supported upgrades information") + raise exceptions.InternalError() + + upgrades = supported_upgrades.findall("upgrade") + + for upgrade in upgrades: + version = upgrade.findtext("version") + supported_versions.append(version.strip()) + + return supported_versions diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py index ef616d93c..695ae42eb 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py @@ -48,6 +48,12 @@ FAKE_DEPLOY_FILES = { FAKE_DEPLOY_CHART_PREFIX: FAKE_DEPLOY_CHART_FILE, } +FAKE_UPGRADES_METADATA = ''' + \n0.2\n + \n\n%s\nPATCH_0001 + \n\n\n +''' % FAKE_SOFTWARE_VERSION + class TestSubcloudDeploy(testroot.DCManagerApiTest): def setUp(self): @@ -65,9 +71,12 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest): fields.append((opt, webtest.Upload(fake_name, fake_content))) mock_upload_files.return_value = True params += fields - response = self.app.post(FAKE_URL, - headers=FAKE_HEADERS, - params=params) + + with mock.patch('builtins.open', mock.mock_open(read_data=FAKE_UPGRADES_METADATA)): + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + params=params) + self.assertEqual(response.status_code, http_client.OK) self.assertEqual(FAKE_SOFTWARE_VERSION, response.json['software_version'])