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:
parent
660754c3f9
commit
33a8b5abb2
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue