diff --git a/neutronclient/tests/unit/test_client_extension.py b/neutronclient/tests/unit/test_client_extension.py index 4b64d4bd7..8684c0891 100644 --- a/neutronclient/tests/unit/test_client_extension.py +++ b/neutronclient/tests/unit/test_client_extension.py @@ -14,6 +14,7 @@ # under the License. # +import inspect import sys import mock @@ -150,3 +151,74 @@ class CLITestV20ExtensionJSONAlternatePlurals(test_cli20.CLITestV20Base): resources = 'ip_addresses' cmd = self.IPAddressesList(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + + +class CLITestV20ExtensionJSONChildResource(test_cli20.CLITestV20Base): + class Child(extension.NeutronClientExtension): + parent_resource = 'parents' + child_resource = 'child' + resource = '%s_%s' % (parent_resource, child_resource) + resource_plural = '%sren' % resource + child_resource_plural = '%ren' % child_resource + object_path = '/%s/%%s/%s' % (parent_resource, child_resource_plural) + resource_path = '/%s/%%s/%s/%%s' % (parent_resource, + child_resource_plural) + versions = ['2.0'] + + class ChildrenList(extension.ClientExtensionList, Child): + shell_command = 'parent-child-list' + + class ChildShow(extension.ClientExtensionShow, Child): + shell_command = 'parent-child-show' + + class ChildUpdate(extension.ClientExtensionUpdate, Child): + shell_command = 'parent-child-update' + + class ChildDelete(extension.ClientExtensionDelete, Child): + shell_command = 'parent-child-delete' + + class ChildCreate(extension.ClientExtensionCreate, Child): + shell_command = 'parent-child-create' + + def setUp(self): + # need to mock before super because extensions loaded on instantiation + self._mock_extension_loading() + super(CLITestV20ExtensionJSONChildResource, self).setUp() + + 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') + child = mock.MagicMock() + child.Child = self.Child + child.ChildrenList = self.ChildrenList + child.ChildShow = self.ChildShow + child.ChildUpdate = self.ChildUpdate + child.ChildDelete = self.ChildDelete + child.ChildCreate = self.ChildCreate + contrib.return_value = [("child", child)] + return contrib + + def test_ext_cmd_loaded(self): + shell.NeutronShell('2.0') + ext_cmd = {'parent-child-list': self.ChildrenList, + 'parent-child-show': self.ChildShow, + 'parent-child-update': self.ChildUpdate, + 'parent-child-delete': self.ChildDelete, + 'parent-child-create': self.ChildCreate} + self.assertDictContainsSubset(ext_cmd, shell.COMMANDS['2.0']) + + def test_client_methods_have_parent_id_arg(self): + methods = (self.client.list_parents_children, + self.client.show_parents_child, + self.client.update_parents_child, + self.client.delete_parents_child, + self.client.create_parents_child) + for method in methods: + argspec = inspect.getargspec(method) + self.assertIn("parent_id", argspec.args) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 094c35278..4849a0f86 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -1631,30 +1631,50 @@ class Client(ClientBase): super(Client, self).__init__(**kwargs) self._register_extensions(self.version) - def extend_show(self, resource_plural, path): + def extend_show(self, resource_plural, path, parent_resource): 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 _parent_fx(parent_id, obj, **_params): + return self.show_ext(path % parent_id, obj, **_params) + fn = _fx if not parent_resource else _parent_fx + setattr(self, "show_%s" % resource_plural, fn) + + def extend_list(self, resource_plural, path, parent_resource): def _fx(**_params): return self.list_ext(path, **_params) - setattr(self, "list_%s" % resource_plural, _fx) - def extend_create(self, resource_singular, path): + def _parent_fx(parent_id, **_params): + return self.list_ext(path % parent_id, **_params) + fn = _fx if not parent_resource else _parent_fx + setattr(self, "list_%s" % resource_plural, fn) + + def extend_create(self, resource_singular, path, parent_resource): 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 _parent_fx(parent_id, body=None): + return self.create_ext(path % parent_id, body) + fn = _fx if not parent_resource else _parent_fx + setattr(self, "create_%s" % resource_singular, fn) + + def extend_delete(self, resource_singular, path, parent_resource): def _fx(obj): return self.delete_ext(path, obj) - setattr(self, "delete_%s" % resource_singular, _fx) - def extend_update(self, resource_singular, path): + def _parent_fx(parent_id, obj): + return self.delete_ext(path % parent_id, obj) + fn = _fx if not parent_resource else _parent_fx + setattr(self, "delete_%s" % resource_singular, fn) + + def extend_update(self, resource_singular, path, parent_resource): def _fx(obj, body=None): return self.update_ext(path, obj, body) - setattr(self, "update_%s" % resource_singular, _fx) + + def _parent_fx(parent_id, obj, body=None): + return self.update_ext(path % parent_id, obj, body) + fn = _fx if not parent_resource else _parent_fx + setattr(self, "update_%s" % resource_singular, fn) def _extend_client_with_module(self, module, version): classes = inspect.getmembers(module, inspect.isclass) @@ -1662,16 +1682,22 @@ class Client(ClientBase): if hasattr(cls, 'versions'): if version not in cls.versions: continue + parent_resource = getattr(cls, 'parent_resource', None) if issubclass(cls, client_extension.ClientExtensionList): - self.extend_list(cls.resource_plural, cls.object_path) + self.extend_list(cls.resource_plural, cls.object_path, + parent_resource) elif issubclass(cls, client_extension.ClientExtensionCreate): - self.extend_create(cls.resource, cls.object_path) + self.extend_create(cls.resource, cls.object_path, + parent_resource) elif issubclass(cls, client_extension.ClientExtensionUpdate): - self.extend_update(cls.resource, cls.resource_path) + self.extend_update(cls.resource, cls.resource_path, + parent_resource) elif issubclass(cls, client_extension.ClientExtensionDelete): - self.extend_delete(cls.resource, cls.resource_path) + self.extend_delete(cls.resource, cls.resource_path, + parent_resource) elif issubclass(cls, client_extension.ClientExtensionShow): - self.extend_show(cls.resource, cls.resource_path) + self.extend_show(cls.resource, cls.resource_path, + parent_resource) elif issubclass(cls, client_extension.NeutronClientExtension): setattr(self, "%s_path" % cls.resource_plural, cls.object_path)