From edaeece7f144545bff9a7b00fccd2ec598ee2144 Mon Sep 17 00:00:00 2001
From: Anindita Das <anindita.das@intel.com>
Date: Wed, 5 Oct 2016 15:57:53 +0000
Subject: [PATCH] OSC Network Flavor

Implements Neutron feature of Network Flavor into OpenstackClient
This patch implements the following commands:
network flavor create
network flavor delete
network flavor list
network flavor show
network flavor set

Works with openstacksdk version 0.9.8

Change-Id: I29d7a62341010a1d067a8ca93bccb7d9b8d4c425
Partially-Implements: blueprint neutron-client-flavors
Partially-Implements: blueprint network-commands-options
---
 doc/source/command-objects/network-flavor.rst | 133 ++++++
 .../network-service-provider.rst              |   1 +
 doc/source/commands.rst                       |   1 +
 openstackclient/network/v2/network_flavor.py  | 247 +++++++++++
 .../network/v2/test_network_flavor.py         | 153 +++++++
 .../tests/unit/network/v2/fakes.py            |  63 +++
 .../unit/network/v2/test_network_flavor.py    | 407 ++++++++++++++++++
 ...avor-command-support-afe3a9da962a09bf.yaml |   7 +
 setup.cfg                                     |   6 +
 9 files changed, 1018 insertions(+)
 create mode 100644 doc/source/command-objects/network-flavor.rst
 create mode 100644 openstackclient/network/v2/network_flavor.py
 create mode 100644 openstackclient/tests/functional/network/v2/test_network_flavor.py
 create mode 100644 openstackclient/tests/unit/network/v2/test_network_flavor.py
 create mode 100644 releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml

diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst
new file mode 100644
index 0000000000..1324499e7d
--- /dev/null
+++ b/doc/source/command-objects/network-flavor.rst
@@ -0,0 +1,133 @@
+==============
+network flavor
+==============
+
+A **network flavor** extension allows the user selection of operator-curated
+flavors during resource creations. It allows administrators to create network
+service flavors.
+
+Network v2
+
+.. _network_flavor_create:
+network flavor create
+---------------------
+
+Create network flavor
+
+.. program:: network flavor create
+.. code:: bash
+
+    openstack network flavor create
+        --service-type <service-type>
+        [--description <description>]
+        [--enable | --disable]
+        [--project <project> [--project-domain <project-domain>]]
+        <name>
+
+.. option:: --service-type <service-type>
+
+    Service type to which the flavor applies to: e.g. VPN.
+    (See openstack :ref:'network_service_provider_list` (required)
+
+.. option:: --description <description>
+
+    Description for the flavor
+
+.. option:: --enable
+
+    Enable the flavor (default)
+
+.. option:: --disable
+
+    Disable the flavor
+
+.. option:: --project <project>
+
+    Owner's project (name or ID)
+
+.. option:: --project-domain <project-domain>
+
+    Domain the project belongs to (name or ID). This can
+    be used in case collisions between project names
+    exist.
+
+.. describe:: <name>
+
+    Name for the flavor
+
+.. _network_flavor_delete:
+network flavor delete
+---------------------
+
+Delete network flavor(s)
+
+.. program:: network flavor delete
+.. code:: bash
+
+    openstack network flavor delete
+        <flavor> [<flavor> ...]
+
+.. describe:: <flavor>
+
+    Flavor(s) to delete (name or ID)
+
+network flavor list
+-------------------
+
+List network flavors
+
+.. program:: network flavor list
+.. code:: bash
+
+    openstack network flavor list
+
+.. _network_flavor_set:
+network flavor set
+------------------
+
+Set network flavor properties
+
+.. program:: network flavor set
+.. code:: bash
+
+    openstack network flavor set
+        [--name <name>]
+        [--description <description>]
+        [--enable | --disable]
+        <flavor>
+
+.. option:: --name <name>
+
+    Set flavor name
+
+.. option:: --description <description>
+
+    Set network flavor description
+
+.. option:: --enable
+
+    Enable network flavor
+
+.. option:: --disable
+
+    Disable network flavor
+
+.. describe:: <flavor>
+
+    Flavor to update (name or ID)
+
+.. _network_flavor_show:
+network flavor show
+-------------------
+
+Show network flavor
+
+.. program:: network flavor show
+.. code:: bash
+
+    openstack network flavor show
+        <flavor>
+
+.. describe:: <flavor>
+
+    Flavor to display (name or ID)
diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst
index 8db2ab448b..8ccec45505 100644
--- a/doc/source/command-objects/network-service-provider.rst
+++ b/doc/source/command-objects/network-service-provider.rst
@@ -7,6 +7,7 @@ networking service
 
 Network v2
 
+.. _network_service_provider_list:
 network service provider list
 -----------------------------
 
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index ece9b34e3f..68c50d24d1 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -111,6 +111,7 @@ referring to both Compute and Volume quotas.
 * ``module``: (**Internal**) - installed Python modules in the OSC process
 * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources
 * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks
+* ``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 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.py b/openstackclient/network/v2/network_flavor.py
new file mode 100644
index 0000000000..3a3324c09d
--- /dev/null
+++ b/openstackclient/network/v2/network_flavor.py
@@ -0,0 +1,247 @@
+#   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.
+#
+
+"""Flavor action implementations"""
+
+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 = {}
+    attrs['name'] = parsed_args.name
+    attrs['service_type'] = parsed_args.service_type
+    if parsed_args.description is not None:
+        attrs['description'] = parsed_args.description
+    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(dasanind): Use the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class CreateNetworkFlavor(command.ShowOne):
+    _description = _("Create new network flavor")
+
+    def get_parser(self, prog_name):
+        parser = super(CreateNetworkFlavor, self).get_parser(prog_name)
+        parser.add_argument(
+            'name',
+            metavar="<name>",
+            help=_("Name for the flavor")
+        )
+        parser.add_argument(
+            '--service-type',
+            metavar="<service-type>",
+            required=True,
+            help=_('Service type to which the flavor applies to: e.g. VPN '
+                   '(See openstack network service provider list for loaded '
+                   'examples.)')
+        )
+        parser.add_argument(
+            '--description',
+            help=_('Description for the flavor')
+        )
+        parser.add_argument(
+            '--project',
+            metavar="<project>",
+            help=_("Owner's project (name or ID)")
+        )
+        identity_common.add_project_domain_option_to_parser(parser)
+
+        enable_group = parser.add_mutually_exclusive_group()
+        enable_group.add_argument(
+            '--enable',
+            action='store_true',
+            help=_("Enable the flavor (default)")
+        )
+        enable_group.add_argument(
+            '--disable',
+            action='store_true',
+            help=_("Disable the flavor")
+        )
+
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        attrs = _get_attrs(self.app.client_manager, parsed_args)
+        obj = client.create_flavor(**attrs)
+        display_columns, columns = _get_columns(obj)
+        data = utils.get_item_properties(obj, columns, formatters={})
+
+        return (display_columns, data)
+
+
+class DeleteNetworkFlavor(command.Command):
+    _description = _("Delete network flavors")
+
+    def get_parser(self, prog_name):
+        parser = super(DeleteNetworkFlavor, self).get_parser(prog_name)
+
+        parser.add_argument(
+            'flavor',
+            metavar='<flavor>',
+            nargs='+',
+            help=_('Flavor(s) to delete (name or ID)')
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        result = 0
+
+        for flavor in parsed_args.flavor:
+            try:
+                obj = client.find_flavor(flavor, ignore_missing=False)
+                client.delete_flavor(obj)
+            except Exception as e:
+                result += 1
+                LOG.error(_("Failed to delete flavor with "
+                            "name or ID '%(flavor)s': %(e)s"),
+                          {"flavor": flavor, "e": e})
+        if result > 0:
+            total = len(parsed_args.flavor)
+            msg = (_("%(result)s of %(total)s flavors failed "
+                     "to delete.") % {"result": result, "total": total})
+            raise exceptions.CommandError(msg)
+
+
+class ListNetworkFlavor(command.Lister):
+    _description = _("List network flavors")
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+
+        columns = (
+            'id',
+            'name',
+            'is_enabled',
+            'service_type',
+            'description'
+        )
+        column_headers = (
+            'ID',
+            'Name',
+            'Enabled',
+            'Service Type',
+            'Description'
+        )
+
+        data = client.flavors()
+        return (column_headers,
+                (utils.get_item_properties(
+                    s, columns,
+                ) for s in data))
+
+
+# TODO(dasanind): Use only the SDK resource mapped attribute names once the
+# OSC minimum requirements include SDK 1.0.
+class SetNetworkFlavor(command.Command):
+    _description = _("Set network flavor properties")
+
+    def get_parser(self, prog_name):
+        parser = super(SetNetworkFlavor, self).get_parser(prog_name)
+        parser.add_argument(
+            'flavor',
+            metavar="<flavor>",
+            help=_("Flavor to update (name or ID)")
+        )
+        parser.add_argument(
+            '--description',
+            help=_('Set network flavor description')
+        )
+        enable_group = parser.add_mutually_exclusive_group()
+        enable_group.add_argument(
+            '--disable',
+            action='store_true',
+            help=_("Disable network flavor")
+        )
+        enable_group.add_argument(
+            '--enable',
+            action='store_true',
+            help=_("Enable network flavor")
+        )
+        parser.add_argument(
+            '--name',
+            metavar="<name>",
+            help=_('Set flavor name')
+        )
+
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        obj = client.find_flavor(
+            parsed_args.flavor,
+            ignore_missing=False)
+        attrs = {}
+        if parsed_args.name is not None:
+            attrs['name'] = parsed_args.name
+        if parsed_args.description is not None:
+            attrs['description'] = parsed_args.description
+        if parsed_args.enable:
+            attrs['enabled'] = True
+        if parsed_args.disable:
+            attrs['enabled'] = False
+        client.update_flavor(obj, **attrs)
+
+
+class ShowNetworkFlavor(command.ShowOne):
+    _description = _("Display network flavor details")
+
+    def get_parser(self, prog_name):
+        parser = super(ShowNetworkFlavor, self).get_parser(prog_name)
+        parser.add_argument(
+            'flavor',
+            metavar='<flavor>',
+            help=_('Flavor to display (name or ID)')
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        obj = client.find_flavor(parsed_args.flavor, 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.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py
new file mode 100644
index 0000000000..e37a7bc71f
--- /dev/null
+++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py
@@ -0,0 +1,153 @@
+#    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 re
+import uuid
+
+from openstackclient.tests.functional import base
+
+
+class NetworkFlavorTests(base.TestCase):
+    """Functional tests for network flavor."""
+
+    @classmethod
+    def setUpClass(cls):
+        # Set up some regex for matching below
+        cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|")
+        cls.re_enabled = re.compile("enabled\s+\|\s+(\S+)")
+        cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|")
+        cls.SERVICE_TYPE = 'L3_ROUTER_NAT'
+
+    def test_network_flavor_delete(self):
+        """Test create, delete multiple"""
+        name1 = uuid.uuid4().hex
+        raw_output = self.openstack(
+            'network flavor create --description testdescription --enable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name1,
+        )
+        self.assertEqual(
+            name1,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'True',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
+
+        name2 = uuid.uuid4().hex
+        raw_output = self.openstack(
+            'network flavor create --description testdescription1 --disable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name2,
+        )
+        self.assertEqual(
+            name2,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'False',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription1',
+            re.search(self.re_description, raw_output).group(1))
+
+        raw_output = self.openstack(
+            'network flavor delete ' + name1 + " " + name2)
+        self.assertOutput('', raw_output)
+
+    def test_network_flavor_list(self):
+        name1 = uuid.uuid4().hex
+        raw_output = self.openstack(
+            'network flavor create --description testdescription --enable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name1,
+        )
+        self.addCleanup(self.openstack, "network flavor delete " + name1)
+        self.assertEqual(
+            name1,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'True',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
+
+        name2 = uuid.uuid4().hex
+        raw_output = self.openstack(
+            'network flavor create --description testdescription --disable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name2,
+        )
+        self.assertEqual(
+            name2,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'False',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
+        self.addCleanup(self.openstack, "network flavor delete " + name2)
+
+        # Test list
+        raw_output = self.openstack('network flavor list')
+        self.assertIsNotNone(raw_output)
+        self.assertIsNotNone(re.search(name1, raw_output))
+        self.assertIsNotNone(re.search(name2, raw_output))
+
+    def test_network_flavor_set(self):
+        name = uuid.uuid4().hex
+        newname = name + "_"
+        raw_output = self.openstack(
+            'network flavor create --description testdescription --enable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name,
+        )
+        self.addCleanup(self.openstack, "network flavor delete " + newname)
+        self.assertEqual(
+            name,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'True',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
+
+        self.openstack(
+            'network flavor set --name ' + newname + ' --disable ' + name
+        )
+        raw_output = self.openstack('network flavor show ' + newname)
+        self.assertEqual(
+            newname,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'False',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
+
+    def test_network_flavor_show(self):
+        name = uuid.uuid4().hex
+        self.openstack(
+            'network flavor create --description testdescription --enable'
+            ' --service-type ' + self.SERVICE_TYPE + ' ' + name,
+        )
+        self.addCleanup(self.openstack, "network flavor delete " + name)
+        raw_output = self.openstack('network flavor show ' + name)
+        self.assertEqual(
+            name,
+            re.search(self.re_name, raw_output).group(1))
+        self.assertEqual(
+            'True',
+            re.search(self.re_enabled, raw_output).group(1))
+        self.assertEqual(
+            'testdescription',
+            re.search(self.re_description, raw_output).group(1))
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index dcecbeee80..6a73b7e9b7 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -390,6 +390,69 @@ class FakeNetwork(object):
         return mock.Mock(side_effect=networks)
 
 
+class FakeNetworkFlavor(object):
+    """Fake Network Flavor."""
+
+    @staticmethod
+    def create_one_network_flavor(attrs=None):
+        """Create a fake network flavor.
+
+        :param Dictionary attrs:
+            A dictionary with all attributes
+        :return:
+            A FakeResource object faking the network flavor
+        """
+        attrs = attrs or {}
+
+        fake_uuid = uuid.uuid4().hex
+        network_flavor_attrs = {
+            'description': 'network-flavor-description-' + fake_uuid,
+            'enabled': True,
+            'id': 'network-flavor-id-' + fake_uuid,
+            'name': 'network-flavor-name-' + fake_uuid,
+            'service_type': 'vpn',
+            'tenant_id': 'project-id-' + uuid.uuid4().hex,
+        }
+
+        # Overwrite default attributes.
+        network_flavor_attrs.update(attrs)
+
+        network_flavor = fakes.FakeResource(
+            info=copy.deepcopy(network_flavor_attrs),
+            loaded=True
+        )
+
+        network_flavor.project_id = network_flavor_attrs['tenant_id']
+        network_flavor.is_enabled = network_flavor_attrs['enabled']
+
+        return network_flavor
+
+    @staticmethod
+    def create_flavor(attrs=None, count=2):
+        """Create multiple fake network flavors.
+
+        :param Dictionary attrs:
+            A dictionary with all attributes
+        :param int count:
+            The number of network flavors to fake
+        :return:
+            A list of FakeResource objects faking the network falvors
+        """
+        network_flavors = []
+        for i in range(0, count):
+            network_flavors.append(
+                FakeNetworkFlavor.create_one_network_flavor(attrs)
+            )
+        return network_flavors
+
+    @staticmethod
+    def get_flavor(network_flavors=None, count=2):
+        """Get a list of flavors."""
+        if network_flavors is None:
+            network_flavors = (FakeNetworkFlavor.create_flavor(count))
+        return mock.Mock(side_effect=network_flavors)
+
+
 class FakeNetworkSegment(object):
     """Fake one or more network segments."""
 
diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py
new file mode 100644
index 0000000000..11e27841d4
--- /dev/null
+++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py
@@ -0,0 +1,407 @@
+# Copyright (c) 2016, Intel Corporation.
+# All Rights Reserved.
+#
+#   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
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.unit.network.v2 import fakes as network_fakes
+from openstackclient.tests.unit import utils as tests_utils
+
+
+class TestNetworkFlavor(network_fakes.TestNetworkV2):
+
+    def setUp(self):
+        super(TestNetworkFlavor, self).setUp()
+
+        # Get a shortcut to the network client
+        self.network = self.app.client_manager.network
+        # Get a shortcut to the ProjectManager Mock
+        self.projects_mock = self.app.client_manager.identity.projects
+        # Get a shortcut to the DomainManager Mock
+        self.domains_mock = self.app.client_manager.identity.domains
+
+
+class TestCreateNetworkFlavor(TestNetworkFlavor):
+
+    project = identity_fakes_v3.FakeProject.create_one_project()
+    domain = identity_fakes_v3.FakeDomain.create_one_domain()
+    # The new network flavor created.
+    new_network_flavor = (
+        network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+    columns = (
+        'description',
+        'enabled',
+        'id',
+        'name',
+        'project_id',
+        'service_type'
+    )
+    data = (
+        new_network_flavor.description,
+        new_network_flavor.enabled,
+        new_network_flavor.id,
+        new_network_flavor.name,
+        new_network_flavor.project_id,
+        new_network_flavor.service_type,
+    )
+
+    def setUp(self):
+        super(TestCreateNetworkFlavor, self).setUp()
+        self.network.create_flavor = mock.Mock(
+            return_value=self.new_network_flavor)
+
+        # Get the command object to test
+        self.cmd = network_flavor.CreateNetworkFlavor(self.app, self.namespace)
+
+        self.projects_mock.get.return_value = self.project
+        self.domains_mock.get.return_value = self.domain
+
+    def test_create_no_options(self):
+        arglist = []
+        verifylist = []
+
+        # Missing required args should bail here
+        self.assertRaises(tests_utils.ParserException, self.check_parser,
+                          self.cmd, arglist, verifylist)
+
+    def test_create_default_options(self):
+        arglist = [
+            '--service-type', self.new_network_flavor.service_type,
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('service_type', self.new_network_flavor.service_type),
+            ('name', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = (self.cmd.take_action(parsed_args))
+
+        self.network.create_flavor.assert_called_once_with(**{
+            'service_type': self.new_network_flavor.service_type,
+            'name': self.new_network_flavor.name,
+        })
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
+
+    def test_create_all_options(self):
+        arglist = [
+            '--description', self.new_network_flavor.description,
+            '--enable',
+            '--project', self.new_network_flavor.project_id,
+            '--project-domain', self.domain.name,
+            '--service-type', self.new_network_flavor.service_type,
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('description', self.new_network_flavor.description),
+            ('enable', True),
+            ('project', self.new_network_flavor.project_id),
+            ('project_domain', self.domain.name),
+            ('service_type', self.new_network_flavor.service_type),
+            ('name', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = (self.cmd.take_action(parsed_args))
+
+        self.network.create_flavor.assert_called_once_with(**{
+            'description': self.new_network_flavor.description,
+            'enabled': True,
+            'tenant_id': self.project.id,
+            'service_type': self.new_network_flavor.service_type,
+            'name': self.new_network_flavor.name,
+        })
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
+
+    def test_create_disable(self):
+        arglist = [
+            '--disable',
+            '--service-type', self.new_network_flavor.service_type,
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('disable', True),
+            ('service_type', self.new_network_flavor.service_type),
+            ('name', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.create_flavor.assert_called_once_with(**{
+            'enabled': False,
+            'service_type': self.new_network_flavor.service_type,
+            'name': self.new_network_flavor.name,
+        })
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
+
+
+class TestDeleteNetworkFlavor(TestNetworkFlavor):
+
+    # The network flavor to delete.
+    _network_flavors = (
+        network_fakes.FakeNetworkFlavor.create_flavor(count=2))
+
+    def setUp(self):
+        super(TestDeleteNetworkFlavor, self).setUp()
+        self.network.delete_flavor = mock.Mock(return_value=None)
+        self.network.find_flavor = (
+            network_fakes.FakeNetworkFlavor.get_flavor(
+                network_flavors=self._network_flavors)
+        )
+
+        # Get the command object to test
+        self.cmd = network_flavor.DeleteNetworkFlavor(self.app, self.namespace)
+
+    def test_network_flavor_delete(self):
+        arglist = [
+            self._network_flavors[0].name,
+        ]
+        verifylist = [
+            ('flavor', [self._network_flavors[0].name]),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+        self.network.find_flavor.assert_called_once_with(
+            self._network_flavors[0].name, ignore_missing=False)
+        self.network.delete_flavor.assert_called_once_with(
+            self._network_flavors[0])
+        self.assertIsNone(result)
+
+    def test_multi_network_flavors_delete(self):
+        arglist = []
+        verifylist = []
+
+        for a in self._network_flavors:
+            arglist.append(a.name)
+        verifylist = [
+            ('flavor', arglist),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        calls = []
+        for a in self._network_flavors:
+            calls.append(mock.call(a))
+        self.network.delete_flavor.assert_has_calls(calls)
+        self.assertIsNone(result)
+
+    def test_multi_network_flavors_delete_with_exception(self):
+        arglist = [
+            self._network_flavors[0].name,
+            'unexist_network_flavor',
+        ]
+        verifylist = [
+            ('flavor',
+             [self._network_flavors[0].name, 'unexist_network_flavor']),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        find_mock_result = [self._network_flavors[0], exceptions.CommandError]
+        self.network.find_flavor = (
+            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 flavors failed to delete.', str(e))
+
+        self.network.find_flavor.assert_any_call(
+            self._network_flavors[0].name, ignore_missing=False)
+        self.network.find_flavor.assert_any_call(
+            'unexist_network_flavor', ignore_missing=False)
+        self.network.delete_flavor.assert_called_once_with(
+            self._network_flavors[0]
+        )
+
+
+class TestListNetworkFlavor(TestNetworkFlavor):
+
+    # The network flavors to list up.
+    _network_flavors = (
+        network_fakes.FakeNetworkFlavor.create_flavor(count=2))
+    columns = (
+        'ID',
+        'Name',
+        'Enabled',
+        'Service Type',
+        'Description',
+    )
+    data = []
+    for flavor in _network_flavors:
+        data.append((
+            flavor.id,
+            flavor.name,
+            flavor.enabled,
+            flavor.service_type,
+            flavor.description,
+        ))
+
+    def setUp(self):
+        super(TestListNetworkFlavor, self).setUp()
+        self.network.flavors = mock.Mock(
+            return_value=self._network_flavors)
+
+        # Get the command object to test
+        self.cmd = network_flavor.ListNetworkFlavor(self.app, self.namespace)
+
+    def test_network_flavor_list(self):
+        arglist = []
+        verifylist = []
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.flavors.assert_called_once_with(**{})
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, list(data))
+
+
+class TestShowNetworkFlavor(TestNetworkFlavor):
+
+    # The network flavor to show.
+    new_network_flavor = (
+        network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+    columns = (
+        'description',
+        'enabled',
+        'id',
+        'name',
+        'project_id',
+        'service_type'
+    )
+    data = (
+        new_network_flavor.description,
+        new_network_flavor.enabled,
+        new_network_flavor.id,
+        new_network_flavor.name,
+        new_network_flavor.project_id,
+        new_network_flavor.service_type,
+    )
+
+    def setUp(self):
+        super(TestShowNetworkFlavor, self).setUp()
+        self.network.find_flavor = mock.Mock(
+            return_value=self.new_network_flavor)
+
+        # Get the command object to test
+        self.cmd = network_flavor.ShowNetworkFlavor(self.app, self.namespace)
+
+    def test_show_no_options(self):
+        arglist = []
+        verifylist = []
+
+        # Missing required args should bail here
+        self.assertRaises(tests_utils.ParserException, self.check_parser,
+                          self.cmd, arglist, verifylist)
+
+    def test_show_all_options(self):
+        arglist = [
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('flavor', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.find_flavor.assert_called_once_with(
+            self.new_network_flavor.name, ignore_missing=False)
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
+
+
+class TestSetNetworkFlavor(TestNetworkFlavor):
+
+    # The network flavor to set.
+    new_network_flavor = (
+        network_fakes.FakeNetworkFlavor.create_one_network_flavor())
+
+    def setUp(self):
+        super(TestSetNetworkFlavor, self).setUp()
+        self.network.update_flavor = mock.Mock(return_value=None)
+        self.network.find_flavor = mock.Mock(
+            return_value=self.new_network_flavor)
+
+        # Get the command object to test
+        self.cmd = network_flavor.SetNetworkFlavor(self.app, self.namespace)
+
+    def test_set_nothing(self):
+        arglist = [self.new_network_flavor.name, ]
+        verifylist = [
+            ('flavor', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        result = self.cmd.take_action(parsed_args)
+
+        attrs = {}
+        self.network.update_flavor.assert_called_with(
+            self.new_network_flavor, **attrs)
+        self.assertIsNone(result)
+
+    def test_set_name_and_enable(self):
+        arglist = [
+            '--name', 'new_network_flavor',
+            '--enable',
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('name', 'new_network_flavor'),
+            ('enable', True),
+            ('flavor', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        result = self.cmd.take_action(parsed_args)
+        attrs = {
+            'name': "new_network_flavor",
+            'enabled': True,
+        }
+        self.network.update_flavor.assert_called_with(
+            self.new_network_flavor, **attrs)
+        self.assertIsNone(result)
+
+    def test_set_disable(self):
+        arglist = [
+            '--disable',
+            self.new_network_flavor.name,
+        ]
+        verifylist = [
+            ('disable', True),
+            ('flavor', self.new_network_flavor.name),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        result = self.cmd.take_action(parsed_args)
+        attrs = {
+            'enabled': False,
+        }
+        self.network.update_flavor.assert_called_with(
+            self.new_network_flavor, **attrs)
+        self.assertIsNone(result)
diff --git a/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml
new file mode 100644
index 0000000000..e1d8996136
--- /dev/null
+++ b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add ``network flavor create``,  ``network flavor delete``,
+    ``network flavor list``, Add ``network flavor show`` and
+    ``network flavor set`` command
+    [Implement Neutron Flavors in OSC `https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client-flavors`]
diff --git a/setup.cfg b/setup.cfg
index aa81559e4c..da7c0aa4f7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -360,6 +360,12 @@ openstack.network.v2 =
     network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent
     network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent
 
+    network_flavor_create = openstackclient.network.v2.network_flavor:CreateNetworkFlavor
+    network_flavor_delete = openstackclient.network.v2.network_flavor:DeleteNetworkFlavor
+    network_flavor_list = openstackclient.network.v2.network_flavor:ListNetworkFlavor
+    network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor
+    network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor
+
     network_create = openstackclient.network.v2.network:CreateNetwork
     network_delete = openstackclient.network.v2.network:DeleteNetwork
     network_list = openstackclient.network.v2.network:ListNetwork