Introduce "openstack overcloud ceph deploy"
The new "openstack overcloud ceph deploy" is used to deploy Ceph after the hardware has been provisioned with networking and before the overcloud is deployed. The command takes the output of "openstack overcloud node provision" as input and returns a Heat enviornment file, e.g. deployed_ceph.yaml, as output. The deployed_ceph.yaml file may then be passed to the "openstack overcloud deploy" command as input. Change-Id: Ie0032190f0c07fd47a36a1915c02f0ba1a9ae2a4
This commit is contained in:
parent
63d3e637dc
commit
61e4f4c788
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new command "openstack overcloud ceph deploy" is added. The command is
|
||||
used to deploy Ceph after the hardware has been provisioned with networking
|
||||
and before the overcloud is deployed. The command takes the output of
|
||||
"openstack overcloud node provision" as input and returns a Heat enviornment
|
||||
file, e.g. deployed_ceph.yaml, as output. The deployed_ceph.yaml file may then
|
||||
be passed to the "openstack overcloud deploy" command as input. During overcloud
|
||||
deployment the Ceph cluster is then configured to host OpenStack. E.g. cephx keys
|
||||
and pools are still created on the Ceph cluster by "openstack overcloud deploy".
|
@ -42,6 +42,7 @@ openstack.tripleoclient.v2 =
|
||||
overcloud_admin_authorize = tripleoclient.v1.overcloud_admin:Authorize
|
||||
overcloud_netenv_validate = tripleoclient.v1.overcloud_netenv_validate:ValidateOvercloudNetenv
|
||||
overcloud_cell_export = tripleoclient.v1.overcloud_cell:ExportCell
|
||||
overcloud_ceph_deploy = tripleoclient.v2.overcloud_ceph:OvercloudCephDeploy
|
||||
overcloud_config_download = tripleoclient.v1.overcloud_config:DownloadConfig
|
||||
overcloud_container_image_upload = tripleoclient.v1.container_image:UploadImage
|
||||
overcloud_container_image_build = tripleoclient.v1.container_image:BuildImage
|
||||
|
@ -2295,3 +2295,41 @@ class TestProhibitedOverrides(base.TestCommand):
|
||||
environment)
|
||||
resource_registry.pop("neutron")
|
||||
self.assertIsNone(utils.check_neutron_resources(environment))
|
||||
|
||||
|
||||
class TestParseContainerImagePrepare(TestCase):
|
||||
|
||||
fake_env = {'parameter_defaults': {'ContainerImagePrepare':
|
||||
[{'push_destination': True, 'set':
|
||||
{'ceph_image': 'ceph',
|
||||
'ceph_namespace': 'quay.io:443/ceph',
|
||||
'ceph_tag': 'latest'}}],
|
||||
'ContainerImageRegistryCredentials':
|
||||
{'quay.io:443': {'quay_username':
|
||||
'quay_password'}}}}
|
||||
|
||||
def test_parse_container_image_prepare(self):
|
||||
key = 'ContainerImagePrepare'
|
||||
keys = ['ceph_namespace', 'ceph_image', 'ceph_tag']
|
||||
reg_expected = {'ceph_image': 'ceph',
|
||||
'ceph_namespace': 'quay.io:443/ceph',
|
||||
'ceph_tag': 'latest'}
|
||||
with tempfile.NamedTemporaryFile(mode='w') as cfgfile:
|
||||
yaml.safe_dump(self.fake_env, cfgfile)
|
||||
reg_actual = \
|
||||
utils.parse_container_image_prepare(key, keys,
|
||||
cfgfile.name)
|
||||
self.assertEqual(reg_actual, reg_expected)
|
||||
|
||||
def test_parse_container_image_prepare_credentials(self):
|
||||
key = 'ContainerImageRegistryCredentials'
|
||||
keys = ['quay.io:443/ceph']
|
||||
reg_expected = {'registry_url': 'quay.io:443',
|
||||
'registry_username': 'quay_username',
|
||||
'registry_password': 'quay_password'}
|
||||
with tempfile.NamedTemporaryFile(mode='w') as cfgfile:
|
||||
yaml.safe_dump(self.fake_env, cfgfile)
|
||||
reg_actual = \
|
||||
utils.parse_container_image_prepare(key, keys,
|
||||
cfgfile.name)
|
||||
self.assertEqual(reg_actual, reg_expected)
|
||||
|
0
tripleoclient/tests/v2/overcloud_ceph/__init__.py
Normal file
0
tripleoclient/tests/v2/overcloud_ceph/__init__.py
Normal file
77
tripleoclient/tests/v2/overcloud_ceph/test_overcloud_ceph.py
Normal file
77
tripleoclient/tests/v2/overcloud_ceph/test_overcloud_ceph.py
Normal file
@ -0,0 +1,77 @@
|
||||
# Copyright 2021 Red Hat, 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
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import mock
|
||||
|
||||
from osc_lib import exceptions as osc_lib_exc
|
||||
|
||||
from tripleoclient.tests import fakes
|
||||
from tripleoclient.v2 import overcloud_ceph
|
||||
|
||||
|
||||
class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudCephDeploy, self).setUp()
|
||||
|
||||
# Get the command object to test
|
||||
app_args = mock.Mock()
|
||||
app_args.verbose_level = 1
|
||||
self.app.options = fakes.FakeOptions()
|
||||
self.cmd = overcloud_ceph.OvercloudCephDeploy(self.app,
|
||||
app_args)
|
||||
|
||||
@mock.patch('tripleoclient.utils.TempDirs', autospect=True)
|
||||
@mock.patch('os.path.abspath', autospect=True)
|
||||
@mock.patch('os.path.exists', autospect=True)
|
||||
@mock.patch('tripleoclient.utils.run_ansible_playbook', autospec=True)
|
||||
def test_overcloud_deploy_ceph(self, mock_playbook, mock_abspath,
|
||||
mock_path_exists, mock_tempdirs):
|
||||
arglist = ['deployed-metal.yaml', '--yes',
|
||||
'--stack', 'overcloud',
|
||||
'--output', 'deployed-ceph.yaml',
|
||||
'--container-namespace', 'quay.io/ceph',
|
||||
'--container-image', 'ceph',
|
||||
'--container-tag', 'latest']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.cmd.take_action(parsed_args)
|
||||
mock_playbook.assert_called_once_with(
|
||||
playbook='cli-deployed-ceph.yaml',
|
||||
inventory=mock.ANY,
|
||||
workdir=mock.ANY,
|
||||
playbook_dir=mock.ANY,
|
||||
verbosity=3,
|
||||
extra_vars={
|
||||
"baremetal_deployed_path": mock.ANY,
|
||||
"deployed_ceph_tht_path": mock.ANY,
|
||||
"working_dir": mock.ANY,
|
||||
"stack_name": 'overcloud',
|
||||
'tripleo_roles_path': mock.ANY,
|
||||
'tripleo_cephadm_container_ns': 'quay.io/ceph',
|
||||
'tripleo_cephadm_container_image': 'ceph',
|
||||
'tripleo_cephadm_container_tag': 'latest',
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('os.path.abspath', autospect=True)
|
||||
@mock.patch('os.path.exists', autospect=True)
|
||||
def test_overcloud_deploy_ceph_no_overwrite(self, mock_abspath,
|
||||
mock_path_exists):
|
||||
arglist = ['deployed-metal.yaml',
|
||||
'--stack', 'overcloud',
|
||||
'--output', 'deployed-ceph.yaml']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.assertRaises(osc_lib_exc.CommandError,
|
||||
self.cmd.take_action, parsed_args)
|
@ -59,6 +59,7 @@ from six.moves import configparser
|
||||
|
||||
from heatclient import exc as hc_exc
|
||||
from six.moves.urllib import error as url_error
|
||||
from six.moves.urllib import parse as url_parse
|
||||
from six.moves.urllib import request
|
||||
|
||||
from tenacity import retry
|
||||
@ -2968,3 +2969,86 @@ def check_prohibited_overrides(protected_overrides, user_environments):
|
||||
|
||||
if found_conflict:
|
||||
raise exceptions.DeploymentError(msg)
|
||||
|
||||
|
||||
def parse_container_image_prepare(tht_key='ContainerImagePrepare',
|
||||
keys=[], source=None):
|
||||
"""Extracts key/value pairs from list of keys in source file
|
||||
If keys=[foo,bar] and source is the following,
|
||||
then return {foo: 1, bar: 2}
|
||||
|
||||
parameter_defaults:
|
||||
ContainerImagePrepare:
|
||||
- tag_from_label: grault
|
||||
set:
|
||||
foo: 1
|
||||
bar: 2
|
||||
namespace: quay.io/garply
|
||||
ContainerImageRegistryCredentials:
|
||||
'quay.io': {'quay_username': 'quay_password'}
|
||||
|
||||
Alternatively, if tht_key='ContainerImageRegistryCredentials' and
|
||||
keys=['quay.io/garply'] for the above, then return the following:
|
||||
|
||||
{'registry_url': 'quay.io',
|
||||
'registry_username': 'quay_username',
|
||||
'registry_password': 'quay_password'}
|
||||
|
||||
If the tht_key is not found, return an empty dictionary
|
||||
|
||||
:param tht_key: string of a THT parameter (only 2 options)
|
||||
:param keys: list of keys to extract
|
||||
:param source: (string) path to container_image_prepare_defaults.yaml
|
||||
|
||||
:return: dictionary
|
||||
"""
|
||||
image_map = {}
|
||||
if source is None:
|
||||
source = kolla_builder.DEFAULT_PREPARE_FILE
|
||||
if not os.path.exists(source):
|
||||
raise RuntimeError(
|
||||
"Path to container image prepare defaults file "
|
||||
"not found: %s." % os.path.abspath(source))
|
||||
with open(source, 'r') as stream:
|
||||
try:
|
||||
images = yaml.safe_load(stream)
|
||||
except yaml.YAMLError as exc:
|
||||
raise RuntimeError(
|
||||
"yaml.safe_load(%s) returned '%s'" % (source, exc))
|
||||
|
||||
if tht_key == 'ContainerImagePrepare':
|
||||
try:
|
||||
tag_list = images['parameter_defaults'][tht_key]
|
||||
for key in keys:
|
||||
for tag in tag_list:
|
||||
if 'set' in tag:
|
||||
if key in tag['set']:
|
||||
image_map[key] = tag['set'][key]
|
||||
except KeyError:
|
||||
raise RuntimeError(
|
||||
"The expected parameter_defaults and %s are not "
|
||||
"defined in data file: %s" % (tht_key, source))
|
||||
elif tht_key == 'ContainerImageRegistryCredentials':
|
||||
try:
|
||||
tag_list = images['parameter_defaults'][tht_key]
|
||||
for key in keys:
|
||||
for tag in tag_list:
|
||||
registry = url_parse.urlparse(key).netloc
|
||||
if len(registry) == 0:
|
||||
registry = url_parse.urlparse('//' + key).netloc
|
||||
if tag == registry:
|
||||
if isinstance(tag_list[registry], collections.Mapping):
|
||||
credentials = tag_list[registry].popitem()
|
||||
image_map['registry_username'] = credentials[0]
|
||||
image_map['registry_password'] = credentials[1]
|
||||
image_map['registry_url'] = registry
|
||||
except KeyError:
|
||||
LOG.info("Unable to parse %s from %s. "
|
||||
"Assuming the container registry does not "
|
||||
"require authentication or that the "
|
||||
"registry URL, username and password "
|
||||
"will be passed another way."
|
||||
% (tht_key, source))
|
||||
else:
|
||||
raise RuntimeError("Unsupported tht_key: %s" % tht_key)
|
||||
return image_map
|
||||
|
272
tripleoclient/v2/overcloud_ceph.py
Normal file
272
tripleoclient/v2/overcloud_ceph.py
Normal file
@ -0,0 +1,272 @@
|
||||
# Copyright 2021 Red Hat, 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
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from osc_lib import exceptions as oscexc
|
||||
from osc_lib.i18n import _
|
||||
from osc_lib import utils
|
||||
|
||||
from tripleoclient import command
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import utils as oooutils
|
||||
|
||||
|
||||
class OvercloudCephDeploy(command.Command):
|
||||
|
||||
log = logging.getLogger(__name__ + ".OvercloudCephDeploy")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(OvercloudCephDeploy, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('baremetal_env',
|
||||
metavar='<deployed_baremetal.yaml>',
|
||||
help=_('Path to the environment file '
|
||||
'output from "openstack '
|
||||
'overcloud node provision".'))
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
metavar='<deployed_ceph.yaml>',
|
||||
help=_('The path to the output environment '
|
||||
'file describing the Ceph deployment '
|
||||
' to pass to the overcloud deployment.'))
|
||||
parser.add_argument('-y', '--yes', default=False, action='store_true',
|
||||
help=_('Skip yes/no prompt before overwriting an '
|
||||
'existing <deployed_ceph.yaml> output file '
|
||||
'(assume yes).'))
|
||||
parser.add_argument('--stack', dest='stack',
|
||||
help=_('Name or ID of heat stack '
|
||||
'(default=Env: OVERCLOUD_STACK_NAME)'),
|
||||
default=utils.env('OVERCLOUD_STACK_NAME',
|
||||
default='overcloud'))
|
||||
parser.add_argument(
|
||||
'--working-dir', action='store',
|
||||
help=_('The working directory for the deployment where all '
|
||||
'input, output, and generated files will be stored.\n'
|
||||
'Defaults to "$HOME/overcloud-deploy/<stack>"'))
|
||||
parser.add_argument('--roles-data',
|
||||
help=_(
|
||||
"Path to an alternative roles_data.yaml. "
|
||||
"Used to decide which node gets which "
|
||||
"Ceph mon, mgr, or osd service "
|
||||
"based on the node's role in "
|
||||
"<deployed_baremetal.yaml>."),
|
||||
default=os.path.join(
|
||||
constants.TRIPLEO_HEAT_TEMPLATES,
|
||||
constants.OVERCLOUD_ROLES_FILE))
|
||||
spec_group = parser.add_mutually_exclusive_group()
|
||||
spec_group.add_argument('--ceph-spec',
|
||||
help=_(
|
||||
"Path to an existing Ceph spec file. "
|
||||
"If not provided a spec will be generated "
|
||||
"automatically based on --roles-data and "
|
||||
"<deployed_baremetal.yaml>"),
|
||||
default=None)
|
||||
spec_group.add_argument('--osd-spec',
|
||||
help=_(
|
||||
"Path to an existing OSD spec file. "
|
||||
"Mutually exclusive with --ceph-spec. "
|
||||
"If the Ceph spec file is generated "
|
||||
"automatically, then the OSD spec "
|
||||
"in the Ceph spec file defaults to "
|
||||
"{data_devices: {all: true}} "
|
||||
"for all service_type osd. "
|
||||
"Use --osd-spec to override the "
|
||||
"data_devices value inside the "
|
||||
"Ceph spec file."),
|
||||
default=None)
|
||||
parser.add_argument('--container-image-prepare',
|
||||
help=_(
|
||||
"Path to an alternative "
|
||||
"container_image_prepare_defaults.yaml. "
|
||||
"Used to control which Ceph container is "
|
||||
"pulled by cephadm via the ceph_namespace, "
|
||||
"ceph_image, and ceph_tag variables in "
|
||||
"addition to registry authentication via "
|
||||
"ContainerImageRegistryCredentials."
|
||||
),
|
||||
default=None)
|
||||
container_group = parser.add_argument_group("container-image-prepare "
|
||||
"overrides",
|
||||
"The following options "
|
||||
"may be used to override "
|
||||
"individual values "
|
||||
"set via "
|
||||
"--container-image-prepare"
|
||||
". If the example "
|
||||
"variables below were "
|
||||
"set the image would be "
|
||||
"concatenated into "
|
||||
"quay.io/ceph/ceph:latest "
|
||||
"and a custom registry "
|
||||
"login would be used."
|
||||
)
|
||||
container_group.add_argument('--container-namespace',
|
||||
required=False,
|
||||
help='e.g. quay.io/ceph')
|
||||
container_group.add_argument('--container-image',
|
||||
required=False,
|
||||
help='e.g. ceph')
|
||||
container_group.add_argument('--container-tag',
|
||||
required=False,
|
||||
help='e.g. latest')
|
||||
container_group.add_argument('--registry-url',
|
||||
required=False,
|
||||
help='')
|
||||
container_group.add_argument('--registry-username',
|
||||
required=False,
|
||||
help='')
|
||||
container_group.add_argument('--registry-password',
|
||||
required=False,
|
||||
help='')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
baremetal_env_path = os.path.abspath(parsed_args.baremetal_env)
|
||||
output_path = os.path.abspath(parsed_args.output)
|
||||
|
||||
if not os.path.exists(baremetal_env_path):
|
||||
raise oscexc.CommandError(
|
||||
"Baremetal environment file does not exist:"
|
||||
" %s" % parsed_args.baremetal_env)
|
||||
|
||||
overwrite = parsed_args.yes
|
||||
if (os.path.exists(output_path) and not overwrite
|
||||
and not oooutils.prompt_user_for_confirmation(
|
||||
'Overwrite existing file %s [y/N]?' % parsed_args.output,
|
||||
self.log)):
|
||||
raise oscexc.CommandError("Will not overwrite existing file:"
|
||||
" %s. See the --yes parameter to "
|
||||
"override this behavior. " %
|
||||
parsed_args.output)
|
||||
else:
|
||||
overwrite = True
|
||||
|
||||
if not parsed_args.working_dir:
|
||||
working_dir = oooutils.get_default_working_dir(
|
||||
parsed_args.stack)
|
||||
else:
|
||||
working_dir = os.path.abspath(parsed_args.working_dir)
|
||||
oooutils.makedirs(working_dir)
|
||||
|
||||
inventory = os.path.join(working_dir,
|
||||
constants.TRIPLEO_STATIC_INVENTORY)
|
||||
if not os.path.exists(inventory):
|
||||
raise oscexc.CommandError(
|
||||
"Inventory file not found in working directory: "
|
||||
"%s. It should have been created by "
|
||||
"'openstack overcloud node provision'."
|
||||
% inventory)
|
||||
|
||||
# mandatory extra_vars are now set, add others conditionally
|
||||
extra_vars = {
|
||||
"baremetal_deployed_path": baremetal_env_path,
|
||||
"deployed_ceph_tht_path": output_path,
|
||||
"working_dir": working_dir,
|
||||
"stack_name": parsed_args.stack,
|
||||
}
|
||||
|
||||
# optional paths to pass to playbook
|
||||
if parsed_args.roles_data:
|
||||
if not os.path.exists(parsed_args.roles_data):
|
||||
raise oscexc.CommandError(
|
||||
"Roles Data file not found --roles-data %s."
|
||||
% os.path.abspath(parsed_args.roles_data))
|
||||
else:
|
||||
extra_vars['tripleo_roles_path'] = \
|
||||
os.path.abspath(parsed_args.roles_data)
|
||||
|
||||
if parsed_args.ceph_spec:
|
||||
if not os.path.exists(parsed_args.ceph_spec):
|
||||
raise oscexc.CommandError(
|
||||
"Ceph Spec file not found --ceph-spec %s."
|
||||
% os.path.abspath(parsed_args.ceph_spec))
|
||||
else:
|
||||
extra_vars['dynamic_ceph_spec'] = False
|
||||
extra_vars['ceph_spec_path'] = \
|
||||
os.path.abspath(parsed_args.ceph_spec)
|
||||
|
||||
if parsed_args.osd_spec:
|
||||
if not os.path.exists(parsed_args.osd_spec):
|
||||
raise oscexc.CommandError(
|
||||
"OSD Spec file not found --osd-spec %s."
|
||||
% os.path.abspath(parsed_args.osd_spec))
|
||||
else:
|
||||
extra_vars['osd_spec_path'] = \
|
||||
os.path.abspath(parsed_args.osd_spec)
|
||||
|
||||
# optional container vars to pass to playbook
|
||||
keys = ['ceph_namespace', 'ceph_image', 'ceph_tag']
|
||||
key = 'ContainerImagePrepare'
|
||||
container_dict = \
|
||||
oooutils.parse_container_image_prepare(key, keys,
|
||||
parsed_args.
|
||||
container_image_prepare)
|
||||
extra_vars['tripleo_cephadm_container_ns'] = \
|
||||
parsed_args.container_namespace or \
|
||||
container_dict['ceph_namespace']
|
||||
extra_vars['tripleo_cephadm_container_image'] = \
|
||||
parsed_args.container_image or \
|
||||
container_dict['ceph_image']
|
||||
extra_vars['tripleo_cephadm_container_tag'] = \
|
||||
parsed_args.container_tag or \
|
||||
container_dict['ceph_tag']
|
||||
|
||||
# optional container registry vars to pass to playbook
|
||||
if 'tripleo_cephadm_container_ns' in extra_vars:
|
||||
keys = [extra_vars['tripleo_cephadm_container_ns']]
|
||||
key = 'ContainerImageRegistryCredentials'
|
||||
registry_dict = \
|
||||
oooutils.parse_container_image_prepare(key, keys,
|
||||
parsed_args.
|
||||
container_image_prepare)
|
||||
# It's valid for the registry_dict to be empty so
|
||||
# we cannot default to it with an 'or' like we can
|
||||
# for ceph_{namespace,image,tag} as above.
|
||||
if 'registry_url' in registry_dict:
|
||||
extra_vars['tripleo_cephadm_registry_url'] = \
|
||||
registry_dict['registry_url']
|
||||
if 'registry_password' in registry_dict:
|
||||
extra_vars['tripleo_cephadm_registry_password'] = \
|
||||
registry_dict['registry_password']
|
||||
if 'registry_username' in registry_dict:
|
||||
extra_vars['tripleo_cephadm_registry_username'] = \
|
||||
registry_dict['registry_username']
|
||||
# Whether registry vars came out of --container-image-prepare
|
||||
# or not, we need either to set them (as above) or override
|
||||
# them if they were passed via the CLI (as follows)
|
||||
if parsed_args.registry_url:
|
||||
extra_vars['tripleo_cephadm_registry_url'] = \
|
||||
parsed_args.registry_url
|
||||
if parsed_args.registry_password:
|
||||
extra_vars['tripleo_cephadm_registry_password'] = \
|
||||
parsed_args.registry_password
|
||||
if parsed_args.registry_username:
|
||||
extra_vars['tripleo_cephadm_registry_username'] = \
|
||||
parsed_args.registry_username
|
||||
|
||||
# call the playbook
|
||||
with oooutils.TempDirs() as tmp:
|
||||
oooutils.run_ansible_playbook(
|
||||
playbook='cli-deployed-ceph.yaml',
|
||||
inventory=inventory,
|
||||
workdir=tmp,
|
||||
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
verbosity=oooutils.playbook_verbosity(self=self),
|
||||
extra_vars=extra_vars,
|
||||
)
|
Loading…
Reference in New Issue
Block a user