Fix support for cinder ceph rbd on Ocata
As of Ocata, the ceph key used to access a specific Cinder
Ceph backend must match the name of the key used by cinder,
with an appropriate secret configured for libvirt use with
the cephx key used by the cinder-ceph charm.
Add support for the new ceph-access relation to allow
nova-compute units to communicate with multiple ceph
backends using different cephx keys and user names.
The lead cinder-ceph unit will generate a UUID for use in
the cinder configuration file, and for use by the remote
nova-compute units when configuring libvirt secrets,
ensuring that both ends of the integration match up.
The side effect of this change is that nova-compute will
have a key for use with its own ephemeral backend ceph
access, and a key for each cinder ceph backend configured
in the deployment.
Change-Id: I974ecb39132feddfffabd6dcef401e91b5548d05
Closes-Bug: 1671422
(cherry picked from commit 62613456e7
)
This commit is contained in:
parent
92f3f36109
commit
fcd1afbe8b
|
@ -5,5 +5,6 @@
|
||||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||||
<path>/cinder-ceph/hooks</path>
|
<path>/cinder-ceph/hooks</path>
|
||||||
<path>/cinder-ceph/unit_tests</path>
|
<path>/cinder-ceph/unit_tests</path>
|
||||||
|
<path>/cinder-ceph/tests</path>
|
||||||
</pydev_pathproperty>
|
</pydev_pathproperty>
|
||||||
</pydev_project>
|
</pydev_project>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
cinder_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
cinder_hooks.py
|
|
@ -15,6 +15,7 @@
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
service_name,
|
service_name,
|
||||||
is_relation_made,
|
is_relation_made,
|
||||||
|
leader_get,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.context import (
|
from charmhelpers.contrib.openstack.context import (
|
||||||
|
@ -48,6 +49,7 @@ class CephSubordinateContext(OSContextGenerator):
|
||||||
('volume_driver', volume_driver),
|
('volume_driver', volume_driver),
|
||||||
('rbd_pool', service),
|
('rbd_pool', service),
|
||||||
('rbd_user', service),
|
('rbd_user', service),
|
||||||
|
('rbd_secret_uuid', leader_get('secret-uuid')),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
from cinder_utils import (
|
from cinder_utils import (
|
||||||
register_configs,
|
register_configs,
|
||||||
|
@ -27,6 +28,7 @@ from cinder_utils import (
|
||||||
VERSION_PACKAGE,
|
VERSION_PACKAGE,
|
||||||
)
|
)
|
||||||
from cinder_contexts import CephSubordinateContext
|
from cinder_contexts import CephSubordinateContext
|
||||||
|
from charmhelpers.contrib.openstack.context import CephContext
|
||||||
|
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
Hooks,
|
Hooks,
|
||||||
|
@ -37,6 +39,9 @@ from charmhelpers.core.hookenv import (
|
||||||
relation_ids,
|
relation_ids,
|
||||||
status_set,
|
status_set,
|
||||||
log,
|
log,
|
||||||
|
leader_get,
|
||||||
|
leader_set,
|
||||||
|
is_leader,
|
||||||
)
|
)
|
||||||
from charmhelpers.fetch import apt_install, apt_update
|
from charmhelpers.fetch import apt_install, apt_update
|
||||||
from charmhelpers.core.host import (
|
from charmhelpers.core.host import (
|
||||||
|
@ -131,6 +136,10 @@ def ceph_broken():
|
||||||
@hooks.hook('config-changed')
|
@hooks.hook('config-changed')
|
||||||
@restart_on_change(restart_map())
|
@restart_on_change(restart_map())
|
||||||
def write_and_restart():
|
def write_and_restart():
|
||||||
|
# NOTE(jamespage): seed uuid for use on compute nodes with libvirt
|
||||||
|
if not leader_get('secret-uuid') and is_leader():
|
||||||
|
leader_set({'secret-uuid': str(uuid.uuid4())})
|
||||||
|
|
||||||
# NOTE(jamespage): trigger any configuration related changes
|
# NOTE(jamespage): trigger any configuration related changes
|
||||||
# for cephx permissions restrictions
|
# for cephx permissions restrictions
|
||||||
ceph_changed()
|
ceph_changed()
|
||||||
|
@ -168,6 +177,40 @@ def upgrade_charm():
|
||||||
storage_backend(rid)
|
storage_backend(rid)
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.hook('leader-settings-changed')
|
||||||
|
def leader_settings_changed():
|
||||||
|
# NOTE(jamespage): lead unit will seed libvirt secret UUID
|
||||||
|
# re-exec relations that use this data.
|
||||||
|
for r_id in relation_ids('ceph-access'):
|
||||||
|
ceph_access_joined(r_id)
|
||||||
|
for r_id in relation_ids('storage-backend'):
|
||||||
|
storage_backend(r_id)
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.hook('ceph-access-relation-joined')
|
||||||
|
def ceph_access_joined(relation_id=None):
|
||||||
|
if 'ceph' not in CONFIGS.complete_contexts():
|
||||||
|
log('Deferring key provision until ceph relation complete')
|
||||||
|
return
|
||||||
|
|
||||||
|
secret_uuid = leader_get('secret-uuid')
|
||||||
|
if not secret_uuid:
|
||||||
|
if is_leader():
|
||||||
|
leader_set({'secret-uuid': str(uuid.uuid4())})
|
||||||
|
else:
|
||||||
|
log('Deferring key provision until leader seeds libvirt uuid')
|
||||||
|
return
|
||||||
|
|
||||||
|
# NOTE(jamespage): get key from ceph using a context
|
||||||
|
ceph_keys = CephContext()()
|
||||||
|
|
||||||
|
relation_set(
|
||||||
|
relation_id=relation_id,
|
||||||
|
relation_settings={'key': ceph_keys.get('key'),
|
||||||
|
'secret-uuid': leader_get('secret-uuid')}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
hooks.execute(sys.argv)
|
hooks.execute(sys.argv)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
cinder_hooks.py
|
|
@ -19,6 +19,8 @@ provides:
|
||||||
storage-backend:
|
storage-backend:
|
||||||
interface: cinder-backend
|
interface: cinder-backend
|
||||||
scope: container
|
scope: container
|
||||||
|
ceph-access:
|
||||||
|
interface: cinder-ceph-key
|
||||||
requires:
|
requires:
|
||||||
juju-info:
|
juju-info:
|
||||||
interface: juju-info
|
interface: juju-info
|
||||||
|
|
|
@ -391,14 +391,27 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment):
|
||||||
'cinder:storage-backend relation data...')
|
'cinder:storage-backend relation data...')
|
||||||
unit = self.cinder_ceph_sentry
|
unit = self.cinder_ceph_sentry
|
||||||
relation = ['storage-backend', 'cinder:storage-backend']
|
relation = ['storage-backend', 'cinder:storage-backend']
|
||||||
|
backend_uuid, _ = unit.run('leader-get secret-uuid')
|
||||||
|
|
||||||
sub = ('{"cinder": {"/etc/cinder/cinder.conf": {"sections": '
|
sub_dict = {
|
||||||
'{"cinder-ceph": [["volume_backend_name", "cinder-ceph"], '
|
"cinder": {
|
||||||
'["volume_driver", "cinder.volume.drivers.rbd.RBDDriver"], '
|
"/etc/cinder/cinder.conf": {
|
||||||
'["rbd_pool", "cinder-ceph"], ["rbd_user", "cinder-ceph"]]}}}}')
|
"sections": {
|
||||||
|
"cinder-ceph": [
|
||||||
|
["volume_backend_name", "cinder-ceph"],
|
||||||
|
["volume_driver",
|
||||||
|
"cinder.volume.drivers.rbd.RBDDriver"],
|
||||||
|
["rbd_pool", "cinder-ceph"],
|
||||||
|
["rbd_user", "cinder-ceph"],
|
||||||
|
["rbd_secret_uuid", backend_uuid],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'subordinate_configuration': sub,
|
'subordinate_configuration': json.dumps(sub_dict),
|
||||||
'private-address': u.valid_ip,
|
'private-address': u.valid_ip,
|
||||||
'backend_name': 'cinder-ceph'
|
'backend_name': 'cinder-ceph'
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ from test_utils import (
|
||||||
TO_PATCH = [
|
TO_PATCH = [
|
||||||
'is_relation_made',
|
'is_relation_made',
|
||||||
'service_name',
|
'service_name',
|
||||||
'get_os_codename_package'
|
'get_os_codename_package',
|
||||||
|
'leader_get',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ class TestCinderContext(CharmTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCinderContext, self).setUp(contexts, TO_PATCH)
|
super(TestCinderContext, self).setUp(contexts, TO_PATCH)
|
||||||
|
self.leader_get.return_value = 'libvirt-uuid'
|
||||||
|
|
||||||
def test_ceph_not_related(self):
|
def test_ceph_not_related(self):
|
||||||
self.is_relation_made.return_value = False
|
self.is_relation_made.return_value = False
|
||||||
|
@ -50,6 +52,7 @@ class TestCinderContext(CharmTestCase):
|
||||||
'cinder.volume.driver.RBDDriver'),
|
'cinder.volume.driver.RBDDriver'),
|
||||||
('rbd_pool', service),
|
('rbd_pool', service),
|
||||||
('rbd_user', service),
|
('rbd_user', service),
|
||||||
|
('rbd_secret_uuid', 'libvirt-uuid'),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +74,7 @@ class TestCinderContext(CharmTestCase):
|
||||||
'cinder.volume.drivers.rbd.RBDDriver'),
|
'cinder.volume.drivers.rbd.RBDDriver'),
|
||||||
('rbd_pool', service),
|
('rbd_pool', service),
|
||||||
('rbd_user', service),
|
('rbd_user', service),
|
||||||
|
('rbd_secret_uuid', 'libvirt-uuid'),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from mock import MagicMock, patch, call
|
from mock import MagicMock, patch, call, ANY
|
||||||
import json
|
import json
|
||||||
import cinder_utils as utils
|
import cinder_utils as utils
|
||||||
|
|
||||||
|
@ -42,6 +42,9 @@ TO_PATCH = [
|
||||||
'service_name',
|
'service_name',
|
||||||
'service_restart',
|
'service_restart',
|
||||||
'log',
|
'log',
|
||||||
|
'leader_get',
|
||||||
|
'leader_set',
|
||||||
|
'is_leader',
|
||||||
# charmhelpers.core.host
|
# charmhelpers.core.host
|
||||||
'apt_install',
|
'apt_install',
|
||||||
'apt_update',
|
'apt_update',
|
||||||
|
@ -195,3 +198,70 @@ class TestCinderHooks(CharmTestCase):
|
||||||
subordinate_configuration=json.dumps({'test': 1}),
|
subordinate_configuration=json.dumps({'test': 1}),
|
||||||
stateless=True,
|
stateless=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch.object(hooks, 'ceph_access_joined')
|
||||||
|
@patch.object(hooks, 'storage_backend')
|
||||||
|
def test_leader_settings_changed(self,
|
||||||
|
storage_backend,
|
||||||
|
ceph_access_joined):
|
||||||
|
self.relation_ids.side_effect = [['ceph-access:1'],
|
||||||
|
['storage-backend:23']]
|
||||||
|
hooks.leader_settings_changed()
|
||||||
|
ceph_access_joined.assert_called_with('ceph-access:1')
|
||||||
|
storage_backend.assert_called_with('storage-backend:23')
|
||||||
|
|
||||||
|
@patch.object(hooks, 'CONFIGS')
|
||||||
|
def test_ceph_access_joined_no_ceph(self,
|
||||||
|
CONFIGS):
|
||||||
|
CONFIGS.complete_contexts.return_value = []
|
||||||
|
hooks.ceph_access_joined()
|
||||||
|
self.relation_set.assert_not_called()
|
||||||
|
|
||||||
|
@patch.object(hooks, 'CONFIGS')
|
||||||
|
def test_ceph_access_joined_follower_unseeded(self,
|
||||||
|
CONFIGS):
|
||||||
|
CONFIGS.complete_contexts.return_value = ['ceph']
|
||||||
|
self.is_leader.return_value = False
|
||||||
|
self.leader_get.return_value = None
|
||||||
|
hooks.ceph_access_joined()
|
||||||
|
self.relation_set.assert_not_called()
|
||||||
|
|
||||||
|
@patch.object(hooks, 'CephContext')
|
||||||
|
@patch.object(hooks, 'CONFIGS')
|
||||||
|
def test_ceph_access_joined_leader(self,
|
||||||
|
CONFIGS,
|
||||||
|
CephContext):
|
||||||
|
CONFIGS.complete_contexts.return_value = ['ceph']
|
||||||
|
self.is_leader.return_value = True
|
||||||
|
self.leader_get.side_effect = [None, 'newuuid']
|
||||||
|
context = MagicMock()
|
||||||
|
context.return_value = {'key': 'mykey'}
|
||||||
|
CephContext.return_value = context
|
||||||
|
hooks.ceph_access_joined()
|
||||||
|
self.leader_get.assert_called_with('secret-uuid')
|
||||||
|
self.leader_set.assert_called_with({'secret-uuid': ANY})
|
||||||
|
self.relation_set.assert_called_with(
|
||||||
|
relation_id=None,
|
||||||
|
relation_settings={'key': 'mykey',
|
||||||
|
'secret-uuid': 'newuuid'}
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch.object(hooks, 'CephContext')
|
||||||
|
@patch.object(hooks, 'CONFIGS')
|
||||||
|
def test_ceph_access_joined_follower_seeded(self,
|
||||||
|
CONFIGS,
|
||||||
|
CephContext):
|
||||||
|
CONFIGS.complete_contexts.return_value = ['ceph']
|
||||||
|
self.is_leader.return_value = False
|
||||||
|
self.leader_get.return_value = 'newuuid'
|
||||||
|
context = MagicMock()
|
||||||
|
context.return_value = {'key': 'mykey'}
|
||||||
|
CephContext.return_value = context
|
||||||
|
hooks.ceph_access_joined()
|
||||||
|
self.leader_get.assert_called_with('secret-uuid')
|
||||||
|
self.leader_set.assert_not_called()
|
||||||
|
self.relation_set.assert_called_with(
|
||||||
|
relation_id=None,
|
||||||
|
relation_settings={'key': 'mykey',
|
||||||
|
'secret-uuid': 'newuuid'}
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue