Add actions for renaming volume host attr

Add two new actions which are essentially wrappers around

cinder-manage volume update_host \
    --currenthost CURRENTHOST \
    --newhost NEWHOST

In previous versions of the charm if block-device is set or the
legacy ceph relation is used (as opposed to storage-backend relation)
then the configuration of those backends is done in the [DEFAULT]
section of the cinder.conf. As of Ocata that is no longer supported
and backends need to be listed in their own sections and referenced
via enable_backends parameter. This change in config results in a
change of host name and existing volumes need to have their metadata
updated to point at the new hostname.

Old Hostname: <unit-name>
New Hostname: <unit-name>@<backend section name>#<volume-backend-name>

New Action: volume-backend-name
Used for updating the host attribute of volumes to add the driver
name. This is needed after an upgrade to Ocata if there are existing
volumes which have been configured prior to multi-backends
being enabled.

New Action: rename-volume-host
Used for updating the host attribute of volumes. This action is
a lower level action then volume-host-add-driver and simply passes
the old and new hosts verbatim to cinder-manage.

Change-Id: I989074a3f41126aa57c514f7e18b887733bc18fe
Partial-Bug: #1665272
This commit is contained in:
Liam Young 2017-02-22 09:48:08 +00:00
parent 8641e81498
commit d21160a0d3
8 changed files with 243 additions and 5 deletions

View File

@ -13,3 +13,24 @@ remove-services:
type: string
default: unused
description: Hostname of the service to be removed.
rename-volume-host:
description: Update the host attribute of volumes from currenthost to newhost
params:
currenthost:
type: string
description: Current value of os-vol-host-attr:host volume attribute
newhost:
type: string
description: New hostname of the service
volume-host-add-driver:
description: Update the os-vol-host-attr:host volume attribute to include driver and volume name. Used for migrating volumes to multi-backend and Ocata+ configurtation.
params:
currenthost:
type: string
description: Current value of os-vol-host-attr:host volume attribute
driver:
type: string
description: driver name (as listed in enabled_backends)
volume-backend-name:
type: string
description: The backend volume name as shown by the volume_backend_name parameter in the driver section

View File

@ -25,6 +25,7 @@ from cinder_utils import (
resume_unit_helper,
register_configs,
)
import cinder_manage
def pause(args):
@ -42,7 +43,13 @@ def resume(args):
# A dictionary of all the defined actions to callables (which take
# parsed arguments).
ACTIONS = {"pause": pause, "resume": resume}
ACTIONS = {
"pause": pause,
"resume": resume,
"remove-services": cinder_manage.remove_services,
"rename-volume-host": cinder_manage.rename_volume_host,
"volume-host-add-driver": cinder_manage.volume_host_add_driver,
}
def main(args):

View File

@ -58,7 +58,13 @@ def cinder_manage_remove(binary, hostname):
hostname])
def remove_services():
def cinder_manage_volume_update_host(currenthost, newhost):
return subprocess.check_call(["cinder-manage", "volume", "update_host",
"--currenthost", currenthost,
"--newhost", newhost])
def remove_services(args):
load_config_file(os.path.join(os.path.sep, "etc", "cinder", "cinder.conf"))
host = action_get(key="host")
@ -92,5 +98,32 @@ def remove_services():
action_set({'removed': ",".join(removed_services)})
if __name__ == "__main__":
remove_services()
def _rename_volume_host(currenthost, newhost):
load_config_file(os.path.join(os.path.sep, "etc", "cinder", "cinder.conf"))
services = model_query({}, models.Service, read_deleted="no",
session=get_session())
services = services.filter(models.Service.host == currenthost)
if services.all():
try:
cinder_manage_volume_update_host(currenthost, newhost)
except:
action_set({'traceback': traceback.format_exc()})
action_fail("Cannot update host {}".format(currenthost))
else:
action_fail(
"Cannot update host attribute from {}, {} not found".format(
currenthost,
currenthost))
def rename_volume_host(args):
action_args = action_get()
_rename_volume_host(action_args['currenthost'], action_args['newhost'])
def volume_host_add_driver(args):
action_args = action_get()
newhost = "{}@{}".format(action_args['currenthost'], action_args['driver'])
if action_args.get('volume-backend-name'):
newhost = newhost + '#' + action_args['volume-backend-name']
_rename_volume_host(action_args['currenthost'], newhost)

View File

@ -1 +1 @@
remove_services.py
actions.py

1
actions/rename-volume-host Symbolic link
View File

@ -0,0 +1 @@
actions.py

View File

@ -0,0 +1 @@
actions.py

View File

@ -13,6 +13,15 @@
# limitations under the License.
import sys
import mock
sys.path.append('actions')
sys.path.append('hooks')
cinder = mock.MagicMock()
sqlalchemy = mock.MagicMock()
sys.modules['sqlalchemy'] = sqlalchemy
sys.modules['cinder'] = cinder
sys.modules['cinder.context'] = cinder.context
sys.modules['cinder.db'] = cinder.db
sys.modules['cinder.db.sqlalchemy'] = cinder.db.sqlalchemy
sys.modules['cinder.db.sqlalchemy.api'] = cinder.db.sqlalchemy.api

View File

@ -0,0 +1,166 @@
# Copyright 2016 Canonical Ltd
#
# 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.
from mock import patch, mock
from test_utils import (
CharmTestCase
)
import cinder_manage
import cinder
TO_PATCH = [
'subprocess',
'action_get',
'action_fail',
'action_set',
'os_release',
]
class CinderManageTestCase(CharmTestCase):
def setUp(self):
super(CinderManageTestCase, self).setUp(cinder_manage, TO_PATCH)
def tearDown(self):
cinder.reset_mock()
cinder.context.reset_mock()
cinder.db.reset_mock()
cinder.db.sqlalchemy.reset_mock()
cinder.db.sqlalchemy.api.reset_mock()
def test_load_config_file(self):
cinder_manage.load_config_file('/cinder.conf')
cinder.flags.FLAGS.assert_called_once_with(
args=[],
default_config_files=['/cinder.conf'],
project='cinder')
def test_cinder_manage_remove(self):
cinder_manage.cinder_manage_remove('mybin', 'myhost')
self.subprocess.check_call.assert_called_once_with(
['cinder-manage', 'service', 'remove', 'mybin', 'myhost'])
def test_manage_volume_update_host(self):
cinder_manage.cinder_manage_remove('host', 'host@this#that')
self.subprocess.check_call.assert_called_once_with(
['cinder-manage', 'service', 'remove', 'host', 'host@this#that'])
@patch.object(cinder_manage, 'cinder_manage_remove')
def test_remove_services(self, cinder_manage_remove):
self.action_get.return_value = 'sv1host'
svc1_mock = mock.MagicMock()
svc1_mock.binary = "svc1bin"
svc1_mock.host = "svc1host"
query_mock = mock.MagicMock()
query_mock.filter().all.return_value = [svc1_mock]
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
self.os_release.return_value = 'liberty'
cinder_manage.remove_services('arg')
cinder_manage_remove.assert_called_once_with('svc1bin', 'svc1host')
self.action_set.assert_called_once_with({'removed': 'svc1host'})
@patch.object(cinder_manage, 'cinder_manage_remove')
def test_remove_services_kilo(self, cinder_manage_remove):
self.action_get.return_value = 'sv1host'
svc1_mock = mock.MagicMock()
svc1_mock.binary = "svc1bin"
svc1_mock.host = "svc1host"
svc1_mock.id = 42
cinder.context.get_admin_context.return_value = 'admctxt'
query_mock = mock.MagicMock()
query_mock.filter().all.return_value = [svc1_mock]
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
self.os_release.return_value = 'kilo'
cinder_manage.remove_services('arg')
cinder.db.service_destroy.assert_called_once_with('admctxt', 42)
self.action_set.assert_called_once_with({'removed': 'svc1host'})
@patch.object(cinder_manage, 'cinder_manage_remove')
def test_remove_services_fail(self, cinder_manage_remove):
cinder_manage_remove.side_effect = Exception()
self.action_get.return_value = 'sv1host'
svc1_mock = mock.MagicMock()
svc1_mock.binary = "svc1bin"
svc1_mock.host = "svc1host"
query_mock = mock.MagicMock()
query_mock.filter().all.return_value = [svc1_mock]
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
self.os_release.return_value = 'liberty'
cinder_manage.remove_services('arg')
cinder_manage_remove.assert_called_once_with('svc1bin', 'svc1host')
self.action_fail.assert_called_once_with(
'Cannot remove service: svc1host')
@patch.object(cinder_manage, 'cinder_manage_volume_update_host')
def test__rename_volume_host(self, cinder_manage_volume_update_host):
self.action_get.return_value = 'myhost'
query_mock = mock.MagicMock()
query_mock.filter().all.return_value = ['myhost']
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
cinder_manage._rename_volume_host('a', 'b')
cinder_manage_volume_update_host.assert_called_once_with('a', 'b')
@patch.object(cinder_manage, 'cinder_manage_volume_update_host')
def test__rename_volume_host_missing(self,
cinder_manage_volume_update_host):
self.action_get.return_value = 'myhost'
query_mock = mock.MagicMock()
query_mock.filter().all.return_value = []
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
cinder_manage._rename_volume_host('a', 'b')
self.assertFalse(cinder_manage_volume_update_host.called)
self.action_fail.assert_called_once_with(
'Cannot update host attribute from a, a not found')
@patch.object(cinder_manage, 'cinder_manage_volume_update_host')
def test__rename_volume_host_fail(self,
cinder_manage_volume_update_host):
cinder_manage_volume_update_host.side_effect = Exception()
self.action_get.return_value = 'myhost'
query_mock = mock.MagicMock()
query_mock.filter().all().return_value = ['myhost']
cinder.db.sqlalchemy.api.model_query.return_value = query_mock
cinder_manage._rename_volume_host('a', 'b')
cinder_manage_volume_update_host.assert_called_once_with('a', 'b')
self.action_fail.assert_called_once_with('Cannot update host a')
@patch.object(cinder_manage, '_rename_volume_host')
def test_rename_volume_host(self, _rename_volume_host):
self.action_get.return_value = {
'currenthost': 'orghost',
'newhost': 'newhost'}
cinder_manage.rename_volume_host('arg')
_rename_volume_host.assert_called_once_with('orghost', 'newhost')
@patch.object(cinder_manage, '_rename_volume_host')
def test_volume_host_add_driver(self, _rename_volume_host):
self.action_get.return_value = {
'currenthost': 'orghost',
'driver': 'lvmdriver-1',
'volume-backend-name': 'LVM'}
cinder_manage.volume_host_add_driver('arg')
_rename_volume_host.assert_called_once_with(
'orghost', 'orghost@lvmdriver-1#LVM')
@patch.object(cinder_manage, '_rename_volume_host')
def test_volume_host_add_driver_novol_backend(self, _rename_volume_host):
self.action_get.return_value = {
'currenthost': 'orghost',
'driver': 'lvmdriver-1',
'volume-backend-name': ''}
cinder_manage.volume_host_add_driver('arg')
_rename_volume_host.assert_called_once_with(
'orghost', 'orghost@lvmdriver-1')