From 7ef1e9ea96ef15b63304a6bccaf30f8c269f2b76 Mon Sep 17 00:00:00 2001 From: Nakul Dahiwade Date: Fri, 11 Nov 2016 21:42:24 +0000 Subject: [PATCH] OSC Network Flavor Profile Implement Neutron feature of Network Flavor Profile into OpenstackClient This patch implements the following commands: network flavor profile create network flavor profile delete network flavor profile list network flavor profile show network flavor profile set SDK Version needed: 0.9.9 Change-Id: Ie6fe5e53122cfb2eda8d326851d54562739a8386 Partially-Implements: blueprint neutron-client-flavors --- .../network-flavor-profile.rst | 145 ++++++ doc/source/commands.rst | 1 + .../network/v2/network_flavor_profile.py | 250 ++++++++++ .../network/v2/test_network_flavor_profile.py | 151 ++++++ .../tests/unit/network/v2/fakes.py | 47 ++ .../network/v2/test_network_flavor_profile.py | 448 ++++++++++++++++++ ...twork-flavor-profile-e7cc5b353c3ed9d9.yaml | 9 + setup.cfg | 6 + 8 files changed, 1057 insertions(+) create mode 100644 doc/source/command-objects/network-flavor-profile.rst create mode 100644 openstackclient/network/v2/network_flavor_profile.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_flavor_profile.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_flavor_profile.py create mode 100644 releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml diff --git a/doc/source/command-objects/network-flavor-profile.rst b/doc/source/command-objects/network-flavor-profile.rst new file mode 100644 index 000000000..fdb95059b --- /dev/null +++ b/doc/source/command-objects/network-flavor-profile.rst @@ -0,0 +1,145 @@ +====================== +network flavor profile +====================== + +A **network flavor profile** allows administrators to create, delete, list, +show and update network service profile, which details a framework to enable +operators to configure and users to select from different abstract +representations of a service implementation in the Networking service. +It decouples the logical configuration from its instantiation enabling +operators to create user options according to deployment needs. + +Network v2 + +network flavor profile create +----------------------------- + +Create a new network flavor profile + +.. program:: network flavor profile create +.. code:: bash + + openstack network flavor profile create + [--project [--project-domain ]] + [--description ] + [--enable | --disable] + (--driver | --metainfo | --driver --metainfo ) + +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can + be used in case collisions between project names + exist + +.. option:: --description + + Description for the flavor profile + + *Network version 2 only* + +.. option:: --enable + + Enable the flavor profile (default) + +.. option:: --disable + + Disable the flavor profile + +.. option:: --driver + + Python module path to driver + + *Network version 2 only* + +.. option:: --metainfo + + Metainfo for the flavor profile + + *Network version 2 only* + + +network flavor profile delete +----------------------------- + +Delete network flavor profile + +.. program:: network flavor profile delete +.. code:: bash + + openstack network flavor profile delete + [ ...] + +.. describe:: + + Flavor profile(s) to delete (ID only) + +network flavor profile list +--------------------------- + +List network flavor profiles + +.. program:: network flavor profile list +.. code:: bash + + openstack network flavor profile list + +network flavor profile set +-------------------------- + +Set network flavor profile properties + +.. program:: network flavor profile set +.. code:: bash + + openstack network flavor profile set + [--description ] + [--driver ] + [--enable | --disable] + [--metainfo ] + + + +.. option:: --description + + Description of the flavor profile + +.. option:: --driver + + Python module path to driver + +.. option:: --enable (Default) + + Enable the flavor profile + +.. option:: --disable + + Disable the flavor profile + +.. option:: --metainfo + + Metainfo for the flavor profile + +.. describe:: + + Flavor profile to update (ID only) + +network flavor profile show +--------------------------- + +Show network flavor profile + +.. program:: network flavor profile show +.. code:: bash + + openstack network flavor profile show + + +.. describe:: + + Flavor profile to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index a0c67cd4c..ccd081e8d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -113,6 +113,7 @@ referring to both Compute and Volume quotas. * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network auto allocated topology``: (**Network**) - an auto-allocated topology for a project * ``network flavor``: (**Network**) - allows the user to choose the type of service by a set of advertised service capabilities (e.g., LOADBALANCER, FWAAS, L3, VPN, etc) rather than by a provider type or named vendor +* ``network flavor profile``: (**Network**) - predefined neutron service configurations: driver * ``network meter``: (**Network**) - allow traffic metering in a network * ``network meter rule``: (**Network**) - rules for network traffic metering * ``network rbac``: (**Network**) - an RBAC policy for network resources diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py new file mode 100644 index 000000000..6cf0c4124 --- /dev/null +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -0,0 +1,250 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_enabled': 'enabled', + 'tenant_id': 'project_id', + } + + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.driver is not None: + attrs['driver'] = parsed_args.driver + if parsed_args.metainfo is not None: + attrs['metainfo'] = parsed_args.metainfo + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +# TODO(ndahiwade): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateNetworkFlavorProfile(command.ShowOne): + _description = _("Create new network flavor profile") + + def get_parser(self, prog_name): + parser = super(CreateNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar="", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--description', + metavar="", + help=_("Description for the flavor profile") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor profile") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor profile") + ) + parser.add_argument( + '--driver', + help=_("Python module path to driver. This becomes " + "required if --metainfo is missing and vice versa") + ) + parser.add_argument( + '--metainfo', + help=_("Metainfo for the flavor profile. This becomes " + "required if --driver is missing and vice versa") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + + if parsed_args.driver is None and parsed_args.metainfo is None: + msg = _("Either --driver or --metainfo or both are required") + raise exceptions.CommandError(msg) + + obj = client.create_service_profile(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteNetworkFlavorProfile(command.Command): + _description = _("Delete network flavor profile") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkFlavorProfile, self).get_parser(prog_name) + + parser.add_argument( + 'flavor_profile', + metavar='', + nargs='+', + help=_("Flavor profile(s) to delete (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for flavor_profile in parsed_args.flavor_profile: + try: + obj = client.find_service_profile(flavor_profile, + ignore_missing=False) + client.delete_service_profile(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor profile with " + "ID '%(flavor_profile)s': %(e)s"), + {"flavor_profile": flavor_profile, "e": e}) + if result > 0: + total = len(parsed_args.flavor_profile) + msg = (_("%(result)s of %(total)s flavor_profiles failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListNetworkFlavorProfile(command.Lister): + _description = _("List network flavor profile(s)") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'driver', + 'is_enabled', + 'metainfo', + 'description', + ) + column_headers = ( + 'ID', + 'Driver', + 'Enabled', + 'Metainfo', + 'Description', + ) + + data = client.service_profiles() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +# TODO(ndahiwade): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class SetNetworkFlavorProfile(command.Command): + _description = _("Set network flavor profile properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor_profile', + metavar="", + help=_("Flavor profile to update (ID only)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--description', + metavar="", + help=_("Description for the flavor profile") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor profile") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor profile") + ) + parser.add_argument( + '--driver', + help=_("Python module path to driver. This becomes " + "required if --metainfo is missing and vice versa") + ) + parser.add_argument( + '--metainfo', + help=_("Metainfo for the flavor profile. This becomes " + "required if --driver is missing and vice versa") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_service_profile(parsed_args.flavor_profile, + ignore_missing=False) + attrs = _get_attrs(self.app.client_manager, parsed_args) + + client.update_service_profile(obj, **attrs) + + +class ShowNetworkFlavorProfile(command.ShowOne): + _description = _("Display network flavor profile details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor_profile', + metavar='', + help=_("Flavor profile to display (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_service_profile(parsed_args.flavor_profile, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py new file mode 100644 index 000000000..1a82c82b0 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py @@ -0,0 +1,151 @@ +# 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 json + +from openstackclient.tests.functional import base + + +class NetworkFlavorProfileTests(base.TestCase): + """Functional tests for network flavor-profile.""" + + DESCRIPTION = 'fakedescription' + METAINFO = 'Extrainfo' + + def test_network_flavor_profile_create(self): + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output.get('id') + self.assertIsNotNone(ID) + self.assertEqual( + True, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_list(self): + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID1 = json_output.get('id') + self.assertIsNotNone(ID1) + self.assertEqual( + True, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --disable --metainfo ' + self.METAINFO)) + ID2 = json_output.get('id') + self.assertIsNotNone(ID2) + self.assertEqual( + False, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Test list + json_output = json.loads(self.openstack( + 'network flavor profile list -f json')) + self.assertIsNotNone(json_output) + + id_list = [item.get('ID') for item in json_output] + self.assertIn(ID1, id_list) + self.assertIn(ID2, id_list) + + # Clean up + raw_output = self.openstack( + 'network flavor profile delete ' + ID1 + " " + ID2) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_set(self): + json_output_1 = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output_1.get('id') + self.assertIsNotNone(ID) + self.assertEqual( + True, + json_output_1.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output_1.get('description')) + self.assertEqual( + 'Extrainfo', + json_output_1.get('meta_info') + ) + + self.openstack('network flavor profile set --disable ' + ID) + + json_output = json.loads(self.openstack('network flavor profile show ' + '-f json ' + ID)) + self.assertEqual( + False, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_show(self): + json_output_1 = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output_1.get('id') + self.assertIsNotNone(ID) + json_output = json.loads(self.openstack('network flavor profile show ' + '-f json ' + ID)) + self.assertEqual( + ID, + json_output["id"]) + self.assertEqual( + True, + json_output["enabled"]) + self.assertEqual( + 'fakedescription', + json_output["description"]) + self.assertEqual( + 'Extrainfo', + json_output["meta_info"]) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 7afe33288..a2274a34e 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -769,6 +769,53 @@ class FakeNetworkRBAC(object): return mock.Mock(side_effect=rbac_policies) +class FakeNetworkFlavorProfile(object): + """Fake network flavor profile.""" + + @staticmethod + def create_one_service_profile(attrs=None): + """Create flavor profile.""" + attrs = attrs or {} + + flavor_profile_attrs = { + 'id': 'flavor-profile-id' + uuid.uuid4().hex, + 'description': 'flavor-profile-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'driver': 'driver-' + uuid.uuid4().hex, + 'metainfo': 'metainfo-' + uuid.uuid4().hex, + 'enabled': True + } + + flavor_profile_attrs.update(attrs) + + flavor_profile = fakes.FakeResource( + info=copy.deepcopy(flavor_profile_attrs), + loaded=True) + + flavor_profile.project_id = flavor_profile_attrs['tenant_id'] + flavor_profile.is_enabled = flavor_profile_attrs['enabled'] + + return flavor_profile + + @staticmethod + def create_service_profile(attrs=None, count=2): + """Create multiple flavor profiles.""" + + flavor_profiles = [] + for i in range(0, count): + flavor_profiles.append(FakeNetworkFlavorProfile. + create_one_service_profile(attrs)) + return flavor_profiles + + @staticmethod + def get_service_profile(flavor_profile=None, count=2): + """Get a list of flavor profiles.""" + if flavor_profile is None: + flavor_profile = (FakeNetworkFlavorProfile. + create_service_profile(count)) + return mock.Mock(side_effect=flavor_profile) + + class FakeNetworkQosPolicy(object): """Fake one or more QoS policies.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py new file mode 100644 index 000000000..91683241c --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -0,0 +1,448 @@ +# 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 osc_lib import exceptions + +from openstackclient.network.v2 import network_flavor_profile +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestFlavorProfile(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFlavorProfile, self).setUp() + # Get the network client + self.network = self.app.client_manager.network + # Get the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateFlavorProfile(TestFlavorProfile): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + new_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile. + create_one_service_profile() + ) + columns = ( + 'description', + 'driver', + 'enabled', + 'id', + 'metainfo', + 'project_id', + ) + + data = ( + new_flavor_profile.description, + new_flavor_profile.driver, + new_flavor_profile.enabled, + new_flavor_profile.id, + new_flavor_profile.metainfo, + new_flavor_profile.project_id, + ) + + def setUp(self): + super(TestCreateFlavorProfile, self).setUp() + self.network.create_service_profile = mock.Mock( + return_value=self.new_flavor_profile) + self.projects_mock.get.return_value = self.project + # Get the command object to test + self.cmd = (network_flavor_profile.CreateNetworkFlavorProfile( + self.app, self.namespace)) + + def test_create_all_options(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--driver", self.new_flavor_profile.driver, + "--metainfo", self.new_flavor_profile.metainfo, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('driver', self.new_flavor_profile.driver), + ('metainfo', self.new_flavor_profile.metainfo) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'driver': self.new_flavor_profile.driver, + 'metainfo': self.new_flavor_profile.metainfo} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_metainfo(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--metainfo", self.new_flavor_profile.metainfo, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('metainfo', self.new_flavor_profile.metainfo) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'metainfo': self.new_flavor_profile.metainfo} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_driver(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--driver", self.new_flavor_profile.driver, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('driver', self.new_flavor_profile.driver), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'driver': self.new_flavor_profile.driver, + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_without_driver_and_metainfo(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + def test_create_disable(self): + arglist = [ + '--disable', + '--driver', self.new_flavor_profile.driver, + ] + verifylist = [ + ('disable', True), + ('driver', self.new_flavor_profile.driver) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_service_profile.assert_called_once_with(**{ + 'enabled': False, + 'driver': self.new_flavor_profile.driver, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteFlavorProfile(TestFlavorProfile): + + # The network flavor_profiles to delete. + _network_flavor_profiles = ( + network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + + def setUp(self): + super(TestDeleteFlavorProfile, self).setUp() + self.network.delete_service_profile = mock.Mock(return_value=None) + self.network.find_service_profile = ( + network_fakes.FakeNetworkFlavorProfile.get_service_profile( + flavor_profile=self._network_flavor_profiles) + ) + + # Get the command object to test + self.cmd = network_flavor_profile.DeleteNetworkFlavorProfile( + self.app, self.namespace) + + def test_network_flavor_profile_delete(self): + arglist = [ + self._network_flavor_profiles[0].id, + ] + verifylist = [ + ('flavor_profile', [self._network_flavor_profiles[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_service_profile.assert_called_once_with( + self._network_flavor_profiles[0].id, ignore_missing=False) + self.network.delete_service_profile.assert_called_once_with( + self._network_flavor_profiles[0]) + self.assertIsNone(result) + + def test_multi_network_flavor_profiles_delete(self): + arglist = [] + + for a in self._network_flavor_profiles: + arglist.append(a.id) + verifylist = [ + ('flavor_profile', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._network_flavor_profiles: + calls.append(mock.call(a)) + self.network.delete_service_profile.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_flavor_profiles_delete_with_exception(self): + arglist = [ + self._network_flavor_profiles[0].id, + 'unexist_network_flavor_profile', + ] + verifylist = [ + ('flavor_profile', + [self._network_flavor_profiles[0].id, + 'unexist_network_flavor_profile']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_flavor_profiles[0], + exceptions.CommandError] + self.network.find_service_profile = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavor_profiles failed to delete.', + str(e)) + + self.network.find_service_profile.assert_any_call( + self._network_flavor_profiles[0].id, ignore_missing=False) + self.network.find_service_profile.assert_any_call( + 'unexist_network_flavor_profile', ignore_missing=False) + self.network.delete_service_profile.assert_called_once_with( + self._network_flavor_profiles[0] + ) + + +class TestListFlavorProfile(TestFlavorProfile): + + # The network flavor profiles list + _network_flavor_profiles = ( + network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + + columns = ( + 'ID', + 'Driver', + 'Enabled', + 'Metainfo', + 'Description', + ) + data = [] + for flavor_profile in _network_flavor_profiles: + data.append(( + flavor_profile.id, + flavor_profile.driver, + flavor_profile.enabled, + flavor_profile.metainfo, + flavor_profile.description, + )) + + def setUp(self): + super(TestListFlavorProfile, self).setUp() + self.network.service_profiles = mock.Mock( + return_value=self._network_flavor_profiles) + + # Get the command object to test + self.cmd = network_flavor_profile.ListNetworkFlavorProfile( + self.app, self.namespace) + + def test_network_flavor_profile_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.service_profiles.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowFlavorProfile(TestFlavorProfile): + + # The network flavor profile to show. + network_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + columns = ( + 'description', + 'driver', + 'enabled', + 'id', + 'metainfo', + 'project_id', + ) + data = ( + network_flavor_profile.description, + network_flavor_profile.driver, + network_flavor_profile.enabled, + network_flavor_profile.id, + network_flavor_profile.metainfo, + network_flavor_profile.project_id, + ) + + def setUp(self): + super(TestShowFlavorProfile, self).setUp() + self.network.find_service_profile = mock.Mock( + return_value=self.network_flavor_profile) + + # Get the command object to test + self.cmd = network_flavor_profile.ShowNetworkFlavorProfile( + self.app, self.namespace) + + def test_show_all_options(self): + arglist = [ + self.network_flavor_profile.id, + ] + verifylist = [ + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_service_profile.assert_called_once_with( + self.network_flavor_profile.id, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSetFlavorProfile(TestFlavorProfile): + + # The network flavor profile to set. + network_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + + def setUp(self): + super(TestSetFlavorProfile, self).setUp() + self.network.update_service_profile = mock.Mock(return_value=None) + self.network.find_service_profile = mock.Mock( + return_value=self.network_flavor_profile) + + # Get the command object to test + self.cmd = network_flavor_profile.SetNetworkFlavorProfile( + self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self.network_flavor_profile.id] + verifylist = [ + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) + + def test_set_enable(self): + arglist = [ + '--enable', + self.network_flavor_profile.id, + ] + verifylist = [ + ('enable', True), + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': True, + } + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) + + def test_set_disable(self): + arglist = [ + '--disable', + self.network_flavor_profile.id, + ] + verifylist = [ + ('disable', True), + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': False, + } + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml b/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml new file mode 100644 index 000000000..b604762dc --- /dev/null +++ b/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add support for Network Flavor Profile commands: + ``network flavor profile create``, ``network flavor profile delete``, + ``network flavor profile list``, ``network flavor profile show`` and + ``network flavor profile set`` + [Blueprint :oscbp:`neutron-client-flavors`] + diff --git a/setup.cfg b/setup.cfg index e18aa5c1e..87251a909 100644 --- a/setup.cfg +++ b/setup.cfg @@ -369,6 +369,12 @@ openstack.network.v2 = network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor + network_flavor_profile_create = openstackclient.network.v2.network_flavor_profile:CreateNetworkFlavorProfile + network_flavor_profile_delete = openstackclient.network.v2.network_flavor_profile:DeleteNetworkFlavorProfile + network_flavor_profile_list = openstackclient.network.v2.network_flavor_profile:ListNetworkFlavorProfile + network_flavor_profile_set = openstackclient.network.v2.network_flavor_profile:SetNetworkFlavorProfile + network_flavor_profile_show = openstackclient.network.v2.network_flavor_profile:ShowNetworkFlavorProfile + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork