Merge "Add fuel2 commands to operate on extensions"

This commit is contained in:
Jenkins 2016-09-14 16:53:25 +00:00 committed by Gerrit Code Review
commit 253d7ebc25
13 changed files with 430 additions and 3 deletions

View File

@ -64,6 +64,7 @@ def get_client(resource, version='v1', connection=None):
'deployment_history': v1.deployment_history, 'deployment_history': v1.deployment_history,
'deployment-info': v1.deployment_info, 'deployment-info': v1.deployment_info,
'environment': v1.environment, 'environment': v1.environment,
'extension': v1.extension,
'fuel-version': v1.fuelversion, 'fuel-version': v1.fuelversion,
'graph': v1.graph, 'graph': v1.graph,
'network-configuration': v1.network_configuration, 'network-configuration': v1.network_configuration,

View File

@ -78,6 +78,10 @@ class BaseListCommand(lister.Lister, BaseCommand):
filters = {} filters = {}
@property
def default_sorting_by(self):
return ['id']
@abc.abstractproperty @abc.abstractproperty
def columns(self): def columns(self):
"""Names of columns in the resulting table.""" """Names of columns in the resulting table."""
@ -100,10 +104,11 @@ class BaseListCommand(lister.Lister, BaseCommand):
nargs='+', nargs='+',
choices=self.columns, choices=self.columns,
metavar='SORT_COLUMN', metavar='SORT_COLUMN',
default=['id'], default=self.default_sorting_by,
help='Space separated list of keys for sorting ' help='Space separated list of keys for sorting '
'the data. Defaults to id. Wrong values ' 'the data. Defaults to {}. Wrong values '
'are ignored.') 'are ignored.'.format(
', '.join(self.default_sorting_by)))
return parser return parser

View File

@ -0,0 +1,98 @@
# -*- 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 cliff import show
from fuelclient.commands import base
class ExtensionMixIn(object):
entity_name = 'extension'
class ExtensionList(ExtensionMixIn, base.BaseListCommand):
"""Show list of all available extensions."""
columns = ("name",
"version",
"description",
"provides")
default_sorting_by = ["name"]
class EnvExtensionShow(ExtensionMixIn, base.BaseShowCommand):
"""Show list of enabled extensions for environment with given id."""
columns = ("extensions", )
def get_parser(self, prog_name):
# Avoid adding id argument by BaseShowCommand
# Because it adds 'id' with wrong help message for this class
parser = show.ShowOne.get_parser(self, prog_name)
parser.add_argument('id', type=int, help='Id of the environment.')
return parser
class EnvExtensionEnable(ExtensionMixIn, base.BaseCommand):
"""Enable specified extensions for environment with given id."""
def get_parser(self, prog_name):
parser = super(EnvExtensionEnable, self).get_parser(prog_name)
parser.add_argument('id', type=int, help='Id of the environment.')
parser.add_argument('-E',
'--extensions',
required=True,
nargs='+',
help='Names of extensions to enable.')
return parser
def take_action(self, parsed_args):
self.client.enable_extensions(parsed_args.id, parsed_args.extensions)
msg = ('The following extensions: {e} have been enabled for '
'the environment with id {id}.\n'.format(
e=', '.join(parsed_args.extensions), id=parsed_args.id))
self.app.stdout.write(msg)
class EnvExtensionDisable(ExtensionMixIn, base.BaseCommand):
"""Disable specified extensions for environment with given id."""
def get_parser(self, prog_name):
parser = super(EnvExtensionDisable, self).get_parser(prog_name)
parser.add_argument('id', type=int, help='Id of the environment.')
parser.add_argument('-E',
'--extensions',
required=True,
nargs='+',
help='Names of extensions to disable.')
return parser
def take_action(self, parsed_args):
self.client.disable_extensions(parsed_args.id, parsed_args.extensions)
msg = ('The following extensions: {e} have been disabled for '
'the environment with id {id}.\n'.format(
e=', '.join(parsed_args.extensions), id=parsed_args.id))
self.app.stdout.write(msg)

View File

@ -18,6 +18,7 @@ functionality from nailgun objects.
from fuelclient.objects.base import BaseObject from fuelclient.objects.base import BaseObject
from fuelclient.objects.environment import Environment from fuelclient.objects.environment import Environment
from fuelclient.objects.extension import Extension
from fuelclient.objects.node import Node from fuelclient.objects.node import Node
from fuelclient.objects.node import NodeCollection from fuelclient.objects.node import NodeCollection
from fuelclient.objects.openstack_config import OpenstackConfig from fuelclient.objects.openstack_config import OpenstackConfig

View File

@ -0,0 +1,47 @@
# 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.objects.base import BaseObject
class Extension(BaseObject):
class_api_path = "extensions/"
instance_api_path = "clusters/{0}/extensions/"
@property
def extensions_url(self):
return self.instance_api_path.format(self.id)
def get_env_extensions(self):
"""Get list of extensions through request to the Nailgun API
"""
return self.connection.get_request(self.extensions_url)
def enable_env_extensions(self, extensions):
"""Enable extensions through request to the Nailgun API
:param extensions: list of extenstion to be enabled
"""
return self.connection.put_request(self.extensions_url, extensions)
def disable_env_extensions(self, extensions):
"""Disable extensions through request to the Nailgun API
:param extensions: list of extenstion to be disabled
"""
url = '{0}?extension_names={1}'.format(self.extensions_url,
','.join(extensions))
return self.connection.delete_request(url)

View File

@ -49,3 +49,34 @@ class TestDeployChanges(base.CLIv2TestCase):
self.check_for_stdout_by_regexp(self.cmd_redeploy_changes, self.check_for_stdout_by_regexp(self.cmd_redeploy_changes,
self.pattern_success) self.pattern_success)
class TestExtensionManagement(base.CLIv2TestCase):
cmd_create_env = "env create -r {0} cluster-test-extensions-mgmt"
cmd_disable_exts = "env extension disable 1 --extensions volume_manager"
cmd_enable_exts = "env extension enable 1 --extensions volume_manager"
pattern_enable_success = (r"^The following extensions: volume_manager "
r"have been enabled for the environment with "
r"id 1.\n$")
pattern_disable_success = (r"^The following extensions: volume_manager "
r"have been disabled for the environment with "
r"id 1.\n$")
def setUp(self):
super(TestExtensionManagement, self).setUp()
self.load_data_to_nailgun_server()
release_id = self.get_first_deployable_release_id()
self.cmd_create_env = self.cmd_create_env.format(release_id)
self.run_cli_commands((
self.cmd_create_env,
))
def test_disable_extensions(self):
self.check_for_stdout_by_regexp(self.cmd_disable_exts,
self.pattern_disable_success)
def test_enable_extensions(self):
self.check_for_stdout_by_regexp(self.cmd_enable_exts,
self.pattern_enable_success)

View File

@ -0,0 +1,79 @@
# -*- 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.
import mock
from fuelclient.tests.unit.v2.cli import test_engine
from fuelclient.tests import utils
class TestExtensionCommand(test_engine.BaseCLITest):
"""Tests for fuel2 extension * commands."""
def test_extensions_list(self):
self.m_client.get_all.return_value = utils.get_fake_extensions(2)
args = 'extension list'
self.exec_command(args)
self.m_client.get_all.assert_called_once_with()
self.m_get_client.assert_called_once_with('extension', mock.ANY)
def test_env_extensions_show(self):
self.m_client.get_extensions.return_value = \
utils.get_fake_env_extensions()
env_id = 45
args = 'env extension show {id}'.format(id=env_id)
self.exec_command(args)
self.m_client.get_by_id.assert_called_once_with(env_id)
self.m_get_client.assert_called_once_with('extension', mock.ANY)
@mock.patch('sys.stderr')
def test_env_extension_show_fail(self, mocked_stderr):
args = 'env extension show'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('id',
mocked_stderr.write.call_args_list[0][0][0])
@mock.patch('sys.stderr')
def test_env_extension_enable_fail(self, mocked_stderr):
args = 'env extension enable 1'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('-E/--extensions',
mocked_stderr.write.call_args_list[-1][0][0])
@mock.patch('sys.stderr')
def test_env_extension_disable_fail(self, mocked_stderr):
args = 'env extension disable 1'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('-E/--extensions',
mocked_stderr.write.call_args_list[-1][0][0])
def test_env_extensions_enable(self):
exts = utils.get_fake_env_extensions()
env_id = 45
args = 'env extension enable {id} --extensions {exts}'.format(
id=env_id, exts=' '.join(exts))
self.exec_command(args)
self.m_client.enable_extensions.assert_called_once_with(env_id, exts)
self.m_get_client.assert_called_once_with('extension', mock.ANY)
def test_env_extensions_disable(self):
exts = utils.get_fake_env_extensions()
env_id = 45
args = 'env extension disable {id} --extensions {exts}'.format(
id=env_id, exts=' '.join(exts))
self.exec_command(args)
self.m_client.disable_extensions.assert_called_once_with(env_id, exts)
self.m_get_client.assert_called_once_with('extension', mock.ANY)

View File

@ -0,0 +1,83 @@
# -*- 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.
import fuelclient
from fuelclient.tests.unit.v2.lib import test_api
from fuelclient.tests import utils
class TestExtensionFacade(test_api.BaseLibTest):
def setUp(self):
super(TestExtensionFacade, self).setUp()
self.version = 'v1'
self.res_uri = '/api/{version}/extensions/'.format(
version=self.version)
self.res_env_uri = '/api/{version}/clusters/'.format(
version=self.version)
self.fake_ext = ['fake_ext1']
self.fake_extensions = utils.get_fake_extensions(10)
self.fake_env_extensions = utils.get_fake_env_extensions()
self.client = fuelclient.get_client('extension', self.version)
def test_extension_list(self):
matcher = self.m_request.get(self.res_uri, json=self.fake_extensions)
self.client.get_all()
self.assertTrue(matcher.called)
def test_env_extension_list(self):
env_id = 42
expected_uri = self.get_object_uri(self.res_env_uri, env_id,
'/extensions/')
matcher = self.m_request.get(expected_uri,
json=self.fake_env_extensions)
extensions = self.client.get_by_id(env_id)
self.assertTrue(matcher.called)
for ext in self.fake_env_extensions:
self.assertIn(ext, extensions['extensions'])
def test_env_extension_enable(self):
env_id = 42
fake_ext = ['enabled_fake_ext4']
expected_uri = self.get_object_uri(self.res_env_uri, env_id,
'/extensions/')
put_matcher = self.m_request.put(expected_uri,
json=self.fake_env_extensions)
self.client.enable_extensions(env_id, fake_ext)
self.assertTrue(put_matcher.called)
self.assertIn(fake_ext[0], put_matcher.last_request.json())
def test_env_extension_disable(self):
env_id = 42
expected_uri = self.get_object_uri(
self.res_env_uri,
env_id,
'/extensions/?extension_names={0}'.format(
','.join(self.fake_env_extensions)))
delete_matcher = self.m_request.delete(expected_uri,
complete_qs=True,
json=self.fake_env_extensions)
self.client.disable_extensions(env_id, self.fake_env_extensions)
self.assertTrue(delete_matcher.called)

View File

@ -32,6 +32,9 @@ from fuelclient.tests.utils.fake_net_conf import get_fake_network_config
from fuelclient.tests.utils.fake_network_group import get_fake_network_group from fuelclient.tests.utils.fake_network_group import get_fake_network_group
from fuelclient.tests.utils.fake_node import get_fake_node from fuelclient.tests.utils.fake_node import get_fake_node
from fuelclient.tests.utils.fake_env import get_fake_env from fuelclient.tests.utils.fake_env import get_fake_env
from fuelclient.tests.utils.fake_extension import get_fake_env_extensions
from fuelclient.tests.utils.fake_extension import get_fake_extension
from fuelclient.tests.utils.fake_extension import get_fake_extensions
from fuelclient.tests.utils.fake_fuel_version import get_fake_fuel_version from fuelclient.tests.utils.fake_fuel_version import get_fake_fuel_version
from fuelclient.tests.utils.fake_task import get_fake_task from fuelclient.tests.utils.fake_task import get_fake_task
from fuelclient.tests.utils.fake_node_group import get_fake_node_group from fuelclient.tests.utils.fake_node_group import get_fake_node_group

View File

@ -0,0 +1,36 @@
# -*- 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.
def get_fake_env_extensions(names=None):
"""Create a list of fake extensions for particular env"""
return names or ['fake_ext1', 'fake_ext2', 'fake_ext3']
def get_fake_extension(name=None, version=None, provides=None,
description=None):
return {'name': name or 'fake_name',
'version': version or 'fake_version',
'provides': provides or ['fake_method_call'],
'description': description or 'fake_description',
}
def get_fake_extensions(extension_count, **kwargs):
"""Create a random fake list of extensions."""
return [get_fake_extension(**kwargs)
for _ in range(extension_count)]

View File

@ -16,6 +16,7 @@ from fuelclient.v1 import cluster_settings
from fuelclient.v1 import deployment_history from fuelclient.v1 import deployment_history
from fuelclient.v1 import deployment_info from fuelclient.v1 import deployment_info
from fuelclient.v1 import environment from fuelclient.v1 import environment
from fuelclient.v1 import extension
from fuelclient.v1 import fuelversion from fuelclient.v1 import fuelversion
from fuelclient.v1 import graph from fuelclient.v1 import graph
from fuelclient.v1 import network_configuration from fuelclient.v1 import network_configuration
@ -35,6 +36,7 @@ __all__ = ('cluster_settings',
'deployment_history', 'deployment_history',
'deployment_info', 'deployment_info',
'environment', 'environment',
'extension',
'fuelversion', 'fuelversion',
'graph', 'graph',
'network_configuration', 'network_configuration',

View File

@ -0,0 +1,37 @@
# 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 import objects
from fuelclient.v1 import base_v1
class ExtensionClient(base_v1.BaseV1Client):
_entity_wrapper = objects.Extension
def get_by_id(self, environment_id):
ext_obj = self._entity_wrapper(environment_id)
return {'extensions': ', '.join(ext_obj.get_env_extensions())}
def enable_extensions(self, environment_id, extensions):
ext_obj = self._entity_wrapper(environment_id)
return ext_obj.enable_env_extensions(extensions)
def disable_extensions(self, environment_id, extensions):
ext_obj = self._entity_wrapper(environment_id)
return ext_obj.disable_env_extensions(extensions)
def get_client(connection):
return ExtensionClient(connection)

View File

@ -37,6 +37,9 @@ fuelclient =
env_deployment-facts_download=fuelclient.commands.environment:EnvDeploymentFactsDownload env_deployment-facts_download=fuelclient.commands.environment:EnvDeploymentFactsDownload
env_deployment-facts_get-default=fuelclient.commands.environment:EnvDeploymentFactsGetDefault env_deployment-facts_get-default=fuelclient.commands.environment:EnvDeploymentFactsGetDefault
env_deployment-facts_upload=fuelclient.commands.environment:EnvDeploymentFactsUpload env_deployment-facts_upload=fuelclient.commands.environment:EnvDeploymentFactsUpload
env_extension_disable=fuelclient.commands.extension:EnvExtensionDisable
env_extension_enable=fuelclient.commands.extension:EnvExtensionEnable
env_extension_show=fuelclient.commands.extension:EnvExtensionShow
env_list=fuelclient.commands.environment:EnvList env_list=fuelclient.commands.environment:EnvList
env_network_download=fuelclient.commands.environment:EnvNetworkDownload env_network_download=fuelclient.commands.environment:EnvNetworkDownload
env_network_upload=fuelclient.commands.environment:EnvNetworkUpload env_network_upload=fuelclient.commands.environment:EnvNetworkUpload
@ -56,6 +59,7 @@ fuelclient =
env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms
env_stop-deployment=fuelclient.commands.environment:EnvStopDeploy env_stop-deployment=fuelclient.commands.environment:EnvStopDeploy
env_update=fuelclient.commands.environment:EnvUpdate env_update=fuelclient.commands.environment:EnvUpdate
extension_list=fuelclient.commands.extension:ExtensionList
fuel-version=fuelclient.commands.fuelversion:FuelVersion fuel-version=fuelclient.commands.fuelversion:FuelVersion
graph_download=fuelclient.commands.graph:GraphDownload graph_download=fuelclient.commands.graph:GraphDownload
graph_execute=fuelclient.commands.graph:GraphExecute graph_execute=fuelclient.commands.graph:GraphExecute