Add the ability to disable/re-enable cephadm SSH user

Add `openstack overcloud ceph user disable | re-enable`
command line option. As requested by those who wish to
disable cephadm and the user which supports it after
deployment. The SSH user and cephadm may be re-enabled
when it is necessary to administer the Ceph cluster.

Depends-On: I648cdf8c8920c120049f05f13f8b7b73513899f1
Change-Id: Ibd4513183f59ebb94d841a847ecfab0425ba8f5a
This commit is contained in:
John Fulton 2021-12-03 21:28:12 +00:00
parent 44a15791fa
commit 66c0890731
6 changed files with 365 additions and 0 deletions

View File

@ -0,0 +1,14 @@
---
features:
- |
Two new commands, "openstack overcloud ceph user disable" and
"openstack overcloud ceph user re-enable", are added which may be run after
"openstack overcloud ceph deploy" has been run. The "ceph user disable"
option will disable cephadm so that it may not be used to administer the
Ceph cluster and no "ceph orch ..." CLI commands will function. This will
also prevent Ceph node overcloud scale operations though the Ceph cluster
will still be able to read/write data. The "ceph user disable" option will
also remove the public and private SSH keys of the cephadm SSH user on
overclouds which host Ceph. The "ceph user re-enable" option restores the
public and private SSH keys of the cephadm SSH user and then re-enables the
cephadm mgr module.

View File

@ -43,6 +43,8 @@ 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_user_disable = tripleoclient.v2.overcloud_ceph:OvercloudCephUserDisable
overcloud_ceph_user_re-enable = tripleoclient.v2.overcloud_ceph:OvercloudCephUserReEnable
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

View File

@ -2574,3 +2574,26 @@ class TestGetCephNetworks(TestCase):
net_name = utils.get_ceph_networks(cfgfile.name,
'storage', 'storage_mgmt')
self.assertEqual(expected, net_name)
class TestGetHostsFromCephSpec(TestCase):
fake_ceph_spec = yaml.safe_load('''
addr: 192.168.24.1
hostname: standalone.example.com
labels:
- osd
- _admin
- mon
- mgr
service_type: host
''')
def test_get_hosts_from_ceph_spec(self):
expected = {'admin': [],
'non_admin': []
}
with tempfile.NamedTemporaryFile(mode='w') as cfgfile:
yaml.safe_dump(self.fake_ceph_spec, cfgfile)
hosts = utils.get_hosts_from_ceph_spec(cfgfile.name)
self.assertEqual(expected, hosts)

View File

@ -77,3 +77,88 @@ class TestOvercloudCephDeploy(fakes.FakePlaybookExecution):
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):
super(TestOvercloudCephUserDisable, 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.OvercloudCephUserDisable(self.app,
app_args)
@mock.patch('tripleoclient.utils.get_hosts_from_ceph_spec', 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_overcloud_ceph_user_disable(self, mock_playbook, mock_abspath,
mock_path_exists, mock_tempdirs,
mock_get_hosts_from_ceph_spec):
arglist = ['ceph_spec.yaml', '--yes',
'--ssh-user', 'ceph-admin',
'--stack', 'overcloud']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
mock_playbook.assert_called_with(
playbook='disable_cephadm.yml',
inventory=mock.ANY,
workdir=mock.ANY,
playbook_dir=mock.ANY,
verbosity=3,
extra_vars={
"backend": '',
"action": 'disable'
}
)
@mock.patch('os.path.abspath', autospect=True)
@mock.patch('os.path.exists', autospect=True)
def test_overcloud_ceph_user_disable_no_yes(self, mock_abspath,
mock_path_exists):
arglist = ['ceph_spec.yaml',
'--ssh-user', 'ceph-admin',
'--stack', 'overcloud']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(osc_lib_exc.CommandError,
self.cmd.take_action, parsed_args)
class TestOvercloudCephUserReEnable(fakes.FakePlaybookExecution):
def setUp(self):
super(TestOvercloudCephUserReEnable, 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.OvercloudCephUserReEnable(self.app,
app_args)
@mock.patch('tripleoclient.utils.get_hosts_from_ceph_spec', 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_overcloud_ceph_user_re_enable(self, mock_playbook, mock_abspath,
mock_path_exists, mock_tempdirs,
mock_get_hosts_from_ceph_spec):
arglist = ['ceph_spec.yaml',
'--ssh-user', 'ceph-admin',
'--stack', 'overcloud']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
mock_playbook.assert_called_with(
playbook='disable_cephadm.yml',
inventory=mock.ANY,
workdir=mock.ANY,
playbook_dir=mock.ANY,
verbosity=3,
extra_vars={
"backend": 'cephadm',
"action": 'enable'
}
)

View File

@ -3318,3 +3318,25 @@ def write_ephemeral_heat_clouds_yaml(heat_dir):
heatrc_path = os.path.join(heat_dir, 'heatrc')
with open(heatrc_path, 'w') as f:
f.write(heatrc)
def get_hosts_from_ceph_spec(ceph_spec_path):
"""Get admin and non-admin hosts from ceph_spec_path file
:param ceph_spec_path: the path to a ceph_spec.yaml file
:return: dict mapping admin and non-admin names to hosts lists
"""
hosts = {}
hosts['admin'] = []
hosts['non_admin'] = []
with open(ceph_spec_path, 'r') as stream:
try:
ceph_spec = yaml.safe_load(stream)
except yaml.YAMLError as exc:
raise RuntimeError(
"yaml.safe_load(%s) returned '%s'" % (ceph_spec_path, exc))
if ceph_spec:
pass
return hosts

View File

@ -349,3 +349,222 @@ class OvercloudCephDeploy(command.Command):
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
)
class OvercloudCephUserDisable(command.Command):
log = logging.getLogger(__name__ + ".OvercloudCephUserDisable")
def get_parser(self, prog_name):
parser = super(OvercloudCephUserDisable, self).get_parser(prog_name)
parser.add_argument('ceph_spec',
metavar='<ceph_spec.yaml>',
help=_(
"Path to an existing Ceph spec file "
"which describes the Ceph cluster "
"where the cephadm SSH user will have "
"their public and private keys removed "
"and cephadm will be disabled. "
"Spec file is necessary to determine "
"which nodes to modify. "
"WARNING: Ceph cluster administration or "
"modification will no longer function."))
parser.add_argument('-y', '--yes', default=False, action='store_true',
help=_('Skip yes/no prompt before disabling '
'cephadm and its SSH user. '
'(assume yes).'))
parser.add_argument('--ssh-user', dest='ssh_user',
help=_('Name of the SSH user used by cephadm. '
'Defaults to "ceph-admin". '
'(default=Env: CEPHADM_SSH_USER)'),
default=utils.env('CEPHADM_SSH_USER',
default='ceph-admin'))
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 from the deployment where all '
'input, output, and generated files are stored.\n'
'Defaults to "$HOME/overcloud-deploy/<stack>"'))
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
ceph_spec = os.path.abspath(parsed_args.ceph_spec)
if not os.path.exists(ceph_spec):
raise oscexc.CommandError(
"Ceph spec file does not exist:"
" %s" % parsed_args.ceph_spec)
overwrite = parsed_args.yes
if (not overwrite
and not oooutils.prompt_user_for_confirmation(
'Are you sure you want to disable Ceph '
'cluster management [y/N]?',
self.log)):
raise oscexc.CommandError("Will not disable cephadm and delete "
"the cephadm SSH user :"
" %s. See the --yes parameter to "
"override this behavior. " %
parsed_args.ssh_user)
else:
overwrite = True
# use stack and working_dir to find inventory
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)
# call the playbook to toggle cephadm w/ disable
extra_vars = {
"backend": '',
"action": 'disable'
}
with oooutils.TempDirs() as tmp:
oooutils.run_ansible_playbook(
playbook='disable_cephadm.yml',
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
)
# call the playbook to remove ssh_user_keys
extra_vars = {
"tripleo_cephadm_ssh_user": parsed_args.ssh_user
}
hosts = oooutils.get_hosts_from_ceph_spec(ceph_spec)
if len(hosts['admin']) > 0 and len(hosts['non_admin']) > 0:
with oooutils.TempDirs() as tmp:
oooutils.run_ansible_playbook(
playbook='ceph-admin-user-disable.yml',
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
limit_hosts=",".join(hosts['admin'] + hosts['non_admin'])
)
class OvercloudCephUserReEnable(command.Command):
log = logging.getLogger(__name__ + ".OvercloudCephUserReEnable")
def get_parser(self, prog_name):
parser = super(OvercloudCephUserReEnable, self).get_parser(prog_name)
parser.add_argument('ceph_spec',
metavar='<ceph_spec.yaml>',
help=_(
"Path to an existing Ceph spec file "
"which describes the Ceph cluster "
"where the cephadm SSH user will have "
"their public or private keys restored "
"and cephadm will be re-enabled for "
"cluster administration. "
"Spec file is necessary to determine "
"which nodes to modify and if "
"a public or private key is required."))
parser.add_argument('--ssh-user', dest='ssh_user',
help=_('Name of the SSH user used by cephadm. '
'Defaults to "ceph-admin". '
'(default=Env: CEPHADM_SSH_USER)'),
default=utils.env('CEPHADM_SSH_USER',
default='ceph-admin'))
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 from the deployment where all '
'input, output, and generated files are stored.\n'
'Defaults to "$HOME/overcloud-deploy/<stack>"'))
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
ceph_spec = os.path.abspath(parsed_args.ceph_spec)
if not os.path.exists(ceph_spec):
raise oscexc.CommandError(
"Ceph spec file does not exist:"
" %s" % parsed_args.ceph_spec)
# use stack and working_dir to find inventory
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)
# Create the ssh_user on admin and then non-admin hosts
hosts = oooutils.get_hosts_from_ceph_spec(ceph_spec)
for limit_list in [hosts['admin'], hosts['non_admin']]:
if len(limit_list) > 0:
extra_vars = {
"tripleo_admin_user": parsed_args.ssh_user,
"distribute_private_key": True
}
# need to include undercloud where RSA keys are generated
limit_list.append('undercloud')
with oooutils.TempDirs() as tmp:
oooutils.run_ansible_playbook(
playbook='ceph-admin-user-playbook.yml',
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
limit_hosts=",".join(limit_list)
)
# admin_hosts are done now so don't distribute private key
extra_vars["distribute_private_key"] = False
# Call the playbook to toggle cephadm w/ enable
extra_vars = {
"backend": 'cephadm',
"action": 'enable'
}
# todo(fultonj): I can limit it to any mon
# but "hosts: ceph_mon[0]" in this playbook won't work
with oooutils.TempDirs() as tmp:
oooutils.run_ansible_playbook(
playbook='disable_cephadm.yml',
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=oooutils.playbook_verbosity(self=self),
extra_vars=extra_vars,
)