From 0d05b33719eba6052f5a4402bd7aa619cc0a99ec Mon Sep 17 00:00:00 2001 From: BoYuan Chang Date: Tue, 25 Jan 2022 12:12:16 -0500 Subject: [PATCH] Support subcloud deploy upload prestaging images Modify the existing REST API to upload the prestaging images list to system controller Test Cases Completed on local VM as well as DC Lab a. (Passed) With all 4 variables b. (Passed) With only --prestage-images c. (Passed) With all other variables expect for --prestage-images d. (Passed) With 1 or 2 missing varibles when --prestage-images is present e. (Passed) With 1 or multiple invalid directory path REST APIs: curl -X POST -H "X-Auth-Token: $TOKEN" $APIURL/subcloud-deploy \ -F deploy_playbook=@ \ -F deploy_overrides=@ \ -F deploy_chart=@full path of the helm chart name> \ -F prestage_images=@ CLI: dcmanager subcloud-deploy upload \ --deploy-playbook \ <- Existing --deploy-chart \ <- Existing --deploy-overrides <- Existing --prestage-images <- Added Story: 2009799 Task: 44342 Change-Id: I75d4ac0931d2a37ceb68281a2ab6137ee2459e99 --- api-ref/source/api-ref-dcmanager-v1.rst | 5 +- api-ref/source/parameters.yaml | 12 +++ .../api/controllers/v1/subcloud_deploy.py | 28 ++++++- distributedcloud/dcmanager/common/consts.py | 6 +- .../v1/controllers/test_subcloud_deploy.py | 82 ++++++++++++++++++- 5 files changed, 124 insertions(+), 9 deletions(-) diff --git a/api-ref/source/api-ref-dcmanager-v1.rst b/api-ref/source/api-ref-dcmanager-v1.rst index 56dbe287b..65fbb8d23 100644 --- a/api-ref/source/api-ref-dcmanager-v1.rst +++ b/api-ref/source/api-ref-dcmanager-v1.rst @@ -1433,7 +1433,7 @@ Subcloud Deploy ---------------- These APIs allow for the display and upload of the deployment manager common -files which include deploy playbook, deploy overrides, and deploy helm charts. +files which include deploy playbook, deploy overrides, deploy helm charts, and prestage images list. ************************** @@ -1463,6 +1463,7 @@ internalServerError (500), serviceUnavailable (503) - deploy_chart: subcloud_deploy_chart - deploy_playbook: subcloud_deploy_playbook - deploy_overrides: subcloud_deploy_overrides + - prestage_images: subcloud_deploy_prestage_images Response Example ---------------- @@ -1496,6 +1497,7 @@ serviceUnavailable (503) - deploy_chart: subcloud_deploy_chart_content - deploy_playbook: subcloud_deploy_playbook_content - deploy_overrides: subcloud_deploy_overrides_content + - prestage_images: subcloud_deploy_prestage_images_content Request Example ---------------- @@ -1510,6 +1512,7 @@ Request Example - deploy_chart: subcloud_deploy_chart - deploy_playbook: subcloud_deploy_playbook - deploy_overrides: subcloud_deploy_overrides + - prestage_images: subcloud_deploy_prestage_images Response Example ---------------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 5e3541200..94963b6d8 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -359,6 +359,18 @@ subcloud_deploy_playbook_content: in: body required: false type: string +subcloud_deploy_prestage_images: + description: | + The file name of the deployment manager prestage images. + in: body + required: false + type: string +subcloud_deploy_prestage_images_content: + description: | + The content of the deployment manager prestage images. + in: body + required: false + type: string subcloud_description: description: | The description of a subcloud. diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py index d2d7c3457..b5985c30a 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2020 Wind River Systems, Inc. +# Copyright (c) 2020, 2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -47,6 +47,7 @@ class SubcloudDeployController(object): @staticmethod def _upload_files(dir_path, file_option, file_item, binary): + prefix = file_option + '_' # create the version directory if it does not exist if not os.path.isdir(dir_path): @@ -78,18 +79,37 @@ class SubcloudDeployController(object): @index.when(method='POST', template='json') def post(self): deploy_dicts = dict() + missing_options = set() for f in consts.DEPLOY_COMMON_FILE_OPTIONS: if f not in request.POST: - pecan.abort(httpclient.BAD_REQUEST, - _("Missing required file for %s") % f) + missing_options.add(f) + + # The API will only accept three types of input scenarios: + # 1. DEPLOY_PLAYBOOK, DEPLOY_OVERRIDES, and DEPLOY_CHART + # 2. DEPLOY_PLAYBOOK, DEPLOY_OVERRIDES, DEPLOY_CHART, and DEPLOY_PRESTAGE + # 3. DEPLOY_PRESTAGE + size = len(missing_options) + if len(missing_options) > 0: + if ((consts.DEPLOY_PRESTAGE in missing_options and size != 1) or + (consts.DEPLOY_PRESTAGE not in missing_options and size != 3)): + missing_str = str() + for missing in missing_options: + if missing is not consts.DEPLOY_PRESTAGE: + missing_str += '--%s ' % missing + error_msg = "error: argument %s is required" % missing_str.rstrip() + pecan.abort(httpclient.BAD_REQUEST, error_msg) + + for f in consts.DEPLOY_COMMON_FILE_OPTIONS: + if f not in request.POST: + continue file_item = request.POST[f] filename = getattr(file_item, 'filename', '') if not filename: pecan.abort(httpclient.BAD_REQUEST, _("No %s file uploaded" % f)) - dir_path = tsc.DEPLOY_PATH + binary = False if f == consts.DEPLOY_CHART: binary = True diff --git a/distributedcloud/dcmanager/common/consts.py b/distributedcloud/dcmanager/common/consts.py index 70f8a40a3..118e91260 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -1,5 +1,5 @@ # Copyright (c) 2016 Ericsson AB. -# Copyright (c) 2017-2021 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -212,11 +212,13 @@ DEPLOY_PLAYBOOK = "deploy_playbook" DEPLOY_OVERRIDES = "deploy_overrides" DEPLOY_CHART = "deploy_chart" DEPLOY_CONFIG = 'deploy_config' +DEPLOY_PRESTAGE = "prestage_images" DEPLOY_COMMON_FILE_OPTIONS = [ DEPLOY_PLAYBOOK, DEPLOY_OVERRIDES, - DEPLOY_CHART + DEPLOY_CHART, + DEPLOY_PRESTAGE ] 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 b2a0b4c25..9b57806e1 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 @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2021 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -50,7 +50,23 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest): @mock.patch.object(subcloud_deploy.SubcloudDeployController, '_upload_files') - def test_post_subcloud_deploy_missing_file(self, mock_upload_files): + def test_post_subcloud_deploy_missing_chart(self, mock_upload_files): + opts = [consts.DEPLOY_PLAYBOOK, consts.DEPLOY_OVERRIDES, consts.DEPLOY_PRESTAGE] + fields = list() + for opt in opts: + fake_name = opt + "_fake" + fake_content = "fake content".encode('utf-8') + fields.append((opt, fake_name, fake_content)) + mock_upload_files.return_value = True + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + upload_files=fields, + expect_errors=True) + self.assertEqual(response.status_code, http_client.BAD_REQUEST) + + @mock.patch.object(subcloud_deploy.SubcloudDeployController, + '_upload_files') + def test_post_subcloud_deploy_missing_chart_prestages(self, mock_upload_files): opts = [consts.DEPLOY_PLAYBOOK, consts.DEPLOY_OVERRIDES] fields = list() for opt in opts: @@ -64,6 +80,68 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest): expect_errors=True) self.assertEqual(response.status_code, http_client.BAD_REQUEST) + @mock.patch.object(subcloud_deploy.SubcloudDeployController, + '_upload_files') + def test_post_subcloud_deploy_missing_playbook_overrides(self, mock_upload_files): + opts = [consts.DEPLOY_CHART, consts.DEPLOY_PRESTAGE] + fields = list() + for opt in opts: + fake_name = opt + "_fake" + fake_content = "fake content".encode('utf-8') + fields.append((opt, fake_name, fake_content)) + mock_upload_files.return_value = True + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + upload_files=fields, + expect_errors=True) + self.assertEqual(response.status_code, http_client.BAD_REQUEST) + + @mock.patch.object(subcloud_deploy.SubcloudDeployController, + '_upload_files') + def test_post_subcloud_deploy_missing_prestage(self, mock_upload_files): + opts = [consts.DEPLOY_PLAYBOOK, consts.DEPLOY_OVERRIDES, consts.DEPLOY_CHART] + fields = list() + for opt in opts: + fake_name = opt + "_fake" + fake_content = "fake content".encode('utf-8') + fields.append((opt, fake_name, fake_content)) + mock_upload_files.return_value = True + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + upload_files=fields) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(subcloud_deploy.SubcloudDeployController, + '_upload_files') + def test_post_subcloud_deploy_all_input(self, mock_upload_files): + opts = [consts.DEPLOY_PLAYBOOK, consts.DEPLOY_OVERRIDES, + consts.DEPLOY_CHART, consts.DEPLOY_PRESTAGE] + fields = list() + for opt in opts: + fake_name = opt + "_fake" + fake_content = "fake content".encode('utf-8') + fields.append((opt, fake_name, fake_content)) + mock_upload_files.return_value = True + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + upload_files=fields) + self.assertEqual(response.status_code, http_client.OK) + + @mock.patch.object(subcloud_deploy.SubcloudDeployController, + '_upload_files') + def test_post_subcloud_deploy_prestage(self, mock_upload_files): + opts = [consts.DEPLOY_PRESTAGE] + fields = list() + for opt in opts: + fake_name = opt + "_fake" + fake_content = "fake content".encode('utf-8') + fields.append((opt, fake_name, fake_content)) + mock_upload_files.return_value = True + response = self.app.post(FAKE_URL, + headers=FAKE_HEADERS, + upload_files=fields) + self.assertEqual(response.status_code, http_client.OK) + @mock.patch.object(subcloud_deploy.SubcloudDeployController, '_upload_files') def test_post_subcloud_deploy_missing_file_name(self, mock_upload_files):