Add Neutron flavor framework CLI

Provide command line control of Neutron flavor framework resources,
adding the following:
  flavor-create
  flavor-delete
  flavor-list
  flavor-show
  flavor-update
  flavor-profile-create
  flavor-profile-delete
  flavor-profile-list
  flavor-profile-show
  flavor-profile-update
as well as ability to manipulate flavor to profile associations:
  flavor-associate
  flavor-disassociate

Change to the '--enabled {True,False}' syntax.

Bump up unit test coverage.

ApiImpact
DocImpact
Implements: blueprint neutron-flavor-framework
Change-Id: I0d73c8de223659071eb305d8bd1c699aefaeeb89
This commit is contained in:
James Arendt 2015-08-19 12:10:36 -07:00
parent 2119b68610
commit a150ce000d
8 changed files with 629 additions and 0 deletions

View File

@ -0,0 +1,167 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# 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.
from __future__ import print_function
import argparse
from neutronclient.common import utils
from neutronclient.i18n import _
from neutronclient.neutron import v2_0 as neutronV20
class ListFlavor(neutronV20.ListCommand):
"""List Neutron service flavors."""
resource = 'flavor'
list_columns = ['id', 'name', 'service_type', 'enabled']
pagination_support = True
sorting_support = True
class ShowFlavor(neutronV20.ShowCommand):
"""Show information about a given Neutron service flavor."""
resource = 'flavor'
class CreateFlavor(neutronV20.CreateCommand):
"""Create a Neutron service flavor."""
resource = 'flavor'
def add_known_arguments(self, parser):
parser.add_argument(
'name',
metavar='NAME',
help=_('Name for the flavor.'))
parser.add_argument(
'service_type',
metavar='SERVICE_TYPE',
help=_('Service type to which the flavor applies to: e.g. VPN. '
'(See service-provider-list for loaded examples.)'))
parser.add_argument(
'--description',
help=_('Description for the flavor.'))
utils.add_boolean_argument(
parser,
'--enabled',
default=argparse.SUPPRESS,
help=_('Sets enabled flag.'))
def args2body(self, parsed_args):
body = {}
neutronV20.update_dict(parsed_args, body,
['name', 'description', 'service_type',
'enabled'])
return {self.resource: body}
class DeleteFlavor(neutronV20.DeleteCommand):
"""Delete a given Neutron service flavor."""
resource = 'flavor'
class UpdateFlavor(neutronV20.UpdateCommand):
"""Update a Neutron service flavor."""
resource = 'flavor'
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help=_('Name for the flavor.'))
parser.add_argument(
'--description',
help=_('Description for the flavor.'))
utils.add_boolean_argument(
parser,
'--enabled',
default=argparse.SUPPRESS,
help=_('Sets enabled flag.'))
def args2body(self, parsed_args):
body = {}
neutronV20.update_dict(parsed_args, body,
['name', 'description', 'enabled'])
return {self.resource: body}
class AssociateFlavor(neutronV20.NeutronCommand):
"""Associate a Neutron service flavor with a flavor profile."""
resource = 'flavor'
def get_parser(self, prog_name):
parser = super(AssociateFlavor, self).get_parser(prog_name)
parser.add_argument(
'flavor',
metavar='FLAVOR',
help=_('Name or ID of the flavor to associate.'))
parser.add_argument(
'flavor_profile',
metavar='FLAVOR_PROFILE',
help=_('ID of the flavor profile to be associated with the '
'flavor.'))
return parser
def run(self, parsed_args):
neutron_client = self.get_client()
neutron_client.format = parsed_args.request_format
flavor_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'flavor', parsed_args.flavor)
service_profile_id = neutronV20.find_resourceid_by_id(
neutron_client, 'service_profile', parsed_args.flavor_profile)
body = {'service_profile': {'id': service_profile_id}}
neutron_client.associate_flavor(flavor_id, body)
print((_('Associated flavor %(flavor)s with '
'flavor_profile %(profile)s') %
{'flavor': parsed_args.flavor,
'profile': parsed_args.flavor_profile}),
file=self.app.stdout)
class DisassociateFlavor(neutronV20.NeutronCommand):
"""Disassociate a Neutron service flavor from a flavor profile."""
resource = 'flavor'
def get_parser(self, prog_name):
parser = super(DisassociateFlavor, self).get_parser(prog_name)
parser.add_argument(
'flavor',
metavar='FLAVOR',
help=_('Name or ID of the flavor.'))
parser.add_argument(
'flavor_profile',
metavar='FLAVOR_PROFILE',
help=_('ID of the flavor profile to be disassociated from the '
'flavor.'))
return parser
def run(self, parsed_args):
neutron_client = self.get_client()
neutron_client.format = parsed_args.request_format
flavor_id = neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'flavor', parsed_args.flavor)
service_profile_id = neutronV20.find_resourceid_by_id(
neutron_client, 'service_profile', parsed_args.flavor_profile)
neutron_client.disassociate_flavor(flavor_id, service_profile_id)
print((_('Disassociated flavor %(flavor)s from '
'flavor_profile %(profile)s') %
{'flavor': parsed_args.flavor,
'profile': parsed_args.flavor_profile}),
file=self.app.stdout)

View File

@ -0,0 +1,99 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# 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 argparse
from neutronclient.common import utils
from neutronclient.i18n import _
from neutronclient.neutron import v2_0 as neutronV20
class ListFlavorProfile(neutronV20.ListCommand):
"""List Neutron service flavor profiles."""
resource = 'service_profile'
list_columns = ['id', 'description', 'enabled', 'metainfo']
pagination_support = True
sorting_support = True
class ShowFlavorProfile(neutronV20.ShowCommand):
"""Show information about a given Neutron service flavor profile."""
resource = 'service_profile'
class CreateFlavorProfile(neutronV20.CreateCommand):
"""Create a Neutron service flavor profile."""
resource = 'service_profile'
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description for the flavor profile.'))
parser.add_argument(
'--driver',
help=_('Python module path to driver.'))
parser.add_argument(
'--metainfo',
help=_('Metainfo for the flavor profile.'))
utils.add_boolean_argument(
parser,
'--enabled',
default=argparse.SUPPRESS,
help=_('Sets enabled flag.'))
def args2body(self, parsed_args):
body = {}
neutronV20.update_dict(parsed_args, body,
['description', 'driver', 'enabled',
'metainfo'])
return {self.resource: body}
class DeleteFlavorProfile(neutronV20.DeleteCommand):
"""Delete a given Neutron service flavor profile."""
resource = 'service_profile'
class UpdateFlavorProfile(neutronV20.UpdateCommand):
"""Update a given Neutron service flavor profile."""
resource = 'service_profile'
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description for the flavor profile.'))
parser.add_argument(
'--driver',
help=_('Python module path to driver.'))
parser.add_argument(
'--metainfo',
help=_('Metainfo for the flavor profile.'))
utils.add_boolean_argument(
parser,
'--enabled',
default=argparse.SUPPRESS,
help=_('Sets enabled flag.'))
def args2body(self, parsed_args):
body = {}
neutronV20.update_dict(parsed_args, body,
['description', 'driver', 'enabled',
'metainfo'])
return {self.resource: body}

View File

@ -50,6 +50,8 @@ from neutronclient.neutron.v2_0 import agent
from neutronclient.neutron.v2_0 import agentscheduler
from neutronclient.neutron.v2_0 import credential
from neutronclient.neutron.v2_0 import extension
from neutronclient.neutron.v2_0.flavor import flavor
from neutronclient.neutron.v2_0.flavor import flavor_profile
from neutronclient.neutron.v2_0 import floatingip
from neutronclient.neutron.v2_0.fw import firewall
from neutronclient.neutron.v2_0.fw import firewallpolicy
@ -396,6 +398,18 @@ COMMAND_V2 = {
bandwidth_limit_rule.DeleteQoSBandwidthLimitRule
),
'qos-available-rule-types': qos_rule.ListQoSRuleTypes,
'flavor-list': flavor.ListFlavor,
'flavor-show': flavor.ShowFlavor,
'flavor-create': flavor.CreateFlavor,
'flavor-delete': flavor.DeleteFlavor,
'flavor-update': flavor.UpdateFlavor,
'flavor-associate': flavor.AssociateFlavor,
'flavor-disassociate': flavor.DisassociateFlavor,
'flavor-profile-list': flavor_profile.ListFlavorProfile,
'flavor-profile-show': flavor_profile.ShowFlavorProfile,
'flavor-profile-create': flavor_profile.CreateFlavorProfile,
'flavor-profile-delete': flavor_profile.DeleteFlavorProfile,
'flavor-profile-update': flavor_profile.UpdateFlavorProfile,
}
COMMANDS = {'2.0': COMMAND_V2}

View File

@ -0,0 +1,154 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# 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 sys
from neutronclient.neutron.v2_0.flavor import flavor
from neutronclient.tests.unit import test_cli20
class CLITestV20FlavorJSON(test_cli20.CLITestV20Base):
def setUp(self):
"""Prepare test environment."""
super(CLITestV20FlavorJSON, self).setUp(plurals={'flavors': 'flavor'})
self.register_non_admin_status_resource('flavor')
self.register_non_admin_status_resource('service_profile')
def test_create_flavor_with_missing_params(self):
"""Create test flavor with missing parameters."""
resource = 'flavor'
cmd = flavor.CreateFlavor(
test_cli20.MyApp(sys.stdout), None)
name = 'Test flavor'
myid = 'myid'
position_names = []
position_values = []
args = []
self.assertRaises(
SystemExit, self._test_create_resource,
resource, cmd, name, myid, args, position_names, position_values)
def test_create_flavor_with_mandatory_params(self):
"""Create test flavor with minimal parameters."""
resource = 'flavor'
cmd = flavor.CreateFlavor(
test_cli20.MyApp(sys.stdout), None)
name = 'Test flavor'
myid = 'myid'
service_type = 'DUMMY'
# Defaults are returned in body
position_names = ['name', 'service_type']
position_values = [name, service_type]
args = [name, service_type]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_flavor_with_optional_params(self):
"""Create test flavor including optional parameters."""
resource = 'flavor'
cmd = flavor.CreateFlavor(
test_cli20.MyApp(sys.stdout), None)
name = 'Test flavor'
myid = 'myid'
service_type = 'DUMMY'
description = 'Test description'
position_names = ['name', 'service_type', 'description', 'enabled']
position_values = [name, service_type, description, 'False']
args = [name, service_type,
'--description', description,
'--enabled=False']
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_delete_flavor(self):
"""Delete flavor."""
resource = 'flavor'
cmd = flavor.DeleteFlavor(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)
def test_list_flavors(self):
"""List flavors test."""
resources = 'flavors'
cmd = flavor.ListFlavor(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_flavors_with_pagination(self):
"""List flavors test with pagination."""
resources = 'flavors'
cmd = flavor.ListFlavor(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_flavors_with_sort(self):
"""List flavors test with sorting by name and id."""
resources = 'flavors'
cmd = flavor.ListFlavor(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_show_flavor(self):
"""Show flavor test."""
resource = 'flavor'
cmd = flavor.ShowFlavor(
test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_update_flavor_with_name(self):
"""Update flavor test."""
resource = 'flavor'
cmd = flavor.UpdateFlavor(
test_cli20.MyApp(sys.stdout), None)
newname = 'Test New Name'
newdescription = 'New Description'
args = ['--name', newname,
'--description', newdescription,
'--enabled', 'False', self.test_id]
self._test_update_resource(resource, cmd, self.test_id, args,
{'name': newname,
'description': newdescription,
'enabled': 'False'})
def test_associate_flavor(self):
"""Associate flavor test."""
resource = 'service_profile'
cmd = flavor.AssociateFlavor(test_cli20.MyApp(sys.stdout), None)
flavor_id = 'flavor-id'
profile_id = 'profile-id'
name = ''
args = [flavor_id, profile_id]
position_names = ['id']
position_values = [profile_id]
self._test_create_resource(resource, cmd, name, profile_id, args,
position_names, position_values,
cmd_resource='flavor_profile_binding',
parent_id=flavor_id)
def test_disassociate_flavor(self):
"""Disassociate flavor test."""
resource = 'flavor_profile_binding'
cmd = flavor.DisassociateFlavor(test_cli20.MyApp(sys.stdout), None)
flavor_id = 'flavor-id'
profile_id = 'profile-id'
args = [flavor_id, profile_id]
self._test_delete_resource(resource, cmd, profile_id, args,
parent_id=flavor_id)

View File

@ -0,0 +1,122 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# 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 sys
from neutronclient.neutron.v2_0.flavor import flavor_profile
from neutronclient.tests.unit import test_cli20
class CLITestV20FlavorProfileJSON(test_cli20.CLITestV20Base):
def setUp(self):
"""Prepare test environment."""
super(CLITestV20FlavorProfileJSON, self).setUp(
plurals={'service_profiles': 'service_profile'})
self.register_non_admin_status_resource('service_profile')
def test_create_flavor_profile_with_mandatory_params(self):
"""Create test flavor profile test."""
resource = 'service_profile'
cmd = flavor_profile.CreateFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
name = ''
description = 'Test flavor profile'
myid = 'myid'
metainfo = "{'a':'b'}"
# Defaults are returned in body
position_names = ['description', 'metainfo']
position_values = [description, metainfo]
args = ['--description', description, '--metainfo', metainfo]
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_create_flavor_profile_with_optional_params(self):
"""Create test flavor profile disabled test."""
resource = 'service_profile'
cmd = flavor_profile.CreateFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
name = ''
description = 'Test flavor profile - disabled'
myid = 'myid'
driver = 'mydriver'
metainfo = "{'a':'b'}"
position_names = ['description', 'driver', 'metainfo', 'enabled']
position_values = [description, driver, metainfo, 'False']
args = ['--description', description, '--driver', driver,
'--metainfo', metainfo, '--enabled=False']
self._test_create_resource(resource, cmd, name, myid, args,
position_names, position_values)
def test_list_flavor_profiles(self):
"""List flavor profiles test."""
resources = 'service_profiles'
cmd = flavor_profile.ListFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_flavor_profiles_with_pagination(self):
"""List flavor profiles test with pagination."""
resources = 'service_profiles'
cmd = flavor_profile.ListFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_flavor_profiles_with_sort(self):
"""List flavor profiles test with sort by description."""
resources = 'service_profiles'
cmd = flavor_profile.ListFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
sort_key=["description"],
sort_dir=["asc"])
def test_show_flavor_profile(self):
"""Show flavor profile test."""
resource = 'service_profile'
cmd = flavor_profile.ShowFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_update_flavor_profile(self):
"""Update flavor profile test."""
resource = 'service_profile'
cmd = flavor_profile.UpdateFlavorProfile(
test_cli20.MyApp(sys.stdout), None)
newdescription = 'Test new description'
newdriver = 'NewDriver'
newmetainfo = "{'c':'d'}"
newenabled = "False"
args = ['--description', newdescription,
'--driver', newdriver,
'--metainfo', newmetainfo,
'--enabled', newenabled,
self.test_id]
self._test_update_resource(resource, cmd, self.test_id, args,
{'description': newdescription,
'driver': newdriver,
'metainfo': newmetainfo,
'enabled': newenabled})
def test_delete_flavor_profile(self):
"""Delete flavor profile."""
resource = 'service_profile'
cmd = flavor_profile.DeleteFlavorProfile(test_cli20.MyApp(sys.stdout),
None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)

View File

@ -437,6 +437,12 @@ class Client(ClientBase):
qos_bandwidth_limit_rule_path = "/qos/policies/%s/bandwidth_limit_rules/%s"
qos_rule_types_path = "/qos/rule-types"
qos_rule_type_path = "/qos/rule-types/%s"
flavors_path = "/flavors"
flavor_path = "/flavors/%s"
service_profiles_path = "/service_profiles"
service_profile_path = "/service_profiles/%s"
flavor_profile_bindings_path = flavor_path + service_profiles_path
flavor_profile_binding_path = flavor_path + service_profile_path
# API has no way to report plurals, so we have to hard code them
EXTED_PLURALS = {'routers': 'router',
@ -474,6 +480,7 @@ class Client(ClientBase):
'policies': 'policy',
'bandwidth_limit_rules': 'bandwidth_limit_rule',
'rule_types': 'rule_type',
'flavors': 'flavor',
}
@APIParamsCall
@ -1737,6 +1744,72 @@ class Client(ClientBase):
return self.delete(self.qos_bandwidth_limit_rule_path %
(policy, rule))
@APIParamsCall
def create_flavor(self, body=None):
"""Creates a new Neutron service flavor."""
return self.post(self.flavors_path, body=body)
@APIParamsCall
def delete_flavor(self, flavor):
"""Deletes the specified Neutron service flavor."""
return self.delete(self.flavor_path % (flavor))
@APIParamsCall
def list_flavors(self, retrieve_all=True, **_params):
"""Fetches a list of all Neutron service flavors for a tenant."""
return self.list('flavors', self.flavors_path, retrieve_all,
**_params)
@APIParamsCall
def show_flavor(self, flavor, **_params):
"""Fetches information for a certain Neutron service flavor."""
return self.get(self.flavor_path % (flavor), params=_params)
@APIParamsCall
def update_flavor(self, flavor, body):
"""Update a Neutron service flavor."""
return self.put(self.flavor_path % (flavor), body=body)
@APIParamsCall
def associate_flavor(self, flavor, body):
"""Associate a Neutron service flavor with a profile."""
return self.post(self.flavor_profile_bindings_path %
(flavor), body=body)
@APIParamsCall
def disassociate_flavor(self, flavor, flavor_profile):
"""Disassociate a Neutron service flavor with a profile."""
return self.delete(self.flavor_profile_binding_path %
(flavor, flavor_profile))
@APIParamsCall
def create_service_profile(self, body=None):
"""Creates a new Neutron service flavor profile."""
return self.post(self.service_profiles_path, body=body)
@APIParamsCall
def delete_service_profile(self, flavor_profile):
"""Deletes the specified Neutron service flavor profile."""
return self.delete(self.service_profile_path % (flavor_profile))
@APIParamsCall
def list_service_profiles(self, retrieve_all=True, **_params):
"""Fetches a list of all Neutron service flavor profiles."""
return self.list('service_profiles', self.service_profiles_path,
retrieve_all, **_params)
@APIParamsCall
def show_service_profile(self, flavor_profile, **_params):
"""Fetches information for a certain Neutron service flavor profile."""
return self.get(self.service_profile_path % (flavor_profile),
params=_params)
@APIParamsCall
def update_service_profile(self, service_profile, body):
"""Update a Neutron service profile."""
return self.put(self.service_profile_path % (service_profile),
body=body)
def __init__(self, **kwargs):
"""Initialize a new client for the Neutron v2.0 API."""
super(Client, self).__init__(**kwargs)