Add 'fuel2 release component list' command

In Nailgun there is an API call for getting JSON data
of compatible components for release and all plugins
releated with it (GET /api/v1/releases/<:id>/components/).
This patch adds new fuel2 command, e.g.:

       fuel2 release component list 1

This command prints list of components for given Fuel release
with respective cross-dependencies

DocImpact
Closes-Bug: 1600501

Change-Id: I055d6fb4dde4cb0ac9508084155bbce5a106220c
This commit is contained in:
tivaliy 2016-07-09 21:05:13 +03:00
parent 962c5bff48
commit 287e5a2470
9 changed files with 235 additions and 0 deletions

View File

@ -79,3 +79,56 @@ class ReleaseReposUpdate(ReleaseMixIn, base.BaseCommand):
file=parsed_args.file
)
)
class ReleaseComponentList(ReleaseMixIn, base.BaseListCommand):
"""Show list of components for a given release."""
columns = ("name",
"requires",
"compatible",
"incompatible",
"default")
@staticmethod
def retrieve_predicates(statement):
"""Retrieve predicates with respective 'items' components
:param statement: the dictionary to extract predicate values from
:return: retrieval result as a string
"""
predicates = ('any_of', 'all_of', 'one_of', 'none_of')
for predicate in predicates:
if predicate in statement:
result = ', '.join(statement[predicate].get('items'))
return "{0} ({1})".format(predicate, result)
raise ValueError('Predicates not found.')
@classmethod
def retrieve_data(cls, value):
"""Retrieve names of components or predicates from nested data
:param value: data to extract name or to retrieve predicates from
:return: names of components or predicates as a string
"""
if isinstance(value, list):
# get only "name" of components otherwise retrieve predicates
return ', '.join([v['name'] if 'name' in v
else cls.retrieve_predicates(v)
for v in value])
return value
def get_parser(self, prog_name):
parser = super(ReleaseComponentList, self).get_parser(prog_name)
parser.add_argument('id', type=int,
help='Id of the {0}.'.format(self.entity_name))
return parser
def take_action(self, parsed_args):
data = self.client.get_components_by_id(parsed_args.id)
# some keys (columns) can be missed in origin data
# then create them with respective '-' value
data = [{k: self.retrieve_data(d.get(k, '-')) for k in self.columns}
for d in data]
data = data_utils.get_display_data_multi(self.columns, data)
return self.columns, data

View File

@ -22,6 +22,7 @@ class Release(BaseObject):
networks_path = 'releases/{0}/networks'
attributes_metadata_path = 'releases/{0}/attributes_metadata'
deployment_tasks_path = 'releases/{0}/deployment_tasks'
components_path = 'releases/{0}/components'
def get_networks(self):
url = self.networks_path.format(self.id)
@ -46,3 +47,7 @@ class Release(BaseObject):
def update_deployment_tasks(self, data):
url = self.deployment_tasks_path.format(self.id)
return self.connection.put_request(url, data)
def get_components(self):
url = self.components_path.format(self.id)
return self.connection.get_request(url)

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
#
# 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.
from fuelclient.commands.release import ReleaseComponentList
from fuelclient.tests.unit.v1 import base
class TestReleaseComponent(base.UnitTestCase):
def test_retrieve_predicates(self):
predicates = ('any_of', 'all_of', 'one_of', 'none_of')
items = {
"items": ["fake:component:1",
"fake:component:2"]
}
for predicate in predicates:
test_data = {predicate: items}
real_data = ReleaseComponentList.retrieve_predicates(test_data)
expected_data = "{} (fake:component:1, fake:component:2)".format(
predicate)
self.assertEqual(expected_data, real_data)
def test_retrieve_predicates_w_wrong_predicate(self):
test_data = {
"bad_predicate": {
"items": ["fake:component:1",
"fake:component:2"],
}
}
self.assertRaisesRegexp(ValueError,
"Predicates not found.",
ReleaseComponentList.retrieve_predicates,
test_data)
def test_retrieve_data(self):
test_data = "fake:component:1"
real_data = ReleaseComponentList.retrieve_data(test_data)
self.assertEqual("fake:component:1", real_data)
test_data = [{"name": "fake:component:1"}]
real_data = ReleaseComponentList.retrieve_data(test_data)
self.assertEqual("fake:component:1", real_data)
test_data = [
{
"one_of": {
"items": ["fake:component:1"]
}
},
{
"any_of": {
"items": ["fake:component:1",
"fake:component:2"]
}
},
{
"all_of": {
"items": ["fake:component:1",
"fake:component:2"]
}
},
{
"none_of": {
"items": ["fake:component:1"]
}
}
]
real_data = ReleaseComponentList.retrieve_data(test_data)
expected_data = ("one_of (fake:component:1), "
"any_of (fake:component:1, fake:component:2), "
"all_of (fake:component:1, fake:component:2), "
"none_of (fake:component:1)")
self.assertEqual(expected_data, real_data)

View File

@ -28,6 +28,8 @@ class TestReleaseCommand(test_engine.BaseCLITest):
self.m_client.get_by_id.return_value = fake_release.get_fake_release()
self.m_client.get_attributes_metadata_by_id.return_value = \
fake_release.get_fake_attributes_metadata()
self.m_client.get_components_by_id.return_value = \
fake_release.get_fake_release_components(10)
def test_release_list(self):
args = 'release list'
@ -63,3 +65,10 @@ class TestReleaseCommand(test_engine.BaseCLITest):
self.m_client.update_attributes_metadata_by_id \
.assert_called_once_with(1, data)
self.m_get_client.assert_called_once_with('release', mock.ANY)
def test_release_component_list(self):
release_id = 42
args = 'release component list {0}'.format(release_id)
self.exec_command(args)
self.m_client.get_components_by_id.assert_called_once_with(release_id)
self.m_get_client.assert_called_once_with('release', mock.ANY)

View File

@ -29,6 +29,7 @@ class TestReleaseFacade(test_api.BaseLibTest):
self.version = 'v1'
self.res_uri = '/api/{version}/releases/'.format(version=self.version)
self.fake_releases = utils.get_fake_releases(10)
self.fake_release_components = utils.get_fake_release_components(10)
self.fake_attributes_metadata = utils.get_fake_attributes_metadata()
self.client = fuelclient.get_client('release', self.version)
@ -63,3 +64,12 @@ class TestReleaseFacade(test_api.BaseLibTest):
self.assertTrue(m_put.called)
self.assertEqual(m_put.last_request.json(),
self.fake_attributes_metadata)
def test_release_component_list(self):
release_id = 42
expected_uri = self.get_object_uri(self.res_uri, release_id,
'/components')
matcher = self.m_request.get(expected_uri,
json=self.fake_release_components)
self.client.get_components_by_id(release_id)
self.assertTrue(matcher.called)

View File

@ -41,6 +41,8 @@ from fuelclient.tests.utils.fake_plugin import get_fake_plugins
from fuelclient.tests.utils.fake_release import get_fake_release
from fuelclient.tests.utils.fake_release import get_fake_releases
from fuelclient.tests.utils.fake_release import get_fake_attributes_metadata
from fuelclient.tests.utils.fake_release import get_fake_release_component
from fuelclient.tests.utils.fake_release import get_fake_release_components
__all__ = (get_fake_deployment_history,
@ -52,6 +54,8 @@ __all__ = (get_fake_deployment_history,
get_fake_release,
get_fake_releases,
get_fake_attributes_metadata,
get_fake_release_component,
get_fake_release_components,
get_fake_fuel_version,
get_fake_interface_config,
get_fake_network_group,

View File

@ -66,3 +66,64 @@ def get_fake_releases(release_count, **kwargs):
"""Create a random fake release list."""
return [get_fake_release(release_id=i, **kwargs)
for i in range(1, release_count + 1)]
def get_fake_release_component(name=None, requires=None, incompatible=None,
compatible=None, default=None):
"""Create a random fake component of release
Returns the serialized and parametrized representation of a dumped Fuel
component of release. Represents the average amount of data.
"""
return {
'name': name or 'network:neutron:ml2:vlan',
'description':
'dialog.create_cluster_wizard.network.neutron_vlan_description',
'weight': 5,
'requires': requires or [
{
'one_of': {
'items': ['hypervisor:qemu'],
'message': 'dialog.create_cluster_wizard.compute.'
'vcenter_warning'
}
},
{
'one_of': {
'items': ['network:neutron:ml2:dvs',
'network:neutron:ml2:nsx'],
'message': 'dialog.create_cluster_wizard.compute.'
'vcenter_requires_network_backend',
'message_invalid': 'dialog.create_cluster_wizard.compute.'
'vcenter_requires_network_plugins'
}
}
],
'incompatible': incompatible or [
{'message': 'dialog.create_cluster_wizard.network.vlan_tun_alert',
'name': 'network:neutron:ml2:tun'}
],
'compatible': compatible or [
{'name': 'network:neutron:core:ml2'},
{'name': 'hypervisor:qemu'},
{'name': 'hypervisor:vmware'},
{'name': 'storage:block:lvm'},
{'name': 'storage:block:ceph'},
{'name': 'storage:object:ceph'},
{'name': 'storage:ephemeral:ceph'},
{'name': 'storage:image:ceph'},
{'name': 'additional_service:sahara'},
{'name': 'additional_service:murano'},
{'name': 'additional_service:ceilometer'},
{'name': 'additional_service:ironic'}
],
'default': default,
'label': 'common.network.neutron_vlan',
}
def get_fake_release_components(component_count, **kwargs):
"""Create a random fake list of release components."""
return [get_fake_release_component(**kwargs)
for _ in range(component_count)]

View File

@ -28,6 +28,10 @@ class ReleaseClient(base_v1.BaseV1Client):
release_obj = self._entity_wrapper(obj_id=release_id)
return release_obj.get_attributes_metadata()
def get_components_by_id(self, release_id):
release_obj = self._entity_wrapper(obj_id=release_id)
return release_obj.get_components()
def get_client(connection):
return ReleaseClient(connection)

View File

@ -70,6 +70,7 @@ fuelclient =
openstack-config_upload=fuelclient.commands.openstack_config:OpenstackConfigUpload
plugins_list=fuelclient.commands.plugins:PluginsList
plugins_sync=fuelclient.commands.plugins:PluginsSync
release_component_list=fuelclient.commands.release:ReleaseComponentList
release_list=fuelclient.commands.release:ReleaseList
release_repos_list=fuelclient.commands.release:ReleaseReposList
release_repos_update=fuelclient.commands.release:ReleaseReposUpdate