Perform cinder-manage volume update_host

The backend name for volumes was changed since Kilo. To make these
volumes back manageable in Mitaka their hostnames have to be changed.
The `cinder-manage volume update_host` command is suitable to do that.

Change-Id: Ieeadd81c714186d58a3e3dfe5fd1223c40d16996
Partial-Bug: #1624341
This commit is contained in:
Ilya Kharin 2016-09-17 17:04:45 +03:00
parent 660754c3f9
commit 33a8b5abb2
8 changed files with 233 additions and 0 deletions

View File

@ -58,6 +58,8 @@ def upgrade_db(orig_id, seed_id, db_role_name):
db.mysqldump_restore_to_env(seed_env, db_role_name, fname)
db.db_sync(seed_env)
if db.does_perform_cinder_volume_update_host(orig_env):
db.cinder_volume_update_host(orig_env, seed_env)
def upgrade_db_with_graph(orig_id, seed_id):

View File

@ -67,6 +67,7 @@ OSD_UPGRADE_REQUIRED_PACKAGES = [
COBBLER_DROP_VERSION = "7.0"
CEPH_UPSTART_VERSION = "7.0"
NOVA_FLAVOR_DATA_MIGRATION_VERSION = "7.0"
CINDER_UPDATE_VOLUME_HOST_VERSION = "7.0"
MIRRORS_EXTRA_DIRS = ["ubuntu-full", "mos-ubuntu"]
@ -167,3 +168,4 @@ COMPUTE_PREUPGRADE_PACKAGES = {
}
ASTUTE_YAML = "/etc/fuel/astute.yaml"
CINDER_CONF = "/etc/cinder/cinder.conf"

View File

@ -121,3 +121,70 @@ def test_nova_migrate_flavor_data(mocker, statuses, is_error, is_timeout):
db.nova_migrate_flavor_data(env, attempts=attempts)
FLAVOR_STATUS = "{0} instances matched query, {1} completed"
@pytest.mark.parametrize(("version", "result"), [
("6.1", False),
("7.0", True),
("8.0", False),
])
def test_does_perform_cinder_volume_update_host(version, result):
env = mock.Mock(data={"fuel_version": version})
assert db.does_perform_cinder_volume_update_host(env) == result
def test_cinder_volume_update_host(mocker):
mock_orig_env = mock.Mock()
mock_new_env = mock.Mock()
mock_orig_cont = mock.Mock()
mock_new_cont = mock.Mock()
mock_get = mocker.patch("octane.util.env.get_one_controller")
mock_get.side_effect = [mock_orig_cont, mock_new_cont]
mock_get_current = mocker.patch("octane.util.db.get_current_host")
mock_get_new = mocker.patch("octane.util.db.get_new_host")
mock_ssh = mocker.patch("octane.util.ssh.call")
db.cinder_volume_update_host(mock_orig_env, mock_new_env)
mock_ssh.assert_called_once_with(
["cinder-manage", "volume", "update_host",
"--currenthost", mock_get_current.return_value,
"--newhost", mock_get_new.return_value],
node=mock_new_cont, parse_levels=True)
assert mock_get.call_args_list == [
mock.call(mock_orig_env),
mock.call(mock_new_env),
]
mock_get_current.assert_called_once_with(mock_orig_cont)
mock_get_new.assert_called_once_with(mock_new_cont)
@pytest.mark.parametrize(("func", "content", "expected"), [
(db.get_current_host, [
(None, "DEFAULT", None, None),
(None, "DEFAULT", "host", "fakehost"),
(None, "DEFAULT", "volume_backend_name", "fakebackend"),
], "fakehost#fakebackend"),
(db.get_new_host, [
(None, "DEFAULT", None, None),
(None, "DEFAULT", "host", "fakehost_default"),
(None, "RBD-backend", None, None),
(None, "RBD-backend", "volume_backend_name", "fakebackend"),
], "fakehost_default@fakebackend#RBD-backend"),
(db.get_new_host, [
(None, "DEFAULT", None, None),
(None, "DEFAULT", "host", "fakehost_default"),
(None, "RBD-backend", None, None),
(None, "RBD-backend", "backend_host", "fakehost_specific"),
(None, "RBD-backend", "volume_backend_name", "fakebackend"),
], "fakehost_specific@fakebackend#RBD-backend"),
])
def test_get_hosts_functional(mocker, func, content, expected):
mock_node = mock.Mock()
mocker.patch("octane.util.ssh.sftp")
mock_iter = mocker.patch("octane.util.helpers.iterate_parameters")
mock_iter.return_value = content
result = func(mock_node)
assert expected == result

View File

@ -9,6 +9,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import pytest
from octane.util import helpers
@ -85,3 +88,33 @@ NORMALIZED_DATA = [
def test_normalized_cliff_show_json(data, normalized_data):
res = helpers.normalized_cliff_show_json(data)
assert res == normalized_data
@pytest.mark.parametrize(("source", "parameters_to_get", "parameters"), [
([
(None, None, "option1", "value1"),
(None, "section1", None, None),
(None, "section1", None, None),
(None, "section1", "option2", "value2"),
(None, "section1", "option3", "value31"),
(None, "section2", None, None),
(None, "section2", "option4", "value4"),
(None, "section2", "option3", "value32"),
(None, "section3", "option3", "value33"),
], {
"opt2": [("section1", "option2")],
"opt3": [("section1", "option3"), ("section2", "option3")],
"opt4": [("section1", "option4"), ("section2", "option4")],
}, {
"opt2": "value2",
"opt3": "value32",
"opt4": "value4",
}),
])
def test_get_parameters(mocker, source, parameters_to_get, parameters):
mock_fp = mock.Mock()
mock_iter = mocker.patch("octane.util.helpers.iterate_parameters")
mock_iter.return_value = source
result = helpers.get_parameters(mock_fp, parameters_to_get)
mock_iter.assert_called_once_with(mock_fp)
assert result == parameters

View File

@ -200,3 +200,45 @@ def test_restart_nova_services(mocker, node, stdout, nova_services_to_restart):
call_mock.assert_any_call(["service", service, "restart"], node=node)
call_output_mock.assert_called_once_with(
["service", "--status-all"], node=node)
@pytest.mark.parametrize(
("parameters", "parameters_to_get", "required", "ensure", "error"), [
({
"opt1": "value1",
"opt2": "value2",
}, {
"opt1": [("section1", "option1")],
"opt2": [("section2", "option2")],
}, ("section1/option1", "section2/option2"), True, False),
({}, {
"opt1": [("section1", "option1")],
}, ("section1/option1"), True, True),
({}, {
"opt1": [("section1", "option1")],
}, (), False, False),
]
)
def test_get_parameters(mocker, parameters, parameters_to_get, required,
ensure, error):
mock_node = mock.Mock(data={"id": 1})
filename = "fake/filename.conf"
mock_sftp = mocker.patch("octane.util.ssh.sftp")
mock_get = mocker.patch("octane.util.helpers.get_parameters")
mock_get.return_value = parameters
if ensure and error:
msg = ("Could not get parameters from the file "
"node-1[fake/filename.conf]: {parameters}"
.format(parameters=", ".join(required)))
with pytest.raises(node_util.AbsentParametersError, message=msg):
node_util.get_parameters(mock_node, filename, parameters_to_get,
ensure=ensure)
else:
result = node_util.get_parameters(
mock_node, filename, parameters_to_get, ensure=ensure)
assert result == parameters
mock_get.assert_called_once_with(
mock_sftp.return_value.open.return_value.__enter__.return_value,
parameters_to_get)

View File

@ -19,6 +19,7 @@ from distutils import version
from octane import magic_consts
from octane.util import env as env_util
from octane.util import node as node_util
from octane.util import ssh
@ -73,6 +74,49 @@ FLAVOR_STATUS_RE = re.compile(
"(?P<completed>[0-9]+) completed$")
def does_perform_cinder_volume_update_host(env):
env_version = version.StrictVersion(env.data["fuel_version"])
return env_version == \
version.StrictVersion(magic_consts.CINDER_UPDATE_VOLUME_HOST_VERSION)
def cinder_volume_update_host(orig_env, new_env):
orig_controller = env_util.get_one_controller(orig_env)
new_controller = env_util.get_one_controller(new_env)
current_host = get_current_host(orig_controller)
new_host = get_new_host(new_controller)
ssh.call(["cinder-manage", "volume", "update_host",
"--currenthost", current_host,
"--newhost", new_host],
node=new_controller, parse_levels=True)
def get_current_host(node):
parameters = node_util.get_parameters(node, magic_consts.CINDER_CONF, {
"host": [("DEFAULT", "host")],
"backend": [("DEFAULT", "volume_backend_name")],
})
# NOTE(akscram): result = "rbd:volumes#DEFAULT"
result = "{host}#{backend}".format(
host=parameters["host"],
backend=parameters["backend"],
)
return result
def get_new_host(node):
parameters = node_util.get_parameters(node, magic_consts.CINDER_CONF, {
"host": [("DEFAULT", "host"), ("RBD-backend", "backend_host")],
"backend": [("RBD-backend", "volume_backend_name")],
})
# NOTE(akscram): result = "rbd:volumes@RBD-backend#RBD-backend"
result = "{host}@{backend}#RBD-backend".format(
host=parameters["host"],
backend=parameters["backend"],
)
return result
def mysqldump_from_env(env, role_name, dbs, fname):
node = env_util.get_one_node_of(env, role_name)
cmd = [

View File

@ -53,6 +53,19 @@ def iterate_parameters(fp):
yield line, section, None, None
def get_parameters(fp, parameters_to_get):
parameters_map = {}
for key, values in parameters_to_get.items():
for value in values:
parameters_map[value] = key
parameters = {}
for _, section, parameter, value in iterate_parameters(fp):
parameter_name = parameters_map.get((section, parameter))
if parameter_name is not None and value is not None:
parameters[parameter_name] = value
return parameters
def normalized_cliff_show_json(data):
if isinstance(data, list):
return {i['Field']: i['Value'] for i in data}

View File

@ -18,6 +18,7 @@ import sys
import time
from distutils import version
from octane.util import helpers
from octane.util import ssh
LOG = logging.getLogger(__name__)
@ -182,3 +183,32 @@ def restart_nova_services(node):
_, status, _, service = service_line.split()
if status == "+" and service.startswith("nova"):
ssh.call(["service", service, "restart"], node=node)
class AbsentParametersError(Exception):
msg = "Could not get parameters from the file " \
"node-{node_id}[{filename}]: {parameters}"
def __init__(self, node_id, filename, parameters):
super(AbsentParametersError, self).__init__(self.msg.format(
node_id=node_id,
filename=filename,
parameters=", ".join(parameters),
))
def get_parameters(node, filename, parameters_to_get, ensure=True):
with ssh.sftp(node).open(filename) as fp:
parameters = helpers.get_parameters(fp, parameters_to_get)
if ensure:
required_parameters = set(parameters_to_get)
current_parameters = set(parameters)
absent_parameters = required_parameters - current_parameters
if absent_parameters:
flat_parameters = []
for aparam in absent_parameters:
for param in parameters_to_get[aparam]:
flat_parameters.append("/".join(param))
raise AbsentParametersError(
node.data["id"], filename, flat_parameters)
return parameters