diff --git a/api-ref/source/api-ref-dcmanager-v1.rst b/api-ref/source/api-ref-dcmanager-v1.rst index 85bd8cbd3..80c940140 100644 --- a/api-ref/source/api-ref-dcmanager-v1.rst +++ b/api-ref/source/api-ref-dcmanager-v1.rst @@ -1791,6 +1791,37 @@ Response Example :language: json +************************************ +Delete Subcloud Deploy Files +************************************ + +.. rest_method:: DELETE /v1.0/subcloud-deploy + +**Normal response codes** + +200 + +**Error response codes** + +badRequest (400), unauthorized (401), forbidden (403), notFound (404), +HTTPUnprocessableEntity (422), internalServerError (500), +serviceUnavailable (503) + +**Request parameters** + +.. rest_parameters:: parameters.yaml + + - release: release_uri + - deployment_files: delete_subcloud_deployment_files + - prestage_images: delete_subcloud_deploy_prestage_images + +Request Example +---------------- + +.. literalinclude:: samples/subcloud-deploy/subcloud-deploy-delete-request.json + :language: json + + ---------------------- Phased Subcloud Deploy ---------------------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 4d9786fbb..97123776f 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -239,6 +239,20 @@ default_instance_action: in: body required: true type: string +delete_subcloud_deploy_prestage_images: + description: | + The flag to indicate the deployment manager prestage images + file to be deleted. + in: body + required: false + type: boolean +delete_subcloud_deployment_files: + description: | + The flag to indicate the deploy playbook, deploy overrides, + deploy chart files to be deleted. + in: body + required: false + type: boolean deploy_config: description: | The content of a file containing the resource definitions describing diff --git a/api-ref/source/samples/subcloud-deploy/subcloud-deploy-delete-request.json b/api-ref/source/samples/subcloud-deploy/subcloud-deploy-delete-request.json new file mode 100644 index 000000000..d6203a76c --- /dev/null +++ b/api-ref/source/samples/subcloud-deploy/subcloud-deploy-delete-request.json @@ -0,0 +1,5 @@ +{ + "release": 23.09, + "prestage_images": false, + "deployment_files": false +} diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py index 6025debbb..78382111a 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py @@ -149,3 +149,44 @@ class SubcloudDeployController(object): filename = filename.replace(prefix, '', 1) deploy_dicts.update({f: filename}) return dict(subcloud_deploy=deploy_dicts) + + @index.when(method='DELETE', template='json') + def delete(self, release=None): + """Delete the subcloud deploy files. + + :param release: release version + """ + policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "delete", {}, + restcomm.extract_credentials_for_policy()) + + is_prestage_images = request.params.get('prestage_images', '').lower() == 'true' + is_deployment_files = request.params.get('deployment_files', '').lower() == 'true' + + dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, utils.get_sw_version(release)) + if not os.path.isdir(dir_path): + pecan.abort(httpclient.NOT_FOUND, + _("Directory not found: %s" % dir_path)) + try: + file_options = [] + if is_prestage_images: + file_options.append(consts.DEPLOY_PRESTAGE) + + if is_deployment_files: + file_options.extend([consts.DEPLOY_OVERRIDES, consts.DEPLOY_CHART, + consts.DEPLOY_PLAYBOOK]) + + if not (is_deployment_files or is_prestage_images): + file_options.extend(consts.DEPLOY_COMMON_FILE_OPTIONS) + + for file_option in file_options: + prefix = file_option + '_' + file_name = utils.get_filename_by_prefix(dir_path, prefix) + if file_name: + os.remove(os.path.join(dir_path, file_name)) + else: + LOG.warning('%s file not present' % file_option) + + except Exception as e: + pecan.abort(httpclient.INTERNAL_SERVER_ERROR, + _("Failed to delete file: %s" % e)) + return None diff --git a/distributedcloud/dcmanager/api/policies/subcloud_deploy.py b/distributedcloud/dcmanager/api/policies/subcloud_deploy.py index f8ace29f9..f96fc93f5 100644 --- a/distributedcloud/dcmanager/api/policies/subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/policies/subcloud_deploy.py @@ -36,6 +36,21 @@ subcloud_deploy_rules = [ 'path': '/v1.0/subcloud-deploy/{release}' } ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete subcloud deploy files.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1.0/subcloud-deploy' + }, + { + 'method': 'DELETE', + 'path': '/v1.0/subcloud-deploy/{release}' + } + ] ) ] 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 201b0c6d4..2896c35e8 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 @@ -14,8 +14,10 @@ # under the License. # import os +from os import path as os_path import mock +import six from six.moves import http_client import webtest @@ -30,7 +32,7 @@ from dcmanager.tests import utils from tsconfig.tsconfig import SW_VERSION -FAKE_SOFTWARE_VERSION = '21.12' +FAKE_SOFTWARE_VERSION = '22.12' FAKE_TENANT = utils.UUID1 FAKE_ID = '1' FAKE_URL = '/v1.0/subcloud-deploy' @@ -40,6 +42,7 @@ FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', FAKE_DEPLOY_PLAYBOOK_PREFIX = consts.DEPLOY_PLAYBOOK + '_' FAKE_DEPLOY_OVERRIDES_PREFIX = consts.DEPLOY_OVERRIDES + '_' FAKE_DEPLOY_CHART_PREFIX = consts.DEPLOY_CHART + '_' +FAKE_PRESTAGE_IMAGES_PREFIX = consts.DEPLOY_PRESTAGE + '_' FAKE_DEPLOY_PLAYBOOK_FILE = 'deployment-manager.yaml' FAKE_DEPLOY_OVERRIDES_FILE = 'deployment-manager-overrides-subcloud.yaml' FAKE_DEPLOY_CHART_FILE = 'deployment-manager.tgz' @@ -48,6 +51,21 @@ FAKE_DEPLOY_FILES = { FAKE_DEPLOY_OVERRIDES_PREFIX: FAKE_DEPLOY_OVERRIDES_FILE, FAKE_DEPLOY_CHART_PREFIX: FAKE_DEPLOY_CHART_FILE, } +FAKE_DEPLOY_DELETE_FILES = { + FAKE_DEPLOY_PLAYBOOK_PREFIX: '/opt/platform/deploy/22.12/deployment-manager.yaml', + FAKE_DEPLOY_OVERRIDES_PREFIX: + '/opt/platform/deploy/22.12/deployment-manager-overrides-subcloud.yaml', + FAKE_DEPLOY_CHART_PREFIX: '/opt/platform/deploy/22.12/deployment-manager.tgz', + FAKE_PRESTAGE_IMAGES_PREFIX: '/opt/platform/deploy/22.12/prestage_images.yml' +} + + +def get_filename_by_prefix_side_effect(dir_path, prefix): + filename = FAKE_DEPLOY_FILES.get(prefix) + if filename: + return prefix + FAKE_DEPLOY_FILES.get(prefix) + else: + return None class TestSubcloudDeploy(testroot.DCManagerApiTest): @@ -269,3 +287,127 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest): f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1/install_values.yml') self.assertEqual(deploy_config, f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1_deploy_config.yml') + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + def test_subcloud_deploy_delete_directory_not_found(self, + mock_get_sw_version, + mock_path_isdir): + + mock_get_sw_version.return_value = '21.12' + url = FAKE_URL + '?prestage_images=' + \ + str(False) + '&deployment_files=' + str(False) + mock_path_isdir.side_effect = lambda x: True \ + if x == '/opt/platform/deploy/22.12' else False + six.assertRaisesRegex(self, webtest.app.AppError, "404 *", + self.app.delete, url, + headers=FAKE_HEADERS) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + def test_subcloud_deploy_delete_internal_server_error(self, + mock_get_sw_version, + mock_path_isdir): + + mock_get_sw_version.return_value = '22.12' + mock_path_isdir.side_effect = lambda x: True \ + if x == '/opt/platform/deploy/22.12' else False + six.assertRaisesRegex(self, webtest.app.AppError, "500 *", + self.app.delete, FAKE_URL, + headers=FAKE_HEADERS) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + @mock.patch.object(dutils, 'get_filename_by_prefix') + @mock.patch.object(os, 'remove') + def test_subcloud_deploy_delete_with_release(self, mock_os_remove, + mock_get_filename_by_prefix, + mock_get_sw_version, + mock_path_isdir): + + mock_os_remove.return_value = None + mock_get_sw_version.return_value = '22.12' + + mock_get_filename_by_prefix.side_effect = \ + get_filename_by_prefix_side_effect + mock_path_isdir.return_value = True + url = FAKE_URL + '/' + FAKE_SOFTWARE_VERSION + \ + '?prestage_images=' + str(False) + '&deployment_files=' + str(False) + response = self.app.delete(url, headers=FAKE_HEADERS) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + @mock.patch.object(dutils, 'get_filename_by_prefix') + @mock.patch.object(os, 'remove') + def test_subcloud_deploy_delete_without_release(self, mock_os_remove, + mock_get_filename_by_prefix, + mock_get_sw_version, + mock_path_isdir): + + mock_os_remove.return_value = None + mock_get_sw_version.return_value = '22.12' + url = FAKE_URL + '?prestage_images=' + \ + str(True) + '&deployment_files=' + str(True) + mock_get_filename_by_prefix.side_effect = \ + get_filename_by_prefix_side_effect + mock_path_isdir.return_value = True + response = self.app.delete(url, headers=FAKE_HEADERS) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + @mock.patch.object(dutils, 'get_filename_by_prefix') + @mock.patch.object(os, 'remove') + def test_subcloud_deploy_delete_deployment_files(self, mock_os_remove, + mock_get_filename_by_prefix, + mock_get_sw_version, + mock_path_isdir): + mock_os_remove.return_value = None + mock_get_sw_version.return_value = '22.12' + url = FAKE_URL + '?prestage_images=' + \ + str(False) + '&deployment_files=' + str(True) + mock_get_filename_by_prefix.side_effect = \ + get_filename_by_prefix_side_effect + mock_path_isdir.side_effect = lambda x: True \ + if x == '/opt/platform/deploy/22.12' else False + response = self.app.delete(url, headers=FAKE_HEADERS) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + @mock.patch.object(dutils, 'get_filename_by_prefix') + @mock.patch.object(os, 'remove') + def test_subcloud_deploy_delete_prestage_images(self, mock_os_remove, + mock_get_filename_by_prefix, + mock_get_sw_version, + mock_path_isdir): + mock_os_remove.return_value = None + mock_get_sw_version.return_value = '22.12' + url = FAKE_URL + '?prestage_images=' + \ + str(True) + '&deployment_files=' + str(False) + mock_get_filename_by_prefix.side_effect = \ + get_filename_by_prefix_side_effect + mock_path_isdir.side_effect = lambda x: True \ + if x == '/opt/platform/deploy/22.12' else False + response = self.app.delete(url, headers=FAKE_HEADERS) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(os_path, 'isdir') + @mock.patch.object(dutils, 'get_sw_version') + @mock.patch.object(dutils, 'get_filename_by_prefix') + @mock.patch.object(os, 'remove') + def test_subcloud_deploy_delete_with_both_parameters(self, mock_os_remove, + mock_get_filename_by_prefix, + mock_get_sw_version, + mock_path_isdir): + mock_os_remove.return_value = None + mock_get_sw_version.return_value = '22.12' + url = FAKE_URL + '?prestage_images=' + \ + str(True) + '&deployment_files=' + str(True) + mock_get_filename_by_prefix.side_effect = \ + get_filename_by_prefix_side_effect + mock_path_isdir.side_effect = lambda x: True \ + if x == '/opt/platform/deploy/22.12' else False + response = self.app.delete(url, headers=FAKE_HEADERS) + self.assertEqual(response.status_code, http_client.OK)