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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
137
fuelclient/tests/unit/v2/lib/test_openstack_config.py
Normal file
137
fuelclient/tests/unit/v2/lib/test_openstack_config.py
Normal 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)
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user