From e96710bfc9892ab6b4f8b160f58c35b4dbabb9bd Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Thu, 11 Jul 2019 10:57:48 -0400 Subject: [PATCH] Zero Touch Provisioning changes for subcloud configuration - Adding deploy field to subcloud entity - removing generate config option - modifying subcloud add to take a yaml file instead of individual parameters Depends-On: https://review.opendev.org/#/c/670328/ Change-Id: I3160fb65dde7c4d514246fcf413e8b341b9c75a8 Story: 2004766 Task: 35756 Signed-off-by: Tyler Smith --- dcmanagerclient/api/v1/subcloud_manager.py | 21 +- .../commands/v1/subcloud_manager.py | 271 ++++-------------- dcmanagerclient/shell.py | 1 - .../tests/v1/test_subcloud_manager.py | 109 +++---- 4 files changed, 103 insertions(+), 299 deletions(-) diff --git a/dcmanagerclient/api/v1/subcloud_manager.py b/dcmanagerclient/api/v1/subcloud_manager.py index d7c87f9..215c615 100644 --- a/dcmanagerclient/api/v1/subcloud_manager.py +++ b/dcmanagerclient/api/v1/subcloud_manager.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Copyright (c) 2017 Wind River Systems, Inc. +# Copyright (c) 2017-2019 Wind River Systems, Inc. # # The right to copy, distribute, modify, or otherwise make use # of this software may be licensed only pursuant to the terms @@ -31,6 +31,7 @@ class Subcloud(base.Resource): def __init__(self, manager, subcloud_id, name, description, location, software_version, management_state, availability_status, + deploy_status, management_subnet, management_start_ip, management_end_ip, management_gateway_ip, systemcontroller_gateway_ip, created_at, updated_at, sync_status="unknown", @@ -44,6 +45,7 @@ class Subcloud(base.Resource): self.management_subnet = management_subnet self.management_state = management_state self.availability_status = availability_status + self.deploy_status = deploy_status self.management_start_ip = management_start_ip self.management_end_ip = management_end_ip self.management_gateway_ip = management_gateway_ip @@ -74,6 +76,7 @@ class subcloud_manager(base.ResourceManager): software_version=json_object['software-version'], management_state=json_object['management-state'], availability_status=json_object['availability-status'], + deploy_status=json_object['deploy-status'], management_subnet=json_object['management-subnet'], management_start_ip=json_object['management-start-ip'], management_end_ip=json_object['management-end-ip'], @@ -101,6 +104,7 @@ class subcloud_manager(base.ResourceManager): software_version=json_object['software-version'], management_state=json_object['management-state'], availability_status=json_object['availability-status'], + deploy_status=json_object['deploy-status'], management_subnet=json_object['management-subnet'], management_start_ip=json_object['management-start-ip'], management_end_ip=json_object['management-end-ip'], @@ -129,6 +133,7 @@ class subcloud_manager(base.ResourceManager): software_version=json_object['software-version'], management_state=json_object['management-state'], availability_status=json_object['availability-status'], + deploy_status=json_object['deploy-status'], management_subnet=json_object['management-subnet'], management_start_ip=json_object['management-start-ip'], management_end_ip=json_object['management-end-ip'], @@ -157,6 +162,7 @@ class subcloud_manager(base.ResourceManager): software_version=json_object['software-version'], management_state=json_object['management-state'], availability_status=json_object['availability-status'], + deploy_status=json_object['deploy-status'], management_subnet=json_object['management-subnet'], management_start_ip=json_object['management-start-ip'], management_end_ip=json_object['management-end-ip'], @@ -168,14 +174,6 @@ class subcloud_manager(base.ResourceManager): endpoint_sync_status=json_object['endpoint_sync_status'])) return resource - def subcloud_generate_config(self, url, data): - data = json.dumps(data) - resp = self.http_client.post(url, data) - if resp.status_code != 200: - self._raise_api_exception(resp) - json_object = get_json(resp) - return json_object['config'] - def add_subcloud(self, **kwargs): data = kwargs url = '/subclouds/' @@ -197,8 +195,3 @@ class subcloud_manager(base.ResourceManager): data = kwargs url = '/subclouds/%s' % subcloud_ref return self.subcloud_update(url, data) - - def generate_config_subcloud(self, subcloud_ref, **kwargs): - data = kwargs - url = '/subclouds/%s/config' % subcloud_ref - return self.subcloud_generate_config(url, data) diff --git a/dcmanagerclient/commands/v1/subcloud_manager.py b/dcmanagerclient/commands/v1/subcloud_manager.py index 67c31f7..84a5cc6 100644 --- a/dcmanagerclient/commands/v1/subcloud_manager.py +++ b/dcmanagerclient/commands/v1/subcloud_manager.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Copyright (c) 2017 Wind River Systems, Inc. +# Copyright (c) 2017-2019 Wind River Systems, Inc. # # The right to copy, distribute, modify, or otherwise make use # of this software may be licensed only pursuant to the terms # of an applicable Wind River license agreement. # +import getpass +import os +import yaml + from osc_lib.command import command from dcmanagerclient.commands.v1 import base @@ -31,6 +35,7 @@ def format(subcloud=None): 'name', 'management', 'availability', + 'deploy status', 'sync' ) @@ -40,6 +45,7 @@ def format(subcloud=None): subcloud.name, subcloud.management_state, subcloud.availability_status, + subcloud.deploy_status, subcloud.sync_status ) @@ -58,6 +64,7 @@ def detail_format(subcloud=None): 'software_version', 'management', 'availability', + 'deploy_status', 'management_subnet', 'management_start_ip', 'management_end_ip', @@ -76,6 +83,7 @@ def detail_format(subcloud=None): subcloud.software_version, subcloud.management_state, subcloud.availability_status, + subcloud.deploy_status, subcloud.management_subnet, subcloud.management_start_ip, subcloud.management_end_ip, @@ -108,51 +116,21 @@ class AddSubcloud(base.DCManagerShowOne): parser = super(AddSubcloud, self).get_parser(parsed_args) parser.add_argument( - '--name', + '--bootstrap-address', required=True, - help='Name of subcloud.' + help='IP address for initial subcloud controller.' ) parser.add_argument( - '--description', + '--bootstrap-values', + required=True, + help='YAML file containing subcloud configuration settings.' + ) + + parser.add_argument( + '--subcloud-password', required=False, - help='Description of subcloud.' - ) - - parser.add_argument( - '--location', - required=False, - help='Location of subcloud.' - ) - - parser.add_argument( - '--management-subnet', - required=True, - help='Management subnet for subcloud in CIDR format.' - ) - - parser.add_argument( - '--management-start-ip', - required=True, - help='Start of management IP address range for subcloud' - ) - - parser.add_argument( - '--management-end-ip', - required=True, - help='End of management IP address range for subcloud', - ) - - parser.add_argument( - '--management-gateway-ip', - required=True, - help='Management gateway IP for subcloud', - ) - - parser.add_argument( - '--systemcontroller-gateway-ip', - required=True, - help='Central gateway IP', + help='sysadmin password of the subcloud to be configured.' ) return parser @@ -160,17 +138,39 @@ class AddSubcloud(base.DCManagerShowOne): def _get_resources(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_manager kwargs = dict() - kwargs['name'] = parsed_args.name - if parsed_args.description: - kwargs['description'] = parsed_args.description - if parsed_args.location: - kwargs['location'] = parsed_args.location - kwargs['management-subnet'] = parsed_args.management_subnet - kwargs['management-start-ip'] = parsed_args.management_start_ip - kwargs['management-end-ip'] = parsed_args.management_end_ip - kwargs['management-gateway-ip'] = parsed_args.management_gateway_ip - kwargs['systemcontroller-gateway-ip'] = \ - parsed_args.systemcontroller_gateway_ip + kwargs['bootstrap-address'] = parsed_args.bootstrap_address + + # Load the configuration from the yaml file + filename = parsed_args.bootstrap_values + if os.path.isdir(filename): + error_msg = "Error: %s is a directory." % filename + raise exceptions.DCManagerClientException(error_msg) + try: + with open(filename, 'rb') as stream: + kwargs.update(yaml.safe_load(stream)) + except Exception: + error_msg = "Error: Could not open file %s." % filename + raise exceptions.DCManagerClientException(error_msg) + + # Prompt the user for the subcloud's password if it isn't provided + if parsed_args.subcloud_password is not None: + kwargs['subcloud_password'] = parsed_args.subcloud_password + else: + while True: + password = getpass.getpass( + "Enter the sysadmin password for the subcloud: ") + if len(password) < 1: + print("Password cannot be empty") + continue + + confirm = getpass.getpass( + "Re-enter sysadmin password to confirm: ") + if password != confirm: + print("Passwords did not match") + continue + kwargs["subcloud_password"] = password + break + return dcmanager_client.subcloud_manager.add_subcloud(**kwargs) @@ -339,170 +339,3 @@ class UpdateSubcloud(base.DCManagerShowOne): print(e) error_msg = "Unable to update subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) - - -class GenerateConfigSubcloud(command.Command): - """Generate configuration for a subcloud.""" - - def get_parser(self, prog_name): - parser = super(GenerateConfigSubcloud, self).get_parser(prog_name) - - parser.add_argument( - 'subcloud', - help='Name or ID of the subcloud to generate config.' - ) - - parser.add_argument( - '--pxe-subnet', - required=False, - help='PXE boot subnet for subcloud in CIDR format.' - ) - - parser.add_argument( - '--management-vlan', - required=False, - help='VLAN for subcloud management network.' - ) - - parser.add_argument( - '--management-interface-port', - required=False, - help='Subcloud management interface port.' - ) - - parser.add_argument( - '--management-interface-mtu', - required=False, - help='Subcloud management interface mtu.' - ) - - parser.add_argument( - '--cluster-vlan', - required=False, - help='VLAN for subcloud cluster network.' - ) - - parser.add_argument( - '--cluster-interface-port', - required=False, - help='Subcloud cluster interface port.' - ) - - parser.add_argument( - '--cluster-interface-mtu', - required=False, - help='Subcloud cluster interface mtu.' - ) - - parser.add_argument( - '--cluster-subnet', - required=False, - help='Cluster subnet for subcloud in CIDR format.' - ) - - parser.add_argument( - '--oam-subnet', - required=False, - help='OAM subnet for subcloud in CIDR format.' - ) - - parser.add_argument( - '--oam-gateway-ip', - required=False, - help='OAM gateway IP for subcloud.' - ) - - parser.add_argument( - '--oam-floating-ip', - required=False, - help='OAM floating IP address for subcloud.' - ) - - parser.add_argument( - '--oam-unit-0-ip', - required=False, - help='OAM unit 0 IP address for subcloud.' - ) - - parser.add_argument( - '--oam-unit-1-ip', - required=False, - help='OAM unit 1 IP address for subcloud.' - ) - - parser.add_argument( - '--oam-interface-port', - required=False, - help='Subcloud OAM interface port.' - ) - - parser.add_argument( - '--oam-interface-mtu', - required=False, - help='Subcloud OAM interface mtu.' - ) - - parser.add_argument( - '--system-mode', - required=False, - help='System mode', - choices=['simplex', 'duplex', 'duplex-direct'] - ) - - return parser - - def take_action(self, parsed_args): - subcloud_ref = parsed_args.subcloud - dcmanager_client = self.app.client_manager.subcloud_manager - - kwargs = dict() - if parsed_args.pxe_subnet: - kwargs['pxe-subnet'] = \ - parsed_args.pxe_subnet - if parsed_args.management_vlan: - kwargs['management-vlan'] = \ - parsed_args.management_vlan - if parsed_args.management_interface_port: - kwargs['management-interface-port'] = \ - parsed_args.management_interface_port - if parsed_args.management_interface_mtu: - kwargs['management-interface-mtu'] = \ - parsed_args.management_interface_mtu - if parsed_args.cluster_vlan: - kwargs['cluster-vlan'] = \ - parsed_args.cluster_vlan - if parsed_args.cluster_interface_port: - kwargs['cluster-interface-port'] = \ - parsed_args.cluster_interface_port - if parsed_args.cluster_interface_mtu: - kwargs['cluster-interface-mtu'] = \ - parsed_args.cluster_interface_mtu - if parsed_args.cluster_subnet: - kwargs['cluster-subnet'] = parsed_args.cluster_subnet - if parsed_args.oam_subnet: - kwargs['oam-subnet'] = parsed_args.oam_subnet - if parsed_args.oam_gateway_ip: - kwargs['oam-gateway-ip'] = parsed_args.oam_gateway_ip - if parsed_args.oam_floating_ip: - kwargs['oam-floating-ip'] = parsed_args.oam_floating_ip - if parsed_args.oam_unit_0_ip: - kwargs['oam-unit-0-ip'] = parsed_args.oam_unit_0_ip - if parsed_args.oam_unit_1_ip: - kwargs['oam-unit-1-ip'] = parsed_args.oam_unit_1_ip - if parsed_args.oam_interface_port: - kwargs['oam-interface-port'] = parsed_args.oam_interface_port - if parsed_args.oam_interface_mtu: - kwargs['oam-interface-mtu'] = parsed_args.oam_interface_mtu - if parsed_args.system_mode: - kwargs['system-mode'] = parsed_args.system_mode - - try: - subcloud_config = dcmanager_client.subcloud_manager.\ - generate_config_subcloud(subcloud_ref, **kwargs) - return subcloud_config - - except Exception as e: - print(e) - error_msg = "Unable to generate config for subcloud %s" % \ - (subcloud_ref) - raise exceptions.DCManagerClientException(error_msg) diff --git a/dcmanagerclient/shell.py b/dcmanagerclient/shell.py index 2ac0300..9629496 100644 --- a/dcmanagerclient/shell.py +++ b/dcmanagerclient/shell.py @@ -480,7 +480,6 @@ class DCManagerShell(app.App): 'subcloud unmanage': sm.UnmanageSubcloud, 'subcloud manage': sm.ManageSubcloud, 'subcloud update': sm.UpdateSubcloud, - 'subcloud generate-config': sm.GenerateConfigSubcloud, 'alarm summary': am.ListAlarmSummary, 'patch-strategy create': sum.CreatePatchStrategy, 'patch-strategy delete': sum.DeletePatchStrategy, diff --git a/dcmanagerclient/tests/v1/test_subcloud_manager.py b/dcmanagerclient/tests/v1/test_subcloud_manager.py index 26a3dbf..3af5864 100644 --- a/dcmanagerclient/tests/v1/test_subcloud_manager.py +++ b/dcmanagerclient/tests/v1/test_subcloud_manager.py @@ -19,8 +19,10 @@ # of an applicable Wind River license agreement. # -import copy import mock +import os +import tempfile +import yaml from oslo_utils import timeutils @@ -28,20 +30,26 @@ from dcmanagerclient.api.v1 import subcloud_manager as sm from dcmanagerclient.commands.v1 import subcloud_manager as subcloud_cmd from dcmanagerclient.tests import base +BOOTSTRAP_ADDRESS = '10.10.10.12' TIME_NOW = timeutils.utcnow().isoformat() ID = '1' ID_1 = '2' NAME = 'subcloud1' +SYSTEM_MODE = "duplex" DESCRIPTION = 'subcloud1 description' LOCATION = 'subcloud1 location' SOFTWARE_VERSION = '12.34' MANAGEMENT_STATE = 'unmanaged' AVAILABILITY_STATUS = 'offline' +DEPLOY_STATUS = 'not-deployed' MANAGEMENT_SUBNET = '192.168.101.0/24' MANAGEMENT_START_IP = '192.168.101.2' MANAGEMENT_END_IP = '192.168.101.50' MANAGEMENT_GATEWAY_IP = '192.168.101.1' SYSTEMCONTROLLER_GATEWAY_IP = '192.168.204.101' +EXTERNAL_OAM_SUBNET = "10.10.10.0/24" +EXTERNAL_OAM_GATEWAY_ADDRESS = "10.10.10.1" +EXTERNAL_OAM_FLOATING_ADDRESS = "10.10.10.12" SUBCLOUD_DICT = { 'SUBCLOUD_ID': ID, @@ -51,6 +59,7 @@ SUBCLOUD_DICT = { 'SOFTWARE_VERSION': SOFTWARE_VERSION, 'MANAGEMENT_STATE': MANAGEMENT_STATE, 'AVAILABILITY_STATUS': AVAILABILITY_STATUS, + 'DEPLOY_STATUS': DEPLOY_STATUS, 'MANAGEMENT_SUBNET': MANAGEMENT_SUBNET, 'MANAGEMENT_START_IP': MANAGEMENT_START_IP, 'MANAGEMENT_END_IP': MANAGEMENT_END_IP, @@ -69,6 +78,7 @@ SUBCLOUD = sm.Subcloud( software_version=SUBCLOUD_DICT['SOFTWARE_VERSION'], management_state=SUBCLOUD_DICT['MANAGEMENT_STATE'], availability_status=SUBCLOUD_DICT['AVAILABILITY_STATUS'], + deploy_status=SUBCLOUD_DICT['DEPLOY_STATUS'], management_subnet=SUBCLOUD_DICT['MANAGEMENT_SUBNET'], management_start_ip=SUBCLOUD_DICT['MANAGEMENT_START_IP'], management_end_ip=SUBCLOUD_DICT['MANAGEMENT_END_IP'], @@ -84,13 +94,13 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): self.client.subcloud_manager.list_subclouds.return_value = [SUBCLOUD] actual_call = self.call(subcloud_cmd.ListSubcloud) self.assertEqual([(ID, NAME, MANAGEMENT_STATE, AVAILABILITY_STATUS, - "unknown")], + DEPLOY_STATUS, "unknown")], actual_call[1]) def test_negative_list_subclouds(self): self.client.subcloud_manager.list_subclouds.return_value = [] actual_call = self.call(subcloud_cmd.ListSubcloud) - self.assertEqual((('', '', '', '', + self.assertEqual((('', '', '', '', '', ''),), actual_call[1]) @@ -113,6 +123,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): SOFTWARE_VERSION, MANAGEMENT_STATE, AVAILABILITY_STATUS, + DEPLOY_STATUS, MANAGEMENT_SUBNET, MANAGEMENT_START_IP, MANAGEMENT_END_IP, @@ -127,63 +138,43 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): self.assertEqual((('', '', '', '', '', '', '', '', '', '', '', '', - '', ''),), + '', '', ''),), actual_call[1]) - def test_add_subcloud(self): + @mock.patch('getpass.getpass', return_value='testpassword') + def test_add_subcloud(self, getpass): self.client.subcloud_manager.add_subcloud.\ return_value = [SUBCLOUD] - actual_call = self.call( - subcloud_cmd.AddSubcloud, app_args=[ - '--name', NAME, - '--description', DESCRIPTION, - '--location', LOCATION, - '--management-subnet', MANAGEMENT_SUBNET, - '--management-start-ip', MANAGEMENT_START_IP, - '--management-end-ip', MANAGEMENT_END_IP, - '--management-gateway-ip', MANAGEMENT_GATEWAY_IP, - '--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP]) + + values = { + "system_mode": SYSTEM_MODE, + "name": NAME, + "description": DESCRIPTION, + "location": LOCATION, + "management_subnet": MANAGEMENT_SUBNET, + "management_start_address": MANAGEMENT_START_IP, + "management_end_address": MANAGEMENT_END_IP, + "management_gateway_address": MANAGEMENT_GATEWAY_IP, + "external_oam_subnet": EXTERNAL_OAM_SUBNET, + "external_oam_gateway_address": EXTERNAL_OAM_GATEWAY_ADDRESS, + "external_oam_floating_address": EXTERNAL_OAM_FLOATING_ADDRESS, + } + + with tempfile.NamedTemporaryFile() as f: + yaml.dump(values, f) + file_path = os.path.abspath(f.name) + actual_call = self.call( + subcloud_cmd.AddSubcloud, app_args=[ + '--bootstrap-address', BOOTSTRAP_ADDRESS, + '--bootstrap-values', file_path, + ]) self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION, - MANAGEMENT_STATE, AVAILABILITY_STATUS, + MANAGEMENT_STATE, AVAILABILITY_STATUS, DEPLOY_STATUS, MANAGEMENT_SUBNET, MANAGEMENT_START_IP, MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP, SYSTEMCONTROLLER_GATEWAY_IP, TIME_NOW, TIME_NOW), actual_call[1]) - def test_add_subcloud_no_optional_parameters(self): - subcloud = copy.copy(SUBCLOUD) - subcloud.description = '' - subcloud.location = '' - self.client.subcloud_manager.add_subcloud.\ - return_value = [subcloud] - actual_call = self.call( - subcloud_cmd.AddSubcloud, app_args=[ - '--name', NAME, - '--management-subnet', MANAGEMENT_SUBNET, - '--management-start-ip', MANAGEMENT_START_IP, - '--management-end-ip', MANAGEMENT_END_IP, - '--management-gateway-ip', MANAGEMENT_GATEWAY_IP, - '--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP]) - self.assertEqual((ID, NAME, '', '', SOFTWARE_VERSION, - MANAGEMENT_STATE, AVAILABILITY_STATUS, - MANAGEMENT_SUBNET, MANAGEMENT_START_IP, - MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP, - SYSTEMCONTROLLER_GATEWAY_IP, - TIME_NOW, TIME_NOW), actual_call[1]) - - def test_add_subcloud_without_name(self): - self.client.subcloud_manager.add_subcloud.\ - return_value = [SUBCLOUD] - self.assertRaises( - SystemExit, self.call, subcloud_cmd.AddSubcloud, app_args=[ - '--description', DESCRIPTION, - '--location', LOCATION, - '--management-subnet', MANAGEMENT_SUBNET, - '--management-start-ip', MANAGEMENT_START_IP, - '--management-end-ip', MANAGEMENT_END_IP, - '--management-gateway-ip', MANAGEMENT_GATEWAY_IP, - '--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP]) - def test_unmanage_subcloud(self): self.client.subcloud_manager.update_subcloud.\ return_value = [SUBCLOUD] @@ -192,7 +183,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION, MANAGEMENT_STATE, - AVAILABILITY_STATUS, + AVAILABILITY_STATUS, DEPLOY_STATUS, MANAGEMENT_SUBNET, MANAGEMENT_START_IP, MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP, SYSTEMCONTROLLER_GATEWAY_IP, @@ -210,7 +201,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION, MANAGEMENT_STATE, - AVAILABILITY_STATUS, + AVAILABILITY_STATUS, DEPLOY_STATUS, MANAGEMENT_SUBNET, MANAGEMENT_START_IP, MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP, SYSTEMCONTROLLER_GATEWAY_IP, @@ -231,20 +222,8 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest): self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION, MANAGEMENT_STATE, - AVAILABILITY_STATUS, + AVAILABILITY_STATUS, DEPLOY_STATUS, MANAGEMENT_SUBNET, MANAGEMENT_START_IP, MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP, SYSTEMCONTROLLER_GATEWAY_IP, TIME_NOW, TIME_NOW), actual_call[1]) - - def test_generate_config_subcloud(self): - FAKE_CONFIG = "This is a fake config file." - self.client.subcloud_manager.generate_config_subcloud.\ - return_value = FAKE_CONFIG - actual_call = self.call( - subcloud_cmd.GenerateConfigSubcloud, app_args=[ID]) - self.assertEqual(FAKE_CONFIG, actual_call) - - def test_generate_config_subcloud_without_subcloud_id(self): - self.assertRaises(SystemExit, self.call, - subcloud_cmd.GenerateConfigSubcloud, app_args=[])