Merge "Squash standalone deployed_ceph for pythonclient to Wallaby" into stable/wallaby

This commit is contained in:
Zuul 2022-06-30 23:21:40 +00:00 committed by Gerrit Code Review
commit 0240f5360c
5 changed files with 406 additions and 19 deletions

View File

@ -0,0 +1,14 @@
---
features:
- |
New command "openstack overcloud ceph spec" has been added. This command
may be used to create a cephadm spec file as a function of the output of
metalsmith and a TripleO roles file. For example, if metalsmith output a
file with multiple hosts of differing roles and each role contained various
Ceph services, then a cephadm spec file could parse these files and return
input compatible with cephadm. The ceph spec file may be then be passed to
"openstack overcloud ceph deploy" so that cephadm deploys only those Ceph
services on those hosts. This feature should save users from the need to
create two different files containing much of the same data and make it
easier and less error prone to include Ceph in a deployment without the
need to manually create the Ceph spec file.

View File

@ -43,6 +43,7 @@ openstack.tripleoclient.v2 =
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_ceph_spec = tripleoclient.v2.overcloud_ceph:OvercloudCephSpec
overcloud_ceph_user_disable = tripleoclient.v2.overcloud_ceph:OvercloudCephUserDisable
overcloud_ceph_user_enable = tripleoclient.v2.overcloud_ceph:OvercloudCephUserEnable
overcloud_container_image_upload = tripleoclient.v1.container_image:UploadImage

View File

@ -45,6 +45,7 @@ class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
'--stack', 'overcloud',
'--skip-user-create',
'--skip-hosts-config',
'--mon-ip', '127.0.0.1',
'--cephadm-ssh-user', 'jimmy',
'--output', 'deployed-ceph.yaml',
'--container-namespace', 'quay.io/ceph',
@ -66,8 +67,10 @@ class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
"deployed_ceph_tht_path": mock.ANY,
"working_dir": mock.ANY,
"stack_name": 'overcloud',
"tripleo_cephadm_standalone": False,
'tripleo_cephadm_ssh_user': 'jimmy',
'tripleo_cephadm_cluster': 'ceph',
'tripleo_cephadm_first_mon_ip': '127.0.0.1',
'tripleo_roles_path': mock.ANY,
'tripleo_cephadm_container_ns': 'quay.io/ceph',
'tripleo_cephadm_container_image': 'ceph',
@ -75,6 +78,53 @@ class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
}
)
@mock.patch('tripleoclient.utils.get_ceph_networks', autospect=True)
@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_deploy_ceph_spec(self, mock_playbook, mock_abspath,
mock_path_exists, mock_tempdirs,
mock_get_ceph_networks):
arglist = ['--yes',
'--stack', 'overcloud',
'--skip-user-create',
'--skip-hosts-config',
'--mon-ip', '127.0.0.1',
'--ceph-spec', 'ceph_spec.yaml',
'--cephadm-ssh-user', 'jimmy',
'--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,
skip_tags='cephadm_ssh_user',
reproduce_command=False,
extra_vars_file=mock.ANY,
extra_vars={
"deployed_ceph_tht_path": mock.ANY,
"working_dir": mock.ANY,
"stack_name": 'overcloud',
"tripleo_cephadm_standalone": False,
'tripleo_roles_path': mock.ANY,
'tripleo_cephadm_first_mon_ip': '127.0.0.1',
'tripleo_cephadm_cluster': 'ceph',
'dynamic_ceph_spec': False,
'ceph_spec_path': mock.ANY,
'tripleo_cephadm_container_ns': 'quay.io/ceph',
'tripleo_cephadm_container_image': 'ceph',
'tripleo_cephadm_container_tag': 'latest',
'tripleo_cephadm_ssh_user': 'jimmy',
}
)
@mock.patch('os.path.abspath', autospect=True)
@mock.patch('os.path.exists', autospect=True)
def test_overcloud_deploy_ceph_no_overwrite(self, mock_abspath,
@ -104,6 +154,16 @@ class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
self.assertRaises(osc_lib_exc.CommandError,
self.cmd.take_action, parsed_args)
@mock.patch('os.path.abspath', autospect=True)
@mock.patch('os.path.exists', autospect=True)
def test_overcloud_deploy_ceph_no_metal(self, mock_abspath,
mock_path_exists):
arglist = ['--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)
class TestOvercloudCephUserDisable(fakes.FakePlaybookExecution):
def setUp(self):
@ -265,3 +325,45 @@ class TestOvercloudCephUserEnable(fakes.FakePlaybookExecution):
"tripleo_cephadm_action": 'enable'
}
)
class TestOvercloudCephSpec(fakes.FakePlaybookExecution):
def setUp(self):
super(TestOvercloudCephSpec, 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.OvercloudCephSpec(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_ceph_spec(self, mock_playbook, mock_abspath,
mock_path_exists, mock_tempdirs):
arglist = ['deployed-metal.yaml', '--yes',
'--stack', 'overcloud',
'--roles-data', 'roles_data.yaml',
'--osd-spec', 'osd_spec.yaml',
'--output', 'ceph_spec.yaml']
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,
tags='ceph_spec',
reproduce_command=False,
extra_vars={
"baremetal_deployed_path": mock.ANY,
'tripleo_roles_path': mock.ANY,
'osd_spec_path': mock.ANY,
'ceph_spec_path': mock.ANY,
}
)

View File

@ -3226,6 +3226,29 @@ def get_host_groups_from_ceph_spec(ceph_spec_path, prefix='',
return hosts
def standalone_ceph_inventory(working_dir):
"""return an ansible inventory for deployed ceph standalone
:param working_dir: directory where inventory should be written
:return string: the path to the inventory
"""
host = get_hostname()
inv = \
{'Standalone':
{'hosts': {host: {},
'undercloud': {}},
'vars': {'ansible_connection': 'local',
'ansible_host': host,
'ansible_python_interpreter': sys.executable}},
'allovercloud':
{'children': {'Standalone': {}}}}
path = os.path.join(working_dir,
constants.TRIPLEO_STATIC_INVENTORY)
with open(path, 'w') as f:
f.write(yaml.safe_dump(inv))
return path
def cleanup_host_entry(entry):
# remove any tab or space excess
entry_stripped = re.sub('[ \t]+', ' ', str(entry).rstrip())

View File

@ -79,11 +79,13 @@ class OvercloudCephDeploy(command.Command):
def get_parser(self, prog_name):
parser = super(OvercloudCephDeploy, self).get_parser(prog_name)
parser.add_argument('baremetal_env',
parser.add_argument('baremetal_env', nargs='?',
metavar='<deployed_baremetal.yaml>',
help=_('Path to the environment file '
'output from "openstack '
'overcloud node provision".'))
'overcloud node provision". '
'This argument may be excluded '
'only if --ceph-spec is used.'))
parser.add_argument('-o', '--output', required=True,
metavar='<deployed_ceph.yaml>',
help=_('The path to the output environment '
@ -176,6 +178,14 @@ class OvercloudCephDeploy(command.Command):
"'ceph --cluster foo health' unless export "
"CEPH_ARGS='--cluster foo' is used."),
default='ceph')
parser.add_argument('--mon-ip',
help=_(
"IP address of the first Ceph monitor. "
"If not set, an IP from the Ceph "
"public_network of a server with the "
"mon label from the Ceph spec is used. "
"IP must already be active on server."),
default='')
parser.add_argument('--config',
help=_(
"Path to an existing ceph.conf with settings "
@ -247,10 +257,12 @@ class OvercloudCephDeploy(command.Command):
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 "
"Path to an existing Ceph spec file. If "
"not provided a spec will be generated "
"automatically based on --roles-data and "
"<deployed_baremetal.yaml>"),
"<deployed_baremetal.yaml>. The "
"<deployed_baremetal.yaml> parameter is "
"optional only if --ceph-spec is used."),
default=None)
spec_group.add_argument('--osd-spec',
help=_(
@ -270,6 +282,11 @@ class OvercloudCephDeploy(command.Command):
"Path to an existing crush hierarchy spec "
"file. "),
default=None)
parser.add_argument('--standalone', default=False,
action='store_true',
help=_("Use single host Ansible inventory. "
"Used only for development or testing "
"environments."))
parser.add_argument('--container-image-prepare',
help=_(
"Path to an alternative "
@ -327,14 +344,7 @@ class OvercloudCephDeploy(command.Command):
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(
@ -354,8 +364,11 @@ class OvercloudCephDeploy(command.Command):
working_dir = os.path.abspath(parsed_args.working_dir)
oooutils.makedirs(working_dir)
inventory = os.path.join(working_dir,
constants.TRIPLEO_STATIC_INVENTORY)
if parsed_args.standalone:
inventory = oooutils.standalone_ceph_inventory(working_dir)
else:
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: "
@ -365,13 +378,29 @@ class OvercloudCephDeploy(command.Command):
# 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,
"tripleo_cephadm_standalone": parsed_args.standalone
}
extra_vars_file = None
# optional paths to pass to playbook
if parsed_args.ceph_spec is None and \
parsed_args.baremetal_env is None:
raise oscexc.CommandError(
"Either <deployed_baremetal.yaml> "
"or --ceph-spec must be used.")
if parsed_args.baremetal_env:
baremetal_env_path = os.path.abspath(parsed_args.baremetal_env)
if not os.path.exists(baremetal_env_path):
raise oscexc.CommandError(
"Baremetal environment file does not exist:"
" %s" % parsed_args.baremetal_env)
else:
extra_vars['baremetal_deployed_path'] = \
os.path.abspath(parsed_args.baremetal_env)
if parsed_args.roles_data:
if not os.path.exists(parsed_args.roles_data):
raise oscexc.CommandError(
@ -406,6 +435,15 @@ class OvercloudCephDeploy(command.Command):
extra_vars['tripleo_cephadm_cluster'] = \
parsed_args.cluster
if parsed_args.mon_ip:
if not oooutils.is_valid_ip(parsed_args.mon_ip):
raise oscexc.CommandError(
"Invalid IP address '%s' passed to --mon-ip."
% parsed_args.mon_ip)
else:
extra_vars['tripleo_cephadm_first_mon_ip'] = \
parsed_args.mon_ip
if parsed_args.ceph_spec:
if not os.path.exists(parsed_args.ceph_spec):
raise oscexc.CommandError(
@ -658,6 +696,11 @@ class OvercloudCephUserDisable(command.Command):
metavar='<FSID>', required=True,
help=_("The FSID of the Ceph cluster to be "
"disabled. Required for disable option."))
parser.add_argument('--standalone', default=False,
action='store_true',
help=_("Use single host Ansible inventory. "
"Used only for development or testing "
"environments."))
return parser
@ -693,8 +736,11 @@ class OvercloudCephUserDisable(command.Command):
working_dir = os.path.abspath(parsed_args.working_dir)
oooutils.makedirs(working_dir)
inventory = os.path.join(working_dir,
constants.TRIPLEO_STATIC_INVENTORY)
if parsed_args.standalone:
inventory = oooutils.standalone_ceph_inventory(working_dir)
else:
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: "
@ -777,6 +823,11 @@ class OvercloudCephUserEnable(command.Command):
"so that cephadm will be re-enabled "
"for the Ceph cluster idenified "
"by the FSID."))
parser.add_argument('--standalone', default=False,
action='store_true',
help=_("Use single host Ansible inventory. "
"Used only for development or testing "
"environments."))
parser = arg_parse_common(parser)
return parser
@ -807,8 +858,11 @@ class OvercloudCephUserEnable(command.Command):
working_dir = os.path.abspath(parsed_args.working_dir)
oooutils.makedirs(working_dir)
inventory = os.path.join(working_dir,
constants.TRIPLEO_STATIC_INVENTORY)
if parsed_args.standalone:
inventory = oooutils.standalone_ceph_inventory(working_dir)
else:
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: "
@ -860,3 +914,196 @@ class OvercloudCephUserEnable(command.Command):
limit_hosts=ceph_hosts['_admin'][0],
reproduce_command=False,
)
class OvercloudCephSpec(command.Command):
log = logging.getLogger(__name__ + ".OvercloudCephSpec")
auth_required = False
def get_parser(self, prog_name):
parser = super(OvercloudCephSpec, self).get_parser(prog_name)
parser.add_argument('baremetal_env', nargs='?',
metavar='<deployed_baremetal.yaml>',
help=_('Path to the environment file '
'output from "openstack '
'overcloud node provision". '
'This argument may be excluded '
'only if --standalone is used.'))
parser.add_argument('-o', '--output', required=True,
metavar='<ceph_spec.yaml>',
help=_('The path to the output cephadm spec '
'file to pass to the "openstack '
'overcloud ceph deploy --ceph-spec '
'<ceph_spec.yaml>" command.'))
parser.add_argument('-y', '--yes', default=False, action='store_true',
help=_('Skip yes/no prompt before overwriting an '
'existing <ceph_spec.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))
parser.add_argument('--mon-ip',
help=_(
"IP address of the first Ceph monitor. "
"Only available with --standalone."),
default='')
parser.add_argument('--standalone', default=False,
action='store_true',
help=_("Create a spec file for a standalone "
"deployment. Used for single server "
"development or testing environments."))
spec_group = parser.add_mutually_exclusive_group()
spec_group.add_argument('--osd-spec',
help=_(
"Path to an existing OSD spec file. "
"When the Ceph spec file is generated "
"its OSD spec 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)
spec_group.add_argument('--crush-hierarchy',
help=_(
"Path to an existing crush hierarchy spec "
"file. "),
default=None)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
output_path = os.path.abspath(parsed_args.output)
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)
if parsed_args.standalone:
inventory = oooutils.standalone_ceph_inventory(working_dir)
else:
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 = {
'ceph_spec_path': output_path,
}
# optional paths to pass to playbook
if parsed_args.standalone is None and \
parsed_args.baremetal_env is None:
raise oscexc.CommandError(
"Either <deployed_baremetal.yaml> "
"or --standalone must be used.")
if parsed_args.baremetal_env:
baremetal_env_path = os.path.abspath(parsed_args.baremetal_env)
if not os.path.exists(baremetal_env_path):
raise oscexc.CommandError(
"Baremetal environment file does not exist:"
" %s" % parsed_args.baremetal_env)
else:
extra_vars['baremetal_deployed_path'] = \
os.path.abspath(parsed_args.baremetal_env)
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.mon_ip:
if not oooutils.is_valid_ip(parsed_args.mon_ip):
raise oscexc.CommandError(
"Invalid IP address '%s' passed to --mon-ip."
% parsed_args.mon_ip)
else:
if parsed_args.standalone:
extra_vars['tripleo_cephadm_first_mon_ip'] = \
parsed_args.mon_ip
else:
raise oscexc.CommandError(
"Option --mon-ip may only be "
"used with --standalone")
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)
if parsed_args.crush_hierarchy:
if not os.path.exists(parsed_args.crush_hierarchy):
raise oscexc.CommandError(
"Crush Hierarchy Spec file not found --crush-hierarchy %s."
% os.path.abspath(parsed_args.crush_hierarchy))
else:
extra_vars['crush_hierarchy_path'] = \
os.path.abspath(parsed_args.crush_hierarchy)
if parsed_args.standalone:
spec_playbook = 'cli-standalone-ceph-spec.yaml'
tags = ''
else:
spec_playbook = 'cli-deployed-ceph.yaml'
tags = 'ceph_spec'
with oooutils.TempDirs() as tmp:
oooutils.run_ansible_playbook(
playbook=spec_playbook,
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
reproduce_command=False,
tags=tags,
)