Add the subcloud deploy config option to dcmanager

This commit adds the command "subcloud deploy config" to dcmanager.
It provides similar options as dcmanager subcloud reconfig. However,
the --deploy-config file is optional if it has been provided previously
via subcloud deploy config or subcloud deploy create command.

Test Plan:
  Success cases:
  - PASS: Bootstrap a subcloud then issue "dcmanager subcloud deploy
          config" command with --deploy-config option to apply initial
          config. Verify that the subcloud is successfully configured.
  - PASS: Create a deploy config using dcmanager subcloud deploy create
          with --deploy-config option. Install and bootstrap the
          subcloud then config the subcloud using dcmanger subcloud
          deploy config with --deploy-config option. Verify that the
          subcloud is successfully configured with config options
          provided last.
  - PASS: Bootstrap a subcloud then issue "dcmanager subcloud deploy
          config" command with --deploy-config option to apply an
          erroneous config. Verify that the subcloud fails to be
          configured. Repeat the command this time with a good config
          file and verify that the subcloud is successfully configured.
  - PASS: Apply config passing deploy_config file for a subcloud
          running a previous release (21.12) and verify that
          the subcloud was successfully configured.
  - PASS: Create a subcloud deploy with --deploy-config option,
          install and bootstrap the subcloud then issue "dcmanager
          subcloud deploy config" command without --deploy-config
          option. Verify that the subcloud is successfully configured.
  - PASS: Repeat previous tests but directly call the API (using
          CURL) instead of using the CLI.
  Failure cases:
  - PASS: Verify that it's not possible to run the config if deploy
          state is not 'complete', 'pre-config-failed', 'config-failed',
          'deploy-failed', 'bootstrapped' or in a prestaging state.
          ('deploy-failed' will be removed once 'subcloud reconfig'
           is deprecated)
  - PASS: Verify that it's not possible to run "dcmanager subcloud
          deploy config" command without providing a deploy config file
          if this has never been provided before.
  - PASS: Verify that it's not possible to run the config without
          previously uploading deploy files.
  - PASS: Verify that it's not possible to configure without
          passing the 'sysadmin-password' parameter (using CURL,
          since the CLI will prompt for the password if it's
          omited)
  - PASS: Call the API directly, passing sysadmin-password as plain
          text as opposed to b64encoded and verify that the response
          contains the correct error code and message.

Story: 2010756
Task: 48022

Signed-off-by: Victor Romano <victor.gluzromano@windriver.com>
Change-Id: I65e1cbea1879d49066c2add69cabd04e64216b8f
This commit is contained in:
Victor Romano 2023-05-16 11:10:08 -03:00
parent 32f6fc5805
commit 6b7b012992
13 changed files with 371 additions and 21 deletions

View File

@ -1673,6 +1673,7 @@ Subcloud Deploy
These APIs allow for the display and upload of the deployment manager common
files which include deploy playbook, deploy overrides, deploy helm charts, and prestage images list.
**************************
Show Subcloud Deploy Files
**************************
@ -1910,4 +1911,76 @@ Response Example
----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-post-response.json
:language: json
**********************************
Configures a subcloud
**********************************
.. rest_method:: PATCH /v1.0/phased-subcloud-deploy/{subcloud}/configure
The attributes of a subcloud which are modifiable:
- subcloud configuration (which is provided through deploy_config file)
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403), badMethod (405),
HTTPUnprocessableEntity (422), internalServerError (500),
serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- subcloud: subcloud_uri
- deploy_config: deploy_config
- sysadmin_password: sysadmin_password
Accepts Content-Type multipart/form-data
Request Example
----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-patch-configure-request.json
:language: json
**Response parameters**
.. rest_parameters:: parameters.yaml
- id: subcloud_id
- group_id: group_id
- name: subcloud_name
- description: subcloud_description
- location: subcloud_location
- software-version: software_version
- availability-status: availability_status
- error-description: error_description
- deploy-status: deploy_status
- backup-status: backup_status
- backup-datetime: backup_datetime
- openstack-installed: openstack_installed
- management-state: management_state
- systemcontroller-gateway-ip: systemcontroller_gateway_ip
- management-start-ip: management_start_ip
- management-end-ip: management_end_ip
- management-subnet: management_subnet
- management-gateway-ip: management_gateway_ip
- created-at: created_at
- updated-at: updated_at
- data_install: data_install
- data_upgrade: data_upgrade
- endpoint_sync_status: endpoint_sync_status
- sync_status: sync_status
- endpoint_type: sync_status_type
Response Example
----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-patch-configure-response.json
:language: json

View File

@ -0,0 +1,5 @@
{
"deploy_config": "content of deploy_config file",
"subcloud": "subcloud1",
"sysadmin_password": "XXXXXXX"
}

View File

@ -0,0 +1,23 @@
{
"id": 1,
"name": "subcloud1",
"created-at": "2023-01-02T03:04:05.678987",
"updated-at": "2023-04-08T15:16:23.424851",
"availability-status": "online",
"data_install": null,
"data_upgrade": null,
"deploy-status": "complete",
"backup-status": "complete",
"backup-datetime": "2023-05-02 11:23:58.132134",
"description": "Ottawa Site",
"group_id": 1,
"location": "YOW",
"management-end-ip": "192.168.101.50",
"management-gateway-ip": "192.168.101.1",
"management-start-ip": "192.168.101.2",
"management-state": "unmanaged",
"management-subnet": "192.168.101.0/24",
"openstack-installed": false,
"software-version": "21.12",
"systemcontroller-gateway-ip": "192.168.204.101"
}

View File

@ -22,6 +22,7 @@ from dcmanager.common.context import RequestContext
from dcmanager.common import exceptions
from dcmanager.common.i18n import _
from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import prestage
from dcmanager.common import utils
from dcmanager.db import api as db_api
from dcmanager.db.sqlalchemy import models
@ -46,6 +47,10 @@ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES,
)
SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (
consts.DEPLOY_CONFIG,
)
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_INSTALLED,
consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
@ -56,6 +61,16 @@ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_CREATED
]
# TODO(vgluzrom): remove deploy_failed once 'subcloud reconfig'
# has been deprecated
VALID_STATES_FOR_DEPLOY_CONFIG = (
consts.DEPLOY_STATE_DONE,
consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
consts.DEPLOY_STATE_CONFIG_FAILED,
consts.DEPLOY_STATE_DEPLOY_FAILED,
consts.DEPLOY_STATE_BOOTSTRAPPED
)
def get_create_payload(request: pecan.Request) -> dict:
payload = dict()
@ -199,6 +214,34 @@ class PhasedSubcloudDeployController(object):
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
_('Unable to bootstrap subcloud'))
def _deploy_config(self, context: RequestContext,
request: pecan.Request, subcloud):
payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS)
if not payload:
pecan.abort(400, _('Body required'))
if not (subcloud.deploy_status in VALID_STATES_FOR_DEPLOY_CONFIG or
prestage.is_deploy_status_prestage(subcloud.deploy_status)):
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_CONFIG)
pecan.abort(400, _('Subcloud deploy status must be either '
'%s or prestage-...') % allowed_states_str)
psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS)
psd_common.validate_sysadmin_password(payload)
try:
subcloud = self.dcmanager_rpc_client.subcloud_deploy_config(
context, subcloud.id, payload)
return subcloud
except RemoteError as e:
pecan.abort(422, e.value)
except Exception:
LOG.exception("Unable to configure subcloud %s" % subcloud.name)
pecan.abort(500, _('Unable to configure subcloud'))
@pecan.expose(generic=True, template='json')
def index(self):
# Route the request to specific methods with parameters
@ -238,6 +281,8 @@ class PhasedSubcloudDeployController(object):
if verb == 'bootstrap':
subcloud = self._deploy_bootstrap(context, pecan.request, subcloud)
elif verb == 'configure':
subcloud = self._deploy_config(context, pecan.request, subcloud)
else:
pecan.abort(400, _('Invalid request'))

View File

@ -30,6 +30,10 @@ phased_subcloud_deploy_rules = [
{
'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap'
},
{
'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/configure'
}
]
)

View File

@ -179,6 +179,10 @@ DEPLOY_STATE_BOOTSTRAPPING = 'bootstrapping'
DEPLOY_STATE_BOOTSTRAP_FAILED = 'bootstrap-failed'
DEPLOY_STATE_BOOTSTRAP_ABORTED = 'bootstrap-aborted'
DEPLOY_STATE_BOOTSTRAPPED = 'bootstrap-complete'
DEPLOY_STATE_PRE_CONFIG = 'pre-config'
DEPLOY_STATE_PRE_CONFIG_FAILED = 'pre-config-failed'
DEPLOY_STATE_CONFIGURING = 'configuring'
DEPLOY_STATE_CONFIG_FAILED = 'config-failed'
DEPLOY_STATE_DEPLOYING = 'deploying'
DEPLOY_STATE_DEPLOY_FAILED = 'deploy-failed'
DEPLOY_STATE_MIGRATING_DATA = 'migrating-data'

View File

@ -692,21 +692,15 @@ def upload_deploy_config_file(request, payload):
def get_config_file_path(subcloud_name, config_file_type=None):
basepath = consts.ANSIBLE_OVERRIDES_PATH
if config_file_type == consts.DEPLOY_CONFIG:
file_path = os.path.join(
consts.ANSIBLE_OVERRIDES_PATH,
subcloud_name + '_' + config_file_type + '.yml'
)
elif config_file_type == INSTALL_VALUES:
file_path = os.path.join(
consts.ANSIBLE_OVERRIDES_PATH + '/' + subcloud_name,
config_file_type + '.yml'
)
filename = f"{subcloud_name}_{config_file_type}.yml"
elif config_file_type == consts.INSTALL_VALUES:
basepath = os.path.join(basepath, subcloud_name)
filename = f'{config_file_type}.yml'
else:
file_path = os.path.join(
consts.ANSIBLE_OVERRIDES_PATH,
subcloud_name + '.yml'
)
filename = f"{subcloud_name}.yml"
file_path = os.path.join(basepath, filename)
return file_path
@ -721,18 +715,24 @@ def upload_config_file(file_item, config_file, config_type):
def get_common_deploy_files(payload, software_version):
missing_deploy_files = []
for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
# Skip the prestage_images option as it is not relevant in this
# context
# Skip the prestage_images option as it is
# not relevant in this context
if f == consts.DEPLOY_PRESTAGE:
continue
filename = None
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version)
if os.path.isdir(dir_path):
filename = utils.get_filename_by_prefix(dir_path, f + '_')
if filename is None:
pecan.abort(400, _("Missing required deploy file for %s") % f)
payload.update({f: os.path.join(dir_path, filename)})
if not filename:
missing_deploy_files.append(f)
else:
payload.update({f: os.path.join(dir_path, filename)})
if missing_deploy_files:
missing_deploy_files_str = ', '.join(missing_deploy_files)
msg = _("Missing required deploy files: %s" % missing_deploy_files_str)
pecan.abort(400, msg)
def validate_subcloud_name_availability(context, subcloud_name):
@ -794,7 +794,7 @@ def get_request_data(request: pecan.Request,
file_item = request.POST[f]
file_item.file.seek(0, os.SEEK_SET)
contents = file_item.file.read()
if subcloud.name and f == consts.DEPLOY_CONFIG:
if f == consts.DEPLOY_CONFIG:
fn = get_config_file_path(subcloud.name, f)
upload_config_file(contents, fn, f)
payload.update({f: fn})

View File

@ -206,6 +206,14 @@ class DCManagerService(service.Service):
subcloud_id,
payload)
@request_context
def subcloud_deploy_config(self, context, subcloud_id, payload):
# Configures a subcloud
LOG.info("Handling subcloud_deploy_config request for: %s" % subcloud_id)
return self.subcloud_manager.subcloud_deploy_config(context,
subcloud_id,
payload)
def _stop_rpc_server(self):
# Stop RPC connection to prevent new requests
LOG.debug(_("Attempting to stop RPC service..."))

View File

@ -108,6 +108,8 @@ TRANSITORY_STATES = {
consts.DEPLOY_STATE_PRE_INSTALL: consts.DEPLOY_STATE_PRE_INSTALL_FAILED,
consts.DEPLOY_STATE_INSTALLING: consts.DEPLOY_STATE_INSTALL_FAILED,
consts.DEPLOY_STATE_BOOTSTRAPPING: consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
consts.DEPLOY_STATE_PRE_CONFIG: consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
consts.DEPLOY_STATE_CONFIGURING: consts.DEPLOY_STATE_CONFIG_FAILED,
consts.DEPLOY_STATE_DEPLOYING: consts.DEPLOY_STATE_DEPLOY_FAILED,
consts.DEPLOY_STATE_MIGRATING_DATA: consts.DEPLOY_STATE_DATA_MIGRATION_FAILED,
consts.DEPLOY_STATE_PRE_RESTORE: consts.DEPLOY_STATE_RESTORE_PREP_FAILED,
@ -260,6 +262,7 @@ class SubcloudManager(manager.Manager):
software_version if software_version else SW_VERSION]
return apply_command
# TODO(vgluzrom): rename compose_deploy_command to compose_config_command
def compose_deploy_command(self, subcloud_name, ansible_subcloud_inventory_file, payload):
deploy_command = [
"ansible-playbook", payload[consts.DEPLOY_PLAYBOOK],
@ -974,6 +977,41 @@ class SubcloudManager(manager.Manager):
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED)
def subcloud_deploy_config(self, context, subcloud_id, payload: dict) -> dict:
"""Configure subcloud
:param context: request context object
:param payload: subcloud configuration
"""
LOG.info("Configuring subcloud %s." % subcloud_id)
subcloud = db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG)
try:
# Ansible inventory filename for the specified subcloud
ansible_subcloud_inventory_file = self._get_ansible_filename(
subcloud.name, INVENTORY_FILE_POSTFIX)
self._prepare_for_deployment(payload, subcloud.name)
deploy_command = self.compose_deploy_command(
subcloud.name,
ansible_subcloud_inventory_file,
payload)
del payload['sysadmin_password']
apply_thread = threading.Thread(
target=self.run_deploy_commands,
args=(subcloud, payload, context),
kwargs={'deploy_command': deploy_command})
apply_thread.start()
return db_api.subcloud_db_model_to_dict(subcloud)
except Exception:
LOG.exception("Failed to configure %s" % subcloud.name)
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG_FAILED)
def _subcloud_operation_notice(
self, operation, restore_subclouds, failed_subclouds,
invalid_subclouds):
@ -1623,6 +1661,9 @@ class SubcloudManager(manager.Manager):
if apply_command:
self._run_subcloud_bootstrap(context, subcloud,
apply_command, log_file)
if deploy_command:
self._run_subcloud_config(subcloud, context,
deploy_command, log_file)
except Exception as ex:
LOG.exception("run_deploy failed")
raise ex
@ -1701,6 +1742,32 @@ class SubcloudManager(manager.Manager):
LOG.info("Successfully bootstrapped %s" % subcloud.name)
def _run_subcloud_config(self, subcloud, context,
deploy_command, log_file):
# Run the custom deploy playbook
LOG.info("Starting deploy of %s" % subcloud.name)
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_CONFIGURING,
error_description=consts.ERROR_DESC_EMPTY)
try:
run_playbook(log_file, deploy_command)
except PlaybookExecutionFailed:
msg = utils.find_ansible_error_msg(
subcloud.name, log_file, consts.DEPLOY_STATE_CONFIGURING)
LOG.error(msg)
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_CONFIG_FAILED,
error_description=msg[0:consts.ERROR_DESCRIPTION_LENGTH])
return
LOG.info("Successfully deployed %s" % subcloud.name)
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_DONE,
error_description=consts.ERROR_DESC_EMPTY)
def _create_addn_hosts_dc(self, context):
"""Generate the addn_hosts_dc file for hostname/ip translation"""

View File

@ -197,6 +197,11 @@ class ManagerClient(RPCClient):
subcloud_id=subcloud_id,
payload=payload))
def subcloud_deploy_config(self, ctxt, subcloud_id, payload):
return self.call(ctxt, self.make_msg('subcloud_deploy_config',
subcloud_id=subcloud_id,
payload=payload))
class DCManagerNotifications(RPCClient):
"""DC Manager Notification interface to broadcast subcloud state changed

View File

@ -4,8 +4,10 @@
# SPDX-License-Identifier: Apache-2.0
#
import base64
import copy
import json
import mock
from os import path as os_path
import six
@ -41,7 +43,7 @@ class FakeRPCClient(object):
# Apply the TestSubcloudPost parameter validation tests to the subcloud deploy
# add endpoint as it uses the same parameter validation functions
class TestSubcloudDeployCreate(TestSubcloudPost):
API_PREFIX = '/v1.0/phased-subcloud-deploy'
API_PREFIX = FAKE_URL
RESULT_KEY = 'phased-subcloud-deploy'
def setUp(self):
@ -229,3 +231,77 @@ class TestSubcloudDeployBootstrap(testroot.DCManagerApiTest):
upload_files=[("bootstrap_values",
"bootstrap_fake_filename",
fake_content)])
class TestSubcloudDeployConfig(testroot.DCManagerApiTest):
def setUp(self):
super(TestSubcloudDeployConfig, self).setUp()
self.ctx = utils.dummy_context()
p = mock.patch.object(rpc_client, 'ManagerClient')
self.mock_rpc_client = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'populate_payload_with_pre_existing_data')
self.mock_populate_payload = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'get_request_data')
self.mock_get_request_data = p.start()
self.addCleanup(p.stop)
def test_configure_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password}
self.mock_rpc_client().subcloud_deploy_config.return_value = True
self.mock_get_request_data.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) +
'/configure',
headers=FAKE_HEADERS,
params=data)
self.mock_rpc_client().subcloud_deploy_config.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
def test_configure_subcloud_no_body(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
# Pass an empty request body
data = {}
self.mock_rpc_client().subcloud_deploy_config.return_value = True
self.mock_get_request_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/configure',
headers=FAKE_HEADERS, params=data)
def test_configure_subcloud_bad_password(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
# Pass a sysadmin_password which is not base64 encoded
data = {'sysadmin_password': 'not_base64'}
self.mock_rpc_client().subcloud_deploy_config.return_value = True
self.mock_get_request_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/configure',
headers=FAKE_HEADERS, params=data)
def test_configure_invalid_deploy_status(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_BOOTSTRAP_FAILED)
fake_password = base64.b64encode('testpass'.encode("utf-8")).decode("utf-8")
data = {'sysadmin_password': fake_password}
self.mock_rpc_client().subcloud_deploy_config.return_value = True
self.mock_get_request_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/configure',
headers=FAKE_HEADERS, params=data)

View File

@ -21,6 +21,7 @@ import webtest
from dcmanager.api.controllers.v1 import subcloud_deploy
from dcmanager.common import consts
from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import utils as dutils
from dcmanager.tests.unit.api import test_root_controller as testroot
from dcmanager.tests import utils
@ -245,3 +246,16 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
response.json['subcloud_deploy'][consts.DEPLOY_CHART])
self.assertEqual(None,
response.json['subcloud_deploy'][consts.DEPLOY_PRESTAGE])
def test_get_config_file_path(self):
bootstrap_file = psd_common.get_config_file_path("subcloud1")
install_values = psd_common.get_config_file_path("subcloud1",
consts.INSTALL_VALUES)
deploy_config = psd_common.get_config_file_path("subcloud1",
consts.DEPLOY_CONFIG)
self.assertEqual(bootstrap_file,
"/var/opt/dc/ansible/subcloud1.yml")
self.assertEqual(install_values,
"/var/opt/dc/ansible/subcloud1/install_values.yml")
self.assertEqual(deploy_config,
"/var/opt/dc/ansible/subcloud1_deploy_config.yml")

View File

@ -557,6 +557,29 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertEqual(consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED,
updated_subcloud.deploy_status)
@mock.patch.object(subcloud_manager.SubcloudManager,
'_prepare_for_deployment')
@mock.patch.object(threading.Thread,
'start')
def test_configure_subcloud(self, mock_thread_start,
mock_prepare_for_deployment):
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG)
fake_payload = {"sysadmin_password": "testpass",
"deploy_playbook": "test_playbook.yaml",
"deploy_overrides": "test_overrides.yaml",
"deploy_chart": "test_chart.yaml",
"deploy_config": "subcloud1.yaml"}
sm = subcloud_manager.SubcloudManager()
sm.subcloud_deploy_config(self.ctx,
subcloud.id,
payload=fake_payload)
mock_thread_start.assert_called_once()
mock_prepare_for_deployment.assert_called_once()
@mock.patch.object(subcloud_manager.SubcloudManager,
'compose_apply_command')
@mock.patch.object(subcloud_manager.SubcloudManager,
@ -2157,12 +2180,14 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertTrue('Subcloud does not exist'
in str(e))
@mock.patch.object(os_path, 'isdir')
@mock.patch.object(os_path, 'exists')
@mock.patch.object(cutils, 'get_filename_by_prefix')
@mock.patch.object(prestage, '_run_ansible')
def test_prestage_remote_pass(self, mock_run_ansible,
mock_get_filename_by_prefix,
mock_file_exists):
mock_file_exists,
mock_isdir):
values = copy.copy(FAKE_PRESTAGE_PAYLOAD)
subcloud = self.create_subcloud_static(self.ctx,
@ -2174,6 +2199,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_run_ansible.return_value = None
mock_get_filename_by_prefix.return_value = 'prestage_images_list.txt'
mock_file_exists.return_value = True
mock_isdir.return_value = True
# Verify that subcloud has the correct deploy status
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)