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:
parent
44a15791fa
commit
66c0890731
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue