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 64bfdea64..c54685daa 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -416,3 +416,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 9cf04537c..507670d6d 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 @@ -1105,3 +1106,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'])