diff --git a/doc/source/data/lbaas.csv b/doc/source/data/lbaas.csv index fddbc17..c093dbb 100644 --- a/doc/source/data/lbaas.csv +++ b/doc/source/data/lbaas.csv @@ -18,14 +18,14 @@ lbaas-listener-delete,,LBaaS v2 Delete a given listener. lbaas-listener-list,,LBaaS v2 List listeners that belong to a given tenant. lbaas-listener-show,,LBaaS v2 Show information of a given listener. lbaas-listener-update,,LBaaS v2 Update a given listener. -lbaas-loadbalancer-create,,LBaaS v2 Create a loadbalancer. -lbaas-loadbalancer-delete,,LBaaS v2 Delete a given loadbalancer. +lbaas-loadbalancer-create,loadbalancer create,LBaaS v2 Create a loadbalancer. +lbaas-loadbalancer-delete,loadbalancer delete,LBaaS v2 Delete a given loadbalancer. lbaas-loadbalancer-list,loadbalancer list,LBaaS v2 List loadbalancers that belong to a given tenant. lbaas-loadbalancer-list-on-agent,,List the loadbalancers on a loadbalancer v2 agent. -lbaas-loadbalancer-show,,LBaaS v2 Show information of a given loadbalancer. +lbaas-loadbalancer-show,loadbalancer show,LBaaS v2 Show information of a given loadbalancer. lbaas-loadbalancer-stats,,Retrieve stats for a given loadbalancer. lbaas-loadbalancer-status,,Retrieve status for a given loadbalancer. -lbaas-loadbalancer-update,,LBaaS v2 Update a given loadbalancer. +lbaas-loadbalancer-update,loadbalancer set,LBaaS v2 Update a given loadbalancer. lbaas-member-create,,LBaaS v2 Create a member. lbaas-member-delete,,LBaaS v2 Delete a given member. lbaas-member-list,,LBaaS v2 List members that belong to a given pool. diff --git a/doc/source/usage/osc/v2/load-balancer.rst b/doc/source/usage/osc/v2/load-balancer.rst index aada1c3..dd0b1a5 100644 --- a/doc/source/usage/osc/v2/load-balancer.rst +++ b/doc/source/usage/osc/v2/load-balancer.rst @@ -11,3 +11,148 @@ List load balancers .. code:: bash openstack loadbalancer list + [--name ] + [--enable | --disable] + [--project ] + +.. option:: --name + + List load balancers according to their name. + +.. option:: --enable + + List enabled load balancers. + +.. option:: --disable + + List disabled load balancers. + +.. option:: --project + + List load balancers according to their project (name or ID). + +loadbalancer show +----------------- + +Show the details for a single load balancer + +.. program:: loadbalancer show +.. code:: bash + + openstack loadbalancer show + + +.. _loadbalancer_show-loadbalancer: +.. describe:: + + Name or UUID of the load balancer. + +loadbalancer create +------------------- + +Create a load balancer + +.. program:: loadbalancer create +.. code:: bash + + openstack loadbalancer create + [--name ] + [--description ] + [--vip-address ] + [--vip-port-id ] + [--vip-subnet-id ] + [--vip-network-id ] + [--project ] + [--enable | --disable] + +.. option:: --name + + New load balancer name. + +.. option:: --description + + Set load balancer description. + +.. option:: --vip-address + + Set the VIP IP Address. + +.. option:: --vip-port-id + + Set Port for the load balancer (name or ID). + +.. option:: --vip-subnet-id + + Set subnet for the load balancer (name or ID). + +.. option:: --vip-network-id + + Set network for the load balancer (name or ID). + +.. option:: --project + + Project for the load balancer (name or ID). + +.. option:: --enable + + Enable load balancer (default). + +.. option:: --disable + + Disable load balancer. + +loadbalancer set +---------------- + +Update a load balancer + +.. program:: loadbalancer set +.. code:: bash + + openstack loadbalancer set + [--enable | --disable] + [--name ] + [--description ] + + +.. _loadbalancer_set-loadbalancer: +.. describe:: + + Name or UUID of the load balancer to update. + +.. option:: --enable + + Enable load balancer. + +.. option:: --disable + + Disable load balancer. + +.. option:: --name + + Set load balancer name. + +.. option:: --description + + Set load balancer description. + +loadbalancer delete +------------------- + +Delete a load balancer + +.. program:: loadbalancer delete +.. code:: bash + + openstack loadbalancer delete + [--cascade] + + +.. _loadbalancer_delete-loadbalancer: +.. describe:: + + Load balancers to delete (name or ID). + +.. option:: --cascade + + Cascade the delete to all child elements of the load balancer. diff --git a/octaviaclient/api/constants.py b/octaviaclient/api/constants.py new file mode 100644 index 0000000..42603bb --- /dev/null +++ b/octaviaclient/api/constants.py @@ -0,0 +1,21 @@ +# 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. +# + +BASE_LOADBALANCER_URL = '/loadbalancers' +BASE_SINGLE_LB_URL = BASE_LOADBALANCER_URL + '/{uuid}' + +BASE_LISTENER_URL = '/listeners' +BASE_SINGLE_LISTENER_URL = BASE_LISTENER_URL + '/{uuid}' + +BASE_POOL_URL = '/pools' +BASE_SINGLE_POOL_URL = BASE_POOL_URL + '/{uuid}' diff --git a/octaviaclient/api/load_balancer_v2.py b/octaviaclient/api/load_balancer_v2.py index 6484afc..ecf853b 100644 --- a/octaviaclient/api/load_balancer_v2.py +++ b/octaviaclient/api/load_balancer_v2.py @@ -15,6 +15,8 @@ from osc_lib.api import api +from octaviaclient.api import constants as const + class APIv2(api.BaseAPI): """Load Balancer v2 API""" @@ -30,11 +32,71 @@ class APIv2(api.BaseAPI): if not self.endpoint.endswith(self._endpoint_suffix): self.endpoint = self.endpoint + self._endpoint_suffix - def load_balancer_list( - self, - **filter - ): - url = '/loadbalancers' - load_balancer_list = self.list(url, **filter)['loadbalancers'] + def load_balancer_list(self, **params): + """List all load balancers + + :param params: + Parameters to filter on (not implemented) + :return: + List of load balancers and their settings + """ + url = const.BASE_LOADBALANCER_URL + load_balancer_list = self.list(url, **params) return load_balancer_list + + def load_balancer_show(self, lb_id): + """Show a load balancer + + :param string lb_id: + ID of the load balancer to show + :return: + A dict of the specified load balancer's settings + """ + load_balancer = self.find(path=const.BASE_LOADBALANCER_URL, + value=lb_id) + + return load_balancer + + def load_balancer_create(self, **params): + """Create a load balancer + + :param params: + Paramaters to create the load balancer with (expects json=) + :return: + A dict of the created load balancer's settings + """ + url = const.BASE_LOADBALANCER_URL + load_balancer = self.create(url, **params) + + return load_balancer + + def load_balancer_delete(self, lb_id, **params): + """Delete a load balancer + + :param string lb_id: + The ID of the load balancer to delete + :param params: + A dict of url parameters + :return: + Response Code from the API + """ + url = const.BASE_SINGLE_LB_URL.format(uuid=lb_id) + load_balancer = self.delete(url, params=params) + + return load_balancer + + def load_balancer_set(self, lb_id, **params): + """Update a load balancer's settings + + :param string lb_id: + The ID of the load baalancer to update + :param params: + A dict of arguments to update a loadbalancer + :return: + A dict of the updated load balancer's settings + """ + url = const.BASE_SINGLE_LB_URL.format(uuid=lb_id) + load_balancer = self.create(url, method='PUT', **params) + + return load_balancer diff --git a/octaviaclient/osc/v2/constants.py b/octaviaclient/osc/v2/constants.py new file mode 100644 index 0000000..3c595f2 --- /dev/null +++ b/octaviaclient/osc/v2/constants.py @@ -0,0 +1,39 @@ +# 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. +# + +LOAD_BALANCER_ROWS = ( + 'admin_state_up', + 'created_at', + 'description', + 'flavor', + 'id', + 'listeners', + 'name', + 'operating_status', + 'pools', + 'project_id', + 'provider', + 'provisioning_status', + 'updated_at', + 'vip_Address', + 'vip_network_id', + 'vip_port_id', + 'vip_subnet_id') + +LOAD_BALANCER_COLUMNS = ( + 'id', + 'name', + 'project_id', + 'vip_address', + 'provisioning_status', + 'provider') diff --git a/octaviaclient/osc/v2/load_balancer.py b/octaviaclient/osc/v2/load_balancer.py index 105d84b..2479d15 100644 --- a/octaviaclient/osc/v2/load_balancer.py +++ b/octaviaclient/osc/v2/load_balancer.py @@ -15,27 +15,244 @@ from cliff import lister +from osc_lib.command import command from osc_lib import utils +from octaviaclient.osc.v2 import constants as const +from octaviaclient.osc.v2 import utils as v2_utils + + +class CreateLoadBalancer(command.ShowOne): + """Create a load balancer""" + + def get_parser(self, prog_name): + parser = super(CreateLoadBalancer, self).get_parser(prog_name) + + parser.add_argument( + '--name', + metavar='', + help="New load balancer name" + ) + parser.add_argument( + '--description', + metavar='', + help="Set load balancer description" + ) + parser.add_argument( + '--vip-address', + metavar='', + help="Set IP Address" + ) + parser.add_argument( + '--vip-port-id', + metavar='', + help="Set Port for the load balancer (name or ID)" + ) + parser.add_argument( + '--vip-subnet-id', + metavar='', + help="Set subnet for the load balancer (name or ID)" + ) + parser.add_argument( + '--vip-network-id', + metavar='', + help="Set network for the load balancer (name or ID)" + ) + parser.add_argument( + '--project', + metavar='', + help="Project for the load balancer (name or ID)" + ) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + action='store_true', + default=True, + help="Enable load balancer (default)" + ) + admin_group.add_argument( + '--disable', + action='store_true', + default=None, + help="Disable load balancer" + ) + + return parser + + def take_action(self, parsed_args): + rows = const.LOAD_BALANCER_ROWS + attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, + parsed_args) + v2_utils.check_loadbalancer_attrs(attrs) + body = {'loadbalancer': attrs} + + data = self.app.client_manager.load_balancer.load_balancer_create( + json=body) + + formatters = { + 'listeners': v2_utils.format_list, + 'pools': v2_utils.format_list, + 'l7policies': v2_utils.format_list + } + + return (rows, (utils.get_dict_properties( + data['loadbalancer'], rows, formatters=formatters))) + + +class DeleteLoadBalancer(command.Command): + """Delete a load balancer""" + + def get_parser(self, prog_name): + parser = super(DeleteLoadBalancer, self).get_parser(prog_name) + + parser.add_argument( + 'loadbalancer', + metavar='', + help="Load balancers to delete (name or ID)" + ) + parser.add_argument( + '--cascade', + action='store_true', + default=None, + help="Cascade the delete to all child elements of the load " + "balancer." + ) + + return parser + + def take_action(self, parsed_args): + attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, + parsed_args) + lb_id = attrs.pop('loadbalancer_id') + + self.app.client_manager.load_balancer.\ + load_balancer_delete(lb_id=lb_id, **attrs) + class ListLoadBalancer(lister.Lister): """List load balancers""" - def parsed_args(self, prog_name): + def get_parser(self, prog_name): parser = super(ListLoadBalancer, self).get_parser(prog_name) + + parser.add_argument( + '--name', + metavar='', + help="List load balancers according to their name" + ) + admin_state_group = parser.add_mutually_exclusive_group() + admin_state_group.add_argument( + '--enable', + action='store_true', + default=None, + help="List load balancers according to their name" + ) + admin_state_group.add_argument( + '--disable', + action='store_true', + default=None, + help="List disabled load balancers" + ) + parser.add_argument( + '--project', + metavar='', + help="List load balancers according to their project (name or ID)" + ) + return parser def take_action(self, parsed_args): - columns = ( - 'ID', - 'Name', - 'Project ID', - 'VIP Address', - 'Provisioning Status',) + columns = const.LOAD_BALANCER_COLUMNS + attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, + parsed_args) + + data = self.app.client_manager.load_balancer.load_balancer_list( + **attrs) - data = self.app.client_manager.load_balancer.load_balancer_list() return (columns, (utils.get_dict_properties( s, columns, formatters={}, - ) for s in data)) + ) for s in data['loadbalancers'])) + + +class ShowLoadBalancer(command.ShowOne): + """Show the details for a single load balancer""" + + def get_parser(self, prog_name): + parser = super(ShowLoadBalancer, self).get_parser(prog_name) + + parser.add_argument( + 'loadbalancer', + metavar='', + help="Name or UUID of the load balancer" + ) + + return parser + + def take_action(self, parsed_args): + rows = const.LOAD_BALANCER_ROWS + + attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, + parsed_args) + lb_id = attrs.pop('loadbalancer_id') + + data = self.app.client_manager.load_balancer.load_balancer_show( + lb_id=lb_id + ) + + formatters = { + 'listeners': v2_utils.format_list, + 'pools': v2_utils.format_list, + 'l7policies': v2_utils.format_list + } + + return (rows, (utils.get_dict_properties( + data, rows, formatters=formatters))) + + +class SetLoadBalancer(command.Command): + """Update a load balancer""" + + def get_parser(self, prog_name): + parser = super(SetLoadBalancer, self).get_parser(prog_name) + + parser.add_argument( + 'loadbalancer', + metavar='', + help='Name or UUID of the load balancer' + ) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + action='store_true', + default=None, + help="Enable load balancer (default)" + ) + admin_group.add_argument( + '--disable', + action='store_true', + default=None, + help="Disable load balancer" + ) + parser.add_argument( + '--name', + metavar='', + help="Set load balancer name" + ) + parser.add_argument( + '--description', + metavar='', + help="Set load balancer description" + ) + + return parser + + def take_action(self, parsed_args): + attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, + parsed_args) + lb_id = attrs.pop('loadbalancer_id') + body = {'loadbalancer': attrs} + + self.app.client_manager.load_balancer.load_balancer_set( + lb_id, json=body) diff --git a/octaviaclient/osc/v2/utils.py b/octaviaclient/osc/v2/utils.py new file mode 100644 index 0000000..fa6b347 --- /dev/null +++ b/octaviaclient/osc/v2/utils.py @@ -0,0 +1,138 @@ +# 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 osc_lib import exceptions + +from openstackclient.identity import common as identity_common + + +def get_resource_id(resource, resource_name, name): + """Converts a resource name into a UUID for consumption for the API + + :param callable resource: + A client_manager callable + :param resource_name: + The resource key name for the dictonary returned + :param name: + The name of the resource to convert to UUID + :return: + The UUID of the found resource + """ + try: + if resource_name == 'project': + if name != 'non-uuid': + project_id = identity_common.find_project( + resource, + name + ).id + return project_id + else: + return 'non-uuid' + else: + names = [re for re in resource()[resource_name] + if re.get('name') == name or re.get('id') == name] + if len(names) > 1: + msg = ("{0} {1} found with name or ID of {2}. Please try " + "again with UUID".format(len(names), resource_name, + name)) + raise exceptions.CommandError(msg) + else: + return names[0].get('id') + except IndexError: + msg = "Unable to locate {0} in {1}".format(name, resource_name) + raise exceptions.CommandError(msg) + + +def get_loadbalancer_attrs(client_manager, parsed_args): + attr_map = { + 'name': ('name', str), + 'description': ('description', str), + 'protocol': ('protocol', str), + 'loadbalancer': ( + 'loadbalancer_id', + 'loadbalancers', + client_manager.load_balancer.load_balancer_list + ), + 'connection_limit': ('connection_limit', str), + 'protocol_port': ('protocol_port', int), + 'default_pool': ('default_pool_id', str), + 'project': ( + 'project_id', + 'project', + client_manager.identity + ), + 'vip_address': ('vip_address', str), + 'vip_port_id': ( + 'vip_port_id', + 'ports', + client_manager.neutronclient.list_ports + ), + 'vip_subnet_id': ( + 'vip_subnet_id', + 'subnets', + client_manager.neutronclient.list_subnets + ), + 'vip_network_id': ( + 'vip_network_id', + 'networks', + client_manager.neutronclient.list_networks + ), + 'enable': ('admin_state_up', lambda x: True), + 'disable': ('admin_state_up', lambda x: False), + 'cascade': ('cascade', lambda x: True) + } + + _attrs = vars(parsed_args) + attrs = _map_attrs(_attrs, attr_map) + + return attrs + + +def check_loadbalancer_attrs(attrs): + verify_args = ['vip_subnet_id', 'vip_network_id', 'vip_port_id'] + if not any(i in attrs.keys() for i in verify_args): + msg = "Missing required argument: Requires one of " \ + "--vip-subnet-id, --vip-network-id or --vip-port-id" + raise exceptions.CommandError(msg) + elif 'vip_port_id' in attrs: + if any(i in attrs.keys() for i in ['vip_subnet_id', 'vip_address']): + msg = "Argument error: --port-id can not be used with " \ + "--vip-network-id or --vip-subnet-id" + raise exceptions.CommandError(msg) + + +def format_headers(headers): + formatted_headers = {} + headers = headers.split(',') + for header in headers: + k, v = header.split('=') + formatted_headers[k] = v + + return formatted_headers + + +def format_list(data): + return '\n'.join(i['id'] for i in data) + + +def _map_attrs(attrs, attr_map): + mapped_attrs = {} + for k, v in attrs.items(): + if v is not None and k in attr_map.keys(): + if len(attr_map[k]) < 3: + mapped_attrs[attr_map[k][0]] = attr_map[k][1](v) + else: + mapped_attrs[attr_map[k][0]] = get_resource_id( + attr_map[k][2], attr_map[k][1], v) + + return mapped_attrs diff --git a/octaviaclient/tests/unit/api/test_load_balancer.py b/octaviaclient/tests/unit/api/test_load_balancer.py index f858b8a..b40531f 100644 --- a/octaviaclient/tests/unit/api/test_load_balancer.py +++ b/octaviaclient/tests/unit/api/test_load_balancer.py @@ -14,6 +14,7 @@ """Load Balancer v2 API Library Tests""" from keystoneauth1 import session +from oslo_utils import uuidutils from requests_mock.contrib import fixture from octaviaclient.api import load_balancer_v2 as lb @@ -23,12 +24,18 @@ FAKE_ACCOUNT = 'q12we34r' FAKE_AUTH = '11223344556677889900' FAKE_URL = 'http://example.com/v2.0/lbaas/' -FAKE_LB = 'rainbarrel' +FAKE_LB = uuidutils.generate_uuid() -LIST_LB_RESP = [ - {'name': 'lb1'}, - {'name': 'lb2'}, -] +LIST_LB_RESP = { + 'loadbalancers': + [{'name': 'lb1'}, + {'name': 'lb2'}] +} + + +SINGLE_LB_RESP = {'loadbalancer': {'id': FAKE_LB, 'name': 'lb1'}} +SINGLE_LB_UPDATE = {"loadbalancer": {"admin_state_up": False}} +SINGLE_LB_UPDATE_INVALID = {"loadbalancer": {"id": 'invalid_param'}} class TestLoadBalancerv2(utils.TestCase): @@ -46,8 +53,47 @@ class TestLoadBalancer(TestLoadBalancerv2): self.requests_mock.register_uri( 'GET', FAKE_URL + 'loadbalancers', - json={'loadbalancers': LIST_LB_RESP}, + json=LIST_LB_RESP, status_code=200, ) ret = self.api.load_balancer_list() self.assertEqual(LIST_LB_RESP, ret) + + def test_show_load_balancer(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + 'loadbalancers/' + FAKE_LB, + json=SINGLE_LB_RESP, + status_code=200 + ) + ret = self.api.load_balancer_show(FAKE_LB) + self.assertEqual(SINGLE_LB_RESP['loadbalancer'], ret) + + def test_create_load_balancer(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + 'loadbalancers', + json=SINGLE_LB_RESP, + status_code=200 + ) + ret = self.api.load_balancer_create(json=SINGLE_LB_RESP) + self.assertEqual(SINGLE_LB_RESP, ret) + + def test_set_load_balancer(self): + self.requests_mock.register_uri( + 'PUT', + FAKE_URL + 'loadbalancers/' + FAKE_LB, + json=SINGLE_LB_UPDATE, + status_code=200 + ) + ret = self.api.load_balancer_set(FAKE_LB, json=SINGLE_LB_UPDATE) + self.assertEqual(SINGLE_LB_UPDATE, ret) + + def test_delete_load_balancer(self): + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + 'loadbalancers/' + FAKE_LB, + status_code=200 + ) + ret = self.api.load_balancer_delete(FAKE_LB) + self.assertEqual(200, ret.status_code) diff --git a/octaviaclient/tests/unit/osc/v2/fakes.py b/octaviaclient/tests/unit/osc/v2/fakes.py index 5f181a9..ccf9d56 100644 --- a/octaviaclient/tests/unit/osc/v2/fakes.py +++ b/octaviaclient/tests/unit/osc/v2/fakes.py @@ -25,6 +25,7 @@ LOADBALANCER = { 'project_id': 'dummyproject', 'vip_address': '192.0.2.2', 'provisioning_status': 'ONLINE', + 'provider': 'octavia' } @@ -66,8 +67,10 @@ class FakeLoadBalancer(object): 'id': str(uuid.uuid4()), 'name': 'lb-name-' + uuid.uuid4().hex, 'project_id': uuid.uuid4().hex, - 'vip_address': '192.0.2.2', + 'vip_address': '192.0.2.124', + 'vip_network_id': uuid.uuid4().hex, 'provisioning_status': 'ONLINE', + 'provider': 'octavia' } lb_info.update(attrs) diff --git a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py index 0852bf7..6b33859 100644 --- a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py +++ b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py @@ -14,6 +14,8 @@ import copy import mock +from osc_lib import exceptions + from octaviaclient.osc.v2 import load_balancer as load_balancer from octaviaclient.tests.unit.osc.v2 import fakes as lb_fakes @@ -23,22 +25,15 @@ AUTH_URL = "http://192.0.2.2" class TestLoadBalancer(lb_fakes.TestLoadBalancerv2): - def setUp(self): - super(TestLoadBalancer, self).setUp() - self.lb_mock = self.app.client_manager.load_balancer.load_balancers - self.lb_mock.reset_mock() - - -class TestLoadBalancerList(TestLoadBalancer): - _lb = lb_fakes.FakeLoadBalancer.create_one_load_balancer() columns = ( - 'ID', - 'Name', - 'Project ID', - 'VIP Address', - 'Provisioning Status', + 'id', + 'name', + 'project_id', + 'vip_address', + 'provisioning_status', + 'provider' ) datalist = ( @@ -48,25 +43,39 @@ class TestLoadBalancerList(TestLoadBalancer): _lb.project_id, _lb.vip_address, _lb.provisioning_status, + _lb.provider ), ) info = { - 'id': _lb.id, - 'name': _lb.name, - 'project_id': _lb.project_id, - 'vip_address': _lb.vip_address, - 'provisioning_status': _lb.provisioning_status, + 'loadbalancers': + [{'id': _lb.id, + 'name': _lb.name, + 'project_id': _lb.project_id, + 'vip_address': _lb.vip_address, + 'vip_network_id': _lb.vip_network_id, + 'provisioning_status': _lb.provisioning_status, + 'provider': _lb.provider + }] } lb_info = copy.deepcopy(info) def setUp(self): - super(TestLoadBalancerList, self).setUp() + super(TestLoadBalancer, self).setUp() + self.lb_mock = self.app.client_manager.load_balancer.load_balancers + self.lb_mock.reset_mock() + self.api_mock = mock.Mock() - self.api_mock.load_balancer_list.return_value = [self.lb_info] + self.api_mock.load_balancer_list.return_value = self.lb_info lb_client = self.app.client_manager lb_client.load_balancer = self.api_mock + lb_client.neutronclient = mock.MagicMock() + +class TestLoadBalancerList(TestLoadBalancer): + + def setUp(self): + super(TestLoadBalancerList, self).setUp() self.cmd = load_balancer.ListLoadBalancer(self.app, None) def test_load_balancer_list_no_options(self): @@ -79,3 +88,115 @@ class TestLoadBalancerList(TestLoadBalancer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + + def test_load_balancer_list_with_options(self): + arglist = ['--name', 'rainbarrel'] + verifylist = [('name', 'rainbarrel')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_list.assert_called_with(name='rainbarrel') + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + +class TestLoadBalancerDelete(TestLoadBalancer): + + def setUp(self): + super(TestLoadBalancerDelete, self).setUp() + self.cmd = load_balancer.DeleteLoadBalancer(self.app, None) + + def test_load_balancer_delete(self): + arglist = [self._lb.id] + verifylist = [ + ('loadbalancer', self._lb.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_delete.assert_called_with( + lb_id=self._lb.id) + + def test_load_balancer_delete_failure(self): + arglist = ['unknown_lb'] + verifylist = [ + ('loadbalancer', 'unknown_lb') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.assertNotCalled(self.api_mock.load_balancer_delete) + + +class TestLoadBalancerCreate(TestLoadBalancer): + + def setUp(self): + super(TestLoadBalancerCreate, self).setUp() + self.api_mock = mock.Mock() + self.api_mock.load_balancer_create.return_value = { + 'loadbalancer': self.lb_info['loadbalancers'][0] + } + lb_client = self.app.client_manager + lb_client.load_balancer = self.api_mock + + self.cmd = load_balancer.CreateLoadBalancer(self.app, None) + + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_create(self, mock_client): + mock_client.return_value = self.lb_info['loadbalancers'][0] + arglist = ['--name', self._lb.name, + '--vip-network-id', self._lb.vip_network_id, + '--project', self._lb.project_id] + verifylist = [ + ('name', self._lb.name), + ('vip_network_id', self._lb.vip_network_id), + ('project', self._lb.project_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_create.assert_called_with( + json={'loadbalancer': self.lb_info['loadbalancers'][0]}) + + +class TestLoadBalancerShow(TestLoadBalancer): + + def setUp(self): + super(TestLoadBalancerShow, self).setUp() + self.api_mock = mock.Mock() + self.api_mock.load_balancer_list.return_value = self.lb_info + self.api_mock.load_balancer_show.return_value = { + 'loadbalancer': self.lb_info['loadbalancers'][0]} + lb_client = self.app.client_manager + lb_client.load_balancer = self.api_mock + + self.cmd = load_balancer.ShowLoadBalancer(self.app, None) + + def test_load_balancer_show(self): + arglist = [self._lb.id] + verifylist = [ + ('loadbalancer', self._lb.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_show.assert_called_with(lb_id=self._lb.id) + + +class TestLoadBalancerSet(TestLoadBalancer): + def setUp(self): + super(TestLoadBalancerSet, self).setUp() + self.cmd = load_balancer.SetLoadBalancer(self.app, None) + + def test_load_balancer_set(self): + arglist = [self._lb.id, '--name', 'new_name'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('name', 'new_name') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_set.assert_called_with( + self._lb.id, json={'loadbalancer': {'name': 'new_name'}}) diff --git a/requirements.txt b/requirements.txt index 6de9f4e..7d54946 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,33 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr!=2.1.0,>=2.0.0 # Apache-2.0 +appdirs>=1.3.0 # MIT License +Babel>=2.3.4,!=2.4.0 # BSD +cliff>=2.6.0 # Apache-2.0 +cmd2>=0.6.7 # MIT +debtcollector>=1.2.0 # Apache-2.0 +funcsigs>=0.4;python_version=='2.7' or python_version=='2.6' # Apache-2.0 +iso8601>=0.1.11 # MIT +keystoneauth1>=2.20.0 # Apache-2.0 +monotonic>=0.6 # Apache-2.0 +netaddr>=0.7.13,!=0.7.16 # BSD +netifaces>=0.10.4 # MIT +python-neutronclient>=6.3.0 # Apache-2.0 +python-openstackclient>=3.3.0,!=3.10.0 # Apache-2.0 +os-client-config>=1.27.0 # Apache-2.0 +osc-lib>=1.5.1 # Apache-2.0 +oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 +pbr>=2.0.0,!=2.1.0 # Apache-2.0 +positional>=1.1.1 # Apache-2.0 +PrettyTable>=0.7.1,<0.8 # BSD +pyparsing>=2.1.0 # MIT +pytz>=2013.6 # MIT +PyYAML>=3.10.0 # MIT +requests>=2.10.0,!=2.12.2,!=2.13.0 # Apache-2.0 +requestsexceptions>=1.2.0 # Apache-2.0 +simplejson>=2.2.0 # MIT +six>=1.9.0 # MIT +stevedore>=1.20.0 # Apache-2.0 +unicodecsv>=0.8.0;python_version<'3.0' # BSD +wrapt>=1.7.0 # BSD License diff --git a/setup.cfg b/setup.cfg index bc63e2d..10b7641 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,11 @@ openstack.cli.extension = load_balancer = octaviaclient.osc.plugin openstack.load_balancer.v2 = + loadbalancer_create = octaviaclient.osc.v2.load_balancer:CreateLoadBalancer loadbalancer_list = octaviaclient.osc.v2.load_balancer:ListLoadBalancer + loadbalancer_show = octaviaclient.osc.v2.load_balancer:ShowLoadBalancer + loadbalancer_delete = octaviaclient.osc.v2.load_balancer:DeleteLoadBalancer + loadbalancer_set = octaviaclient.osc.v2.load_balancer:SetLoadBalancer [build_sphinx] source-dir = doc/source diff --git a/test-requirements.txt b/test-requirements.txt index b19040a..24d336e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,6 +7,7 @@ requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 mock>=2.0 # BSD +keystoneauth1>=2.20.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 sphinx!=1.6.1,>=1.5.1 # BSD