Merge "Client command extension support"
This commit is contained in:
86
neutronclient/common/extension.py
Normal file
86
neutronclient/common/extension.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Copyright 2015 Rackspace Hosting Inc.
|
||||
# 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 stevedore import extension
|
||||
|
||||
from neutronclient.neutron import v2_0 as neutronV20
|
||||
|
||||
|
||||
def _discover_via_entry_points():
|
||||
emgr = extension.ExtensionManager('neutronclient.extension',
|
||||
invoke_on_load=False)
|
||||
return ((ext.name, ext.plugin) for ext in emgr)
|
||||
|
||||
|
||||
class NeutronClientExtension(neutronV20.NeutronCommand):
|
||||
pagination_support = False
|
||||
_formatters = {}
|
||||
sorting_support = False
|
||||
|
||||
|
||||
class ClientExtensionShow(NeutronClientExtension, neutronV20.ShowCommand):
|
||||
def get_data(self, parsed_args):
|
||||
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
|
||||
# for any implementers adding extensions with
|
||||
# regard to any other extension verb.
|
||||
return self.execute(parsed_args)
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return super(ClientExtensionShow, self).get_data(parsed_args)
|
||||
|
||||
|
||||
class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand):
|
||||
|
||||
def get_data(self, parsed_args):
|
||||
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
|
||||
# for any implementers adding extensions with
|
||||
# regard to any other extension verb.
|
||||
return self.execute(parsed_args)
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return super(ClientExtensionList, self).get_data(parsed_args)
|
||||
|
||||
|
||||
class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand):
|
||||
def run(self, parsed_args):
|
||||
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
|
||||
# for any implementers adding extensions with
|
||||
# regard to any other extension verb.
|
||||
return self.execute(parsed_args)
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return super(ClientExtensionDelete, self).run(parsed_args)
|
||||
|
||||
|
||||
class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand):
|
||||
def get_data(self, parsed_args):
|
||||
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
|
||||
# for any implementers adding extensions with
|
||||
# regard to any other extension verb.
|
||||
return self.execute(parsed_args)
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return super(ClientExtensionCreate, self).get_data(parsed_args)
|
||||
|
||||
|
||||
class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand):
|
||||
def run(self, parsed_args):
|
||||
# NOTE(mdietz): Calls 'execute' to provide a consistent pattern
|
||||
# for any implementers adding extensions with
|
||||
# regard to any other extension verb.
|
||||
return self.execute(parsed_args)
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return super(ClientExtensionUpdate, self).run(parsed_args)
|
0
neutronclient/neutron/v2_0/contrib/__init__.py
Normal file
0
neutronclient/neutron/v2_0/contrib/__init__.py
Normal file
85
neutronclient/neutron/v2_0/contrib/_fox_sockets.py
Normal file
85
neutronclient/neutron/v2_0/contrib/_fox_sockets.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# Copyright 2015 Rackspace Hosting Inc.
|
||||
# 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 neutronclient.common import extension
|
||||
from neutronclient.i18n import _
|
||||
from neutronclient.neutron import v2_0 as neutronV20
|
||||
|
||||
|
||||
def _add_updatable_args(parser):
|
||||
parser.add_argument(
|
||||
'name',
|
||||
help=_('Name of this fox socket.'))
|
||||
|
||||
|
||||
def _updatable_args2body(parsed_args, body, client):
|
||||
if parsed_args.name:
|
||||
body['fox_socket'].update({'name': parsed_args.name})
|
||||
|
||||
|
||||
class FoxInSocket(extension.NeutronClientExtension):
|
||||
resource = 'fox_socket'
|
||||
resource_plural = '%ss' % resource
|
||||
object_path = '/%s' % resource_plural
|
||||
resource_path = '/%s/%%s' % resource_plural
|
||||
versions = ['2.0']
|
||||
|
||||
|
||||
class FoxInSocketsList(extension.ClientExtensionList, FoxInSocket):
|
||||
shell_command = 'fox-sockets-list'
|
||||
list_columns = ['id', 'name']
|
||||
pagination_support = True
|
||||
sorting_support = True
|
||||
|
||||
|
||||
class FoxInSocketsCreate(extension.ClientExtensionCreate, FoxInSocket):
|
||||
shell_command = 'fox-sockets-create'
|
||||
list_columns = ['id', 'name']
|
||||
|
||||
def add_known_arguments(self, parser):
|
||||
_add_updatable_args(parser)
|
||||
|
||||
def args2body(self, parsed_args):
|
||||
body = {'fox_socket': {}}
|
||||
client = self.get_client()
|
||||
_updatable_args2body(parsed_args, body, client)
|
||||
neutronV20.update_dict(parsed_args, body['fox_socket'], [])
|
||||
return body
|
||||
|
||||
|
||||
class FoxInSocketsUpdate(extension.ClientExtensionUpdate, FoxInSocket):
|
||||
shell_command = 'fox-sockets-update'
|
||||
list_columns = ['id', 'name']
|
||||
|
||||
def add_known_arguments(self, parser):
|
||||
#_add_updatable_args(parser)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
help=_('Name of this fox socket.'))
|
||||
|
||||
def args2body(self, parsed_args):
|
||||
body = {'fox_socket': {
|
||||
'name': parsed_args.name}, }
|
||||
neutronV20.update_dict(parsed_args, body['fox_socket'], [])
|
||||
return body
|
||||
|
||||
|
||||
class FoxInSocketsDelete(extension.ClientExtensionDelete, FoxInSocket):
|
||||
shell_command = 'fox-sockets-delete'
|
||||
|
||||
|
||||
class FoxInSocketsShow(extension.ClientExtensionShow, FoxInSocket):
|
||||
shell_command = 'fox-sockets-show'
|
@@ -22,6 +22,8 @@ from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import inspect
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -40,6 +42,7 @@ from cliff import commandmanager
|
||||
from neutronclient.common import clientmanager
|
||||
from neutronclient.common import command as openstack_command
|
||||
from neutronclient.common import exceptions as exc
|
||||
from neutronclient.common import extension as client_extension
|
||||
from neutronclient.common import utils
|
||||
from neutronclient.i18n import _
|
||||
from neutronclient.neutron.v2_0 import agent
|
||||
@@ -381,6 +384,8 @@ class NeutronShell(app.App):
|
||||
for k, v in self.commands[apiversion].items():
|
||||
self.command_manager.add_command(k, v)
|
||||
|
||||
self._register_extensions(VERSION)
|
||||
|
||||
# Pop the 'complete' to correct the outputs of 'neutron help'.
|
||||
self.command_manager.commands.pop('complete')
|
||||
|
||||
@@ -671,6 +676,25 @@ class NeutronShell(app.App):
|
||||
options.add(option)
|
||||
print(' '.join(commands | options))
|
||||
|
||||
def _register_extensions(self, version):
|
||||
for name, module in itertools.chain(
|
||||
client_extension._discover_via_entry_points()):
|
||||
self._extend_shell_commands(module, version)
|
||||
|
||||
def _extend_shell_commands(self, module, version):
|
||||
classes = inspect.getmembers(module, inspect.isclass)
|
||||
for cls_name, cls in classes:
|
||||
if (issubclass(cls, client_extension.NeutronClientExtension) and
|
||||
hasattr(cls, 'shell_command')):
|
||||
cmd = cls.shell_command
|
||||
if hasattr(cls, 'versions'):
|
||||
if version not in cls.versions:
|
||||
continue
|
||||
try:
|
||||
self.command_manager.add_command(cmd, cls)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def run(self, argv):
|
||||
"""Equivalent to the main program for the application.
|
||||
|
||||
|
@@ -217,7 +217,8 @@ class CLITestV20Base(base.BaseTestCase):
|
||||
'credential', 'network_profile',
|
||||
'policy_profile', 'ikepolicy',
|
||||
'ipsecpolicy', 'metering_label',
|
||||
'metering_label_rule', 'net_partition']
|
||||
'metering_label_rule', 'net_partition',
|
||||
'fox_socket']
|
||||
if not cmd_resource:
|
||||
cmd_resource = resource
|
||||
if (resource in non_admin_status_resources):
|
||||
|
87
neutronclient/tests/unit/test_client_extension.py
Normal file
87
neutronclient/tests/unit/test_client_extension.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Copyright 2015 Rackspace Hosting Inc.
|
||||
# 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
|
||||
|
||||
import mock
|
||||
|
||||
from neutronclient.neutron.v2_0.contrib import _fox_sockets as fox_sockets
|
||||
from neutronclient.tests.unit import test_cli20
|
||||
|
||||
|
||||
class CLITestV20ExtensionJSON(test_cli20.CLITestV20Base):
|
||||
def setUp(self):
|
||||
# need to mock before super because extensions loaded on instantiation
|
||||
self._mock_extension_loading()
|
||||
super(CLITestV20ExtensionJSON, self).setUp(plurals={'tags': 'tag'})
|
||||
|
||||
def _create_patch(self, name, func=None):
|
||||
patcher = mock.patch(name)
|
||||
thing = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
return thing
|
||||
|
||||
def _mock_extension_loading(self):
|
||||
ext_pkg = 'neutronclient.common.extension'
|
||||
contrib = self._create_patch(ext_pkg + '._discover_via_entry_points')
|
||||
iterator = iter([("_fox_sockets", fox_sockets)])
|
||||
contrib.return_value.__iter__.return_value = iterator
|
||||
return contrib
|
||||
|
||||
def test_delete_fox_socket(self):
|
||||
"""Delete fox socket: myid."""
|
||||
resource = 'fox_socket'
|
||||
cmd = fox_sockets.FoxInSocketsDelete(test_cli20.MyApp(sys.stdout),
|
||||
None)
|
||||
myid = 'myid'
|
||||
args = [myid]
|
||||
self._test_delete_resource(resource, cmd, myid, args)
|
||||
|
||||
def test_update_fox_socket(self):
|
||||
"""Update fox_socket: myid --name myname."""
|
||||
resource = 'fox_socket'
|
||||
cmd = fox_sockets.FoxInSocketsUpdate(test_cli20.MyApp(sys.stdout),
|
||||
None)
|
||||
self._test_update_resource(resource, cmd, 'myid',
|
||||
['myid', '--name', 'myname'],
|
||||
{'name': 'myname'})
|
||||
|
||||
def test_create_fox_socket(self):
|
||||
"""Create fox_socket: myname."""
|
||||
resource = 'fox_socket'
|
||||
cmd = fox_sockets.FoxInSocketsCreate(test_cli20.MyApp(sys.stdout),
|
||||
None)
|
||||
name = 'myname'
|
||||
myid = 'myid'
|
||||
args = [name, ]
|
||||
position_names = ['name', ]
|
||||
position_values = [name, ]
|
||||
self._test_create_resource(resource, cmd, name, myid, args,
|
||||
position_names, position_values)
|
||||
|
||||
def test_list_fox_sockets(self):
|
||||
"""List fox_sockets."""
|
||||
resources = 'fox_sockets'
|
||||
cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None)
|
||||
self._test_list_resources(resources, cmd, True)
|
||||
|
||||
def test_show_fox_socket(self):
|
||||
"""Show fox_socket: --fields id --fields name myid."""
|
||||
resource = 'fox_socket'
|
||||
cmd = fox_sockets.FoxInSocketsShow(test_cli20.MyApp(sys.stdout), None)
|
||||
args = ['--fields', 'id', '--fields', 'name', self.test_id]
|
||||
self._test_show_resource(resource, cmd, self.test_id,
|
||||
args, ['id', 'name'])
|
@@ -15,6 +15,8 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import inspect
|
||||
import itertools
|
||||
import logging
|
||||
import time
|
||||
|
||||
@@ -24,6 +26,7 @@ import six.moves.urllib.parse as urlparse
|
||||
from neutronclient import client
|
||||
from neutronclient.common import constants
|
||||
from neutronclient.common import exceptions
|
||||
from neutronclient.common import extension as client_extension
|
||||
from neutronclient.common import serializer
|
||||
from neutronclient.common import utils
|
||||
from neutronclient.i18n import _
|
||||
@@ -453,6 +456,36 @@ class Client(ClientBase):
|
||||
'healthmonitors': 'healthmonitor',
|
||||
}
|
||||
|
||||
@APIParamsCall
|
||||
def list_ext(self, path, **_params):
|
||||
"""Client extension hook for lists.
|
||||
"""
|
||||
return self.get(path, params=_params)
|
||||
|
||||
@APIParamsCall
|
||||
def show_ext(self, path, id, **_params):
|
||||
"""Client extension hook for shows.
|
||||
"""
|
||||
return self.get(path % id, params=_params)
|
||||
|
||||
@APIParamsCall
|
||||
def create_ext(self, path, body=None):
|
||||
"""Client extension hook for creates.
|
||||
"""
|
||||
return self.post(path, body=body)
|
||||
|
||||
@APIParamsCall
|
||||
def update_ext(self, path, id, body=None):
|
||||
"""Client extension hook for updates.
|
||||
"""
|
||||
return self.put(path % id, body=body)
|
||||
|
||||
@APIParamsCall
|
||||
def delete_ext(self, path, id):
|
||||
"""Client extension hook for deletes.
|
||||
"""
|
||||
return self.delete(path % id)
|
||||
|
||||
@APIParamsCall
|
||||
def get_quotas_tenant(self, **_params):
|
||||
"""Fetch tenant info in server's context for following quota operation.
|
||||
@@ -1523,3 +1556,59 @@ class Client(ClientBase):
|
||||
def delete_packet_filter(self, packet_filter_id):
|
||||
"""Delete the specified packet filter."""
|
||||
return self.delete(self.packet_filter_path % packet_filter_id)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize a new client for the Neutron v2.0 API."""
|
||||
super(Client, self).__init__(**kwargs)
|
||||
self._register_extensions(self.version)
|
||||
|
||||
def extend_show(self, resource_plural, path):
|
||||
def _fx(obj, **_params):
|
||||
return self.show_ext(path, obj, **_params)
|
||||
setattr(self, "show_%s" % resource_plural, _fx)
|
||||
|
||||
def extend_list(self, resource_plural, path):
|
||||
def _fx(**_params):
|
||||
return self.list_ext(path, **_params)
|
||||
setattr(self, "list_%s" % resource_plural, _fx)
|
||||
|
||||
def extend_create(self, resource_singular, path):
|
||||
def _fx(body=None):
|
||||
return self.create_ext(path, body)
|
||||
setattr(self, "create_%s" % resource_singular, _fx)
|
||||
|
||||
def extend_delete(self, resource_singular, path):
|
||||
def _fx(obj):
|
||||
return self.delete_ext(path, obj)
|
||||
setattr(self, "delete_%s" % resource_singular, _fx)
|
||||
|
||||
def extend_update(self, resource_singular, path):
|
||||
def _fx(obj, body=None):
|
||||
return self.update_ext(path, obj, body)
|
||||
setattr(self, "update_%s" % resource_singular, _fx)
|
||||
|
||||
def _extend_client_with_module(self, module, version):
|
||||
classes = inspect.getmembers(module, inspect.isclass)
|
||||
for cls_name, cls in classes:
|
||||
if hasattr(cls, 'versions'):
|
||||
if version not in cls.versions:
|
||||
continue
|
||||
if issubclass(cls, client_extension.ClientExtensionList):
|
||||
self.extend_list(cls.resource_plural, cls.object_path)
|
||||
elif issubclass(cls, client_extension.ClientExtensionCreate):
|
||||
self.extend_create(cls.resource, cls.object_path)
|
||||
elif issubclass(cls, client_extension.ClientExtensionUpdate):
|
||||
self.extend_update(cls.resource, cls.resource_path)
|
||||
elif issubclass(cls, client_extension.ClientExtensionDelete):
|
||||
self.extend_delete(cls.resource, cls.resource_path)
|
||||
elif issubclass(cls, client_extension.ClientExtensionShow):
|
||||
self.extend_show(cls.resource, cls.resource_path)
|
||||
elif issubclass(cls, client_extension.NeutronClientExtension):
|
||||
setattr(self, "%s_path" % cls.resource_plural,
|
||||
cls.object_path)
|
||||
setattr(self, "%s_path" % cls.resource, cls.resource_path)
|
||||
|
||||
def _register_extensions(self, version):
|
||||
for name, module in itertools.chain(
|
||||
client_extension._discover_via_entry_points()):
|
||||
self._extend_client_with_module(module, version)
|
||||
|
@@ -8,6 +8,7 @@ coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
mox3>=0.7.0
|
||||
mock>=1.0
|
||||
oslosphinx>=2.2.0 # Apache-2.0
|
||||
oslotest>=1.2.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
|
Reference in New Issue
Block a user