Add multinode support to openstack config CLIv2

* Option --node accepts list of node ids.
* Improve client library, make many parameters optional.
* Add unit tests for openstack config library code.

Related-Bug: #1557462
Change-Id: Ib6e6ab3cbba6fb28cdec5738267d7aab354dce9f
(cherry-picked from 78a01f5aa1)
This commit is contained in:
Alexander Saprykin
2016-03-25 11:42:04 +02:00
parent a54da335f1
commit 51a6f5f733
5 changed files with 167 additions and 23 deletions

View File

@@ -41,10 +41,10 @@ class OpenstackConfigMixin(object):
)
@staticmethod
def add_node_id_arg(parser):
def add_node_ids_arg(parser):
parser.add_argument(
'-n', '--node',
type=int, default=None, help='Node ID.'
type=int, nargs='+', default=None, help='Node IDs.'
)
@staticmethod
@@ -81,7 +81,7 @@ class OpenstackConfigList(OpenstackConfigMixin, base.BaseCommand):
parser = super(OpenstackConfigList, self).get_parser(prog_name)
self.add_env_arg(parser)
self.add_node_id_arg(parser)
self.add_node_ids_arg(parser)
self.add_node_role_arg(parser)
self.add_deleted_arg(parser)
@@ -89,7 +89,7 @@ class OpenstackConfigList(OpenstackConfigMixin, base.BaseCommand):
def take_action(self, args):
data = self.client.get_filtered(
cluster_id=args.env, node_id=args.node,
cluster_id=args.env, node_ids=args.node,
node_role=args.role, is_active=(not args.deleted))
data = data_utils.get_display_data_multi(self.columns, data)
@@ -124,7 +124,7 @@ class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseCommand):
parser = super(OpenstackConfigUpload, self).get_parser(prog_name)
self.add_env_arg(parser)
self.add_node_id_arg(parser)
self.add_node_ids_arg(parser)
self.add_node_role_arg(parser)
self.add_file_arg(parser)
@@ -133,7 +133,7 @@ class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseCommand):
def take_action(self, args):
config = self.client.upload(
path=args.file, cluster_id=args.env,
node_id=args.node, node_role=args.role)
node_ids=args.node, node_role=args.role)
msg = "OpenStack configuration with id {0} " \
"uploaded from file '{0}'\n".format(config.id, args.file)
@@ -148,7 +148,7 @@ class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand):
parser = super(OpenstackConfigExecute, self).get_parser(prog_name)
self.add_env_arg(parser)
self.add_node_id_arg(parser)
self.add_node_ids_arg(parser)
self.add_node_role_arg(parser)
self.add_force_arg(parser)
@@ -156,7 +156,7 @@ class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand):
def take_action(self, args):
self.client.execute(
cluster_id=args.env, node_id=args.node, node_role=args.role,
cluster_id=args.env, node_ids=args.node, node_role=args.role,
force=args.force)
msg = "OpenStack configuration execution started.\n"

View File

@@ -50,6 +50,11 @@ class OpenstackConfig(BaseObject):
def get_filtered_data(cls, **kwargs):
url = cls.class_api_path
params = cls._prepare_params(kwargs)
node_ids = params.get('node_ids')
if node_ids is not None:
params['node_ids'] = ','.join([str(n) for n in node_ids])
return cls.connection.get_request(url, params=params)
@classmethod

View File

@@ -36,21 +36,21 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
cmd_line='--env {0} --node {1}'.format(self.CLUSTER_ID,
self.NODE_ID),
expected_kwargs={'cluster_id': self.CLUSTER_ID,
'node_id': self.NODE_ID, 'node_role': None,
'node_ids': [self.NODE_ID], 'node_role': None,
'is_active': True}
)
def test_config_list_for_role(self):
self._test_config_list(
cmd_line='--env {0} --role compute'.format(self.CLUSTER_ID),
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None,
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_ids': None,
'node_role': 'compute', 'is_active': True}
)
def test_config_list_for_cluster(self):
self._test_config_list(
cmd_line='--env {0}'.format(self.CLUSTER_ID),
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None,
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_ids': None,
'node_role': None, 'is_active': True}
)
@@ -71,7 +71,7 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
self.m_client.upload.assert_called_once_with(
path='config.yaml', cluster_id=self.CLUSTER_ID,
node_id=self.NODE_ID, node_role=None)
node_ids=[self.NODE_ID], node_role=None)
@mock.patch('sys.stderr')
def test_config_upload_fail(self, mocked_stderr):
@@ -111,8 +111,8 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
self.m_client.execute.assert_called_once_with(
cluster_id=self.CLUSTER_ID, node_id=self.NODE_ID, node_role=None,
force=False)
cluster_id=self.CLUSTER_ID, node_ids=[self.NODE_ID],
node_role=None, force=False)
def test_config_force_execute(self):
cmd = 'openstack-config execute --env {0} --node {1} --force' \
@@ -121,5 +121,5 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
self.m_client.execute.assert_called_once_with(
cluster_id=self.CLUSTER_ID, node_id=self.NODE_ID, node_role=None,
force=True)
cluster_id=self.CLUSTER_ID, node_ids=[self.NODE_ID],
node_role=None, force=True)

View File

@@ -0,0 +1,137 @@
# Copyright 2016 Mirantis, Inc.
#
# 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.
import mock
import yaml
import fuelclient
from fuelclient.tests.unit.v2.lib import test_api
from fuelclient.tests import utils
class TestOpenstackConfigClient(test_api.BaseLibTest):
def setUp(self):
super(TestOpenstackConfigClient, self).setUp()
self.version = 'v1'
self.uri = '/api/{version}/openstack-config/'.format(
version=self.version)
self.client = fuelclient.get_client('openstack-config', self.version)
def test_config_list_for_cluster(self):
cluster_id = 1
fake_configs = [
utils.get_fake_openstack_config(cluster_id=cluster_id)
]
uri = self.uri + '?cluster_id={0}&is_active=True'.format(cluster_id)
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
data = self.client.get_filtered(cluster_id=1)
self.assertTrue(m_get.called)
self.assertEqual(data[0]['cluster_id'], cluster_id)
def test_config_list_for_node(self):
cluster_id = 1
fake_configs = [
utils.get_fake_openstack_config(
cluster_id=cluster_id, node_id=22),
]
uri = self.uri + '?cluster_id={0}&node_ids=22' \
'&is_active=True'.format(cluster_id)
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
data = self.client.get_filtered(cluster_id=1, node_ids=[22])
self.assertTrue(m_get.called)
self.assertEqual(data[0]['cluster_id'], cluster_id)
def test_config_list_for_multinode(self):
cluster_id = 1
fake_configs = [
utils.get_fake_openstack_config(
cluster_id=cluster_id, node_id=22),
utils.get_fake_openstack_config(
cluster_id=cluster_id, node_id=44),
]
uri = self.uri + '?cluster_id={0}&node_ids=22,44' \
'&is_active=True'.format(cluster_id)
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
data = self.client.get_filtered(cluster_id=1, node_ids=[22, 44])
self.assertTrue(m_get.called)
self.assertEqual(data[0]['cluster_id'], cluster_id)
def test_config_download(self):
config_id = 42
uri = self.uri + '{0}/'.format(42)
fake_config = utils.get_fake_openstack_config(id=config_id)
m_get = self.m_request.get(uri, json=fake_config)
m_open = mock.mock_open()
with mock.patch('fuelclient.cli.serializers.open',
m_open, create=True):
self.client.download(config_id, '/path/to/config')
self.assertTrue(m_get.called)
written_yaml = yaml.safe_load(m_open().write.mock_calls[0][1][0])
self.assertEqual(written_yaml,
{'configuration': fake_config['configuration']})
def test_config_upload(self):
cluster_id = 1
fake_config = utils.get_fake_openstack_config(
cluster_id=cluster_id)
m_post = self.m_request.post(self.uri, json=fake_config)
m_open = mock.mock_open(read_data=yaml.safe_dump({
'configuration': fake_config['configuration']
}))
with mock.patch(
'fuelclient.cli.serializers.open', m_open, create=True), \
mock.patch('os.path.exists', mock.Mock(return_value=True)):
self.client.upload('/path/to/config', cluster_id)
self.assertTrue(m_post.called)
body = m_post.last_request.json()
self.assertEqual(body['cluster_id'], cluster_id)
self.assertNotIn('node_ids', body)
self.assertNotIn('node_role', body)
def test_config_upload_multinode(self):
cluster_id = 1
fake_config = utils.get_fake_openstack_config(
cluster_id=cluster_id)
m_post = self.m_request.post(self.uri, json={'id': 42})
m_open = mock.mock_open(read_data=yaml.safe_dump({
'configuration': fake_config['configuration']
}))
with mock.patch(
'fuelclient.cli.serializers.open', m_open, create=True), \
mock.patch('os.path.exists', mock.Mock(return_value=True)):
self.client.upload(
'/path/to/config', cluster_id, node_ids=[42, 44])
self.assertTrue(m_post.called)
body = m_post.last_request.json()
self.assertEqual(body['cluster_id'], cluster_id)
self.assertEqual(body['node_ids'], [42, 44])
self.assertNotIn('node_role', body)

View File

@@ -20,11 +20,11 @@ class OpenstackConfigClient(base_v1.BaseV1Client):
_entity_wrapper = objects.OpenstackConfig
def upload(self, path, cluster_id, node_id=None, node_role=None):
def upload(self, path, cluster_id, node_ids=None, node_role=None):
data = self._entity_wrapper.read_file(path)
return self._entity_wrapper.create(
cluster_id=cluster_id, configuration=data['configuration'],
node_id=node_id, node_role=node_role)
node_ids=node_ids, node_role=node_role)
def download(self, config_id, path):
config = self._entity_wrapper(config_id)
@@ -33,15 +33,17 @@ class OpenstackConfigClient(base_v1.BaseV1Client):
return path
def execute(self, config_id, cluster_id, node_id, node_role, force=False):
def execute(self, cluster_id, config_id=None, node_ids=None,
node_role=None, force=False):
return self._entity_wrapper.execute(
config_id=config_id, cluster_id=cluster_id, node_id=node_id,
cluster_id=cluster_id, config_id=config_id, node_ids=node_ids,
node_role=node_role, force=force)
def get_filtered(self, cluster_id, node_id, node_role, is_active=True):
def get_filtered(self, cluster_id, node_ids=None,
node_role=None, is_active=True):
return self._entity_wrapper.get_filtered_data(
cluster_id=cluster_id, node_id=node_id, node_role=node_role,
is_active=is_active)
cluster_id=cluster_id, node_ids=node_ids,
node_role=node_role, is_active=is_active)
def get_client():