diff --git a/doc/source/enforcer.py b/doc/source/enforcer.py index 8f653b099..40740e033 100644 --- a/doc/source/enforcer.py +++ b/doc/source/enforcer.py @@ -36,6 +36,7 @@ def get_proxy_methods(): "openstack.image.v1._proxy", "openstack.image.v2._proxy", "openstack.key_manager.v1._proxy", + "openstack.load_balancer.v2._proxy", "openstack.message.v1._proxy", "openstack.message.v2._proxy", "openstack.metric.v1._proxy", diff --git a/doc/source/users/index.rst b/doc/source/users/index.rst index 2209a451d..c06b98577 100644 --- a/doc/source/users/index.rst +++ b/doc/source/users/index.rst @@ -114,6 +114,7 @@ The following services have exposed *Resource* classes. Identity Image Key Management + Load Balancer Metric Network Orchestration diff --git a/doc/source/users/proxies/load_balancer_v2.rst b/doc/source/users/proxies/load_balancer_v2.rst index 220c4f708..ef566a563 100644 --- a/doc/source/users/proxies/load_balancer_v2.rst +++ b/doc/source/users/proxies/load_balancer_v2.rst @@ -16,7 +16,8 @@ Load Balancer Operations .. autoclass:: openstack.load_balancer.v2._proxy.Proxy .. automethod:: openstack.load_balancer.v2._proxy.Proxy.create_load_balancer - .. automethod:: openstack.load_balancer.v2._proxy.Proxy.get_load_balancer - .. automethod:: openstack.load_balancer.v2._proxy.Proxy.load_balancers .. automethod:: openstack.load_balancer.v2._proxy.Proxy.delete_load_balancer .. automethod:: openstack.load_balancer.v2._proxy.Proxy.find_load_balancer + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.get_load_balancer + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.load_balancers + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.update_load_balancer diff --git a/doc/source/users/resources/load_balancer/index.rst b/doc/source/users/resources/load_balancer/index.rst new file mode 100644 index 000000000..61838c730 --- /dev/null +++ b/doc/source/users/resources/load_balancer/index.rst @@ -0,0 +1,7 @@ +Load Balancer Resources +======================= + +.. toctree:: + :maxdepth: 1 + + v2/load_balancer diff --git a/doc/source/users/resources/load_balancer/v2/load_balancer.rst b/doc/source/users/resources/load_balancer/v2/load_balancer.rst new file mode 100644 index 000000000..fd55e4668 --- /dev/null +++ b/doc/source/users/resources/load_balancer/v2/load_balancer.rst @@ -0,0 +1,13 @@ +openstack.load_balancer.v2.load_balancer +======================================== + +.. automodule:: openstack.load_balancer.v2.load_balancer + +The LoadBalancer Class +---------------------- + +The ``LoadBalancer`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.load_balancer.v2.load_balancer.LoadBalancer + :members: + diff --git a/openstack/load_balancer/load_balancer_service.py b/openstack/load_balancer/load_balancer_service.py index 1ff22b22f..3c645e6f6 100644 --- a/openstack/load_balancer/load_balancer_service.py +++ b/openstack/load_balancer/load_balancer_service.py @@ -16,11 +16,11 @@ from openstack import service_filter class LoadBalancerService(service_filter.ServiceFilter): """The load balancer service.""" - valid_versions = [service_filter.ValidVersion('v2')] + valid_versions = [service_filter.ValidVersion('v2', 'v2.0')] def __init__(self, version=None): """Create a load balancer service.""" super(LoadBalancerService, self).__init__( - service_type='load_balancer', + service_type='load-balancer', version=version ) diff --git a/openstack/load_balancer/v2/_proxy.py b/openstack/load_balancer/v2/_proxy.py index 224fddb57..4533cb664 100644 --- a/openstack/load_balancer/v2/_proxy.py +++ b/openstack/load_balancer/v2/_proxy.py @@ -80,3 +80,17 @@ class Proxy(proxy2.BaseProxy): """ return self._find(_lb.LoadBalancer, name_or_id, ignore_missing=ignore_missing) + + def update_load_balancer(self, load_balancer, **attrs): + """Update a load balancer + + :param load_balancer: The load_balancer can be either the name or a + :class:`~openstack.load_balancer.v2.load_balancer.LoadBalancer` + instance + :param dict attrs: The attributes to update on the load balancer + represented by ``load_balancer``. + + :returns: The updated load_balancer + :rtype: :class:`~openstack.load_balancer.v2.load_balancer.LoadBalancer` + """ + return self._update(_lb.LoadBalancer, load_balancer, **attrs) diff --git a/openstack/load_balancer/v2/load_balancer.py b/openstack/load_balancer/v2/load_balancer.py index 509f0ae80..7c444376a 100644 --- a/openstack/load_balancer/v2/load_balancer.py +++ b/openstack/load_balancer/v2/load_balancer.py @@ -17,22 +17,31 @@ from openstack import resource2 as resource class LoadBalancer(resource.Resource): resource_key = 'loadbalancer' resources_key = 'loadbalancers' - base_path = '/loadbalancers' + base_path = '/v2.0/lbaas/loadbalancers' service = lb_service.LoadBalancerService() # capabilities allow_create = True - allow_list = True allow_get = True + allow_update = True allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'description', 'flavor', 'name', 'project_id', 'provider', + 'vip_address', 'vip_network_id', 'vip_port_id', 'vip_subnet_id', + is_admin_state_up='admin_state_up' + ) #: Properties + #: The administrative state of the load balancer *Type: bool* + is_admin_state_up = resource.Body('admin_state_up', type=bool) #: Timestamp when the load balancer was created created_at = resource.Body('created_at') #: The load balancer description description = resource.Body('description') - #: The administrative state of the load balancer *Type: bool* - is_admin_state_up = resource.Body('admin_state_up', type=bool) + #: The load balancer flavor + flavor = resource.Body('flavor') #: List of listeners associated with this load balancer listeners = resource.Body('listeners', type=list) #: The load balancer name @@ -43,13 +52,17 @@ class LoadBalancer(resource.Resource): pools = resource.Body('pools', type=list) #: The ID of the project this load balancer is associated with. project_id = resource.Body('project_id') + #: Provider name for the load balancer. + provider = resource.Body('provider') #: The provisioning status of this load balancer provisioning_status = resource.Body('provisioning_status') + #: Timestamp when the load balancer was last updated + updated_at = resource.Body('updated_at') #: VIP address of load balancer vip_address = resource.Body('vip_address') + #: VIP netowrk ID + vip_network_id = resource.Body('vip_network_id') #: VIP port ID vip_port_id = resource.Body('vip_port_id') #: VIP subnet ID vip_subnet_id = resource.Body('vip_subnet_id') - #: Timestamp when the load balancer was last updated - updated_at = resource.Body('updated_at') diff --git a/openstack/session.py b/openstack/session.py index f3a55e902..d989d1172 100644 --- a/openstack/session.py +++ b/openstack/session.py @@ -330,6 +330,11 @@ class Session(_session.Session): self.endpoint_cache[key] = sc_endpoint return sc_endpoint + # We just want what is returned from catalog + if service_type == "load-balancer": + self.endpoint_cache[key] = sc_endpoint + return sc_endpoint + endpoint = self._get_endpoint_versions(service_type, sc_endpoint) profile_version = self._parse_version(filt.version) diff --git a/openstack/tests/functional/load_balancer/base.py b/openstack/tests/functional/load_balancer/base.py new file mode 100644 index 000000000..09f96f093 --- /dev/null +++ b/openstack/tests/functional/load_balancer/base.py @@ -0,0 +1,63 @@ +# Copyright 2017 Rackspace, US Inc. +# 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 time + +from openstack import exceptions +from openstack.tests.functional import base + + +class BaseLBFunctionalTest(base.BaseFunctionalTest): + + @classmethod + def setUpClass(cls): + super(BaseLBFunctionalTest, cls).setUpClass() + + @classmethod + def lb_wait_for_status(cls, lb, status, failures, interval=1, wait=120): + """Wait for load balancer to be in a particular provisioning status. + + :param lb: The load balancer to wait on to reach the status. + :type lb: :class:`~openstack.load_blanacer.v2.load_balancer + :param status: Desired status of the resource. + :param list failures: Statuses that would indicate the transition + failed such as 'ERROR'. + :param interval: Number of seconds to wait between checks. + :param wait: Maximum number of seconds to wait for transition. + Note, most actions should easily finish in 120 seconds, + but for load balancer create slow hosts can take up to + ten minutes for nova to fully boot a VM. + :return: None + :raises: :class:`~openstack.exceptions.ResourceTimeout` transition + to status failed to occur in wait seconds. + :raises: :class:`~openstack.exceptions.ResourceFailure` resource + transitioned to one of the failure states. + """ + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < wait: + lb = cls.conn.load_balancer.get_load_balancer(lb.id) + if lb.provisioning_status == status: + return None + if lb.provisioning_status in failures: + msg = ("Load Balancer %s transitioned to failure state %s" % + (lb.id, lb.provisioning_status)) + raise exceptions.ResourceFailure(msg) + time.sleep(interval) + total_sleep += interval + msg = "Timeout waiting for Load Balancer %s to transition to %s" % ( + lb.id, status) + raise exceptions.ResourceTimeout(msg) diff --git a/openstack/tests/functional/load_balancer/v2/test_load_balancer.py b/openstack/tests/functional/load_balancer/v2/test_load_balancer.py index acf953498..a6b126651 100644 --- a/openstack/tests/functional/load_balancer/v2/test_load_balancer.py +++ b/openstack/tests/functional/load_balancer/v2/test_load_balancer.py @@ -15,30 +15,43 @@ import uuid from openstack.load_balancer.v2 import load_balancer from openstack.tests.functional import base +from openstack.tests.functional.load_balancer import base as lb_base -@unittest.skipUnless(base.service_exists(service_type='load_balancer'), - 'Load-balancing service does not exist') -class TestLoadBalancer(base.BaseFunctionalTest): +@unittest.skipUnless(base.service_exists(service_type='load-balancer'), + 'Load-balancer service does not exist') +class TestLoadBalancer(lb_base.BaseLBFunctionalTest): NAME = uuid.uuid4().hex ID = None - VIP_SUBNET_ID = uuid.uuid4().hex + VIP_SUBNET_ID = None + PROJECT_ID = None + UPDATE_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): super(TestLoadBalancer, cls).setUpClass() + subnets = list(cls.conn.network.subnets()) + cls.VIP_SUBNET_ID = subnets[0].id + cls.PROJECT_ID = cls.conn.session.get_project_id() test_lb = cls.conn.load_balancer.create_load_balancer( - name=cls.NAME, vip_subnet_id=cls.VIP_SUBNET_ID) + name=cls.NAME, vip_subnet_id=cls.VIP_SUBNET_ID, + project_id=cls.PROJECT_ID) assert isinstance(test_lb, load_balancer.LoadBalancer) cls.assertIs(cls.NAME, test_lb.name) + # Wait for the LB to go ACTIVE. On non-virtualization enabled hosts + # it can take nova up to ten minutes to boot a VM. + cls.lb_wait_for_status(test_lb, status='ACTIVE', + failures=['ERROR'], interval=1, wait=600) cls.ID = test_lb.id @classmethod def tearDownClass(cls): - test_lb = cls.conn.load_balancer.delete_load_balancer( + test_lb = cls.conn.load_balancer.get_load_balancer(cls.ID) + cls.lb_wait_for_status(test_lb, status='ACTIVE', + failures=['ERROR']) + cls.conn.load_balancer.delete_load_balancer( cls.ID, ignore_missing=False) - cls.assertIs(None, test_lb) def test_find(self): test_lb = self.conn.load_balancer.find_load_balancer(self.NAME) @@ -53,3 +66,11 @@ class TestLoadBalancer(base.BaseFunctionalTest): def test_list(self): names = [lb.name for lb in self.conn.load_balancer.load_balancers()] self.assertIn(self.NAME, names) + + def test_update(self): + update_lb = self.conn.load_balancer.update_load_balancer( + self.ID, name=self.UPDATE_NAME) + self.lb_wait_for_status(update_lb, status='ACTIVE', + failures=['ERROR']) + test_lb = self.conn.load_balancer.get_load_balancer(self.ID) + self.assertEqual(self.UPDATE_NAME, test_lb.name) diff --git a/openstack/tests/unit/load_balancer/test_load_balancer.py b/openstack/tests/unit/load_balancer/test_load_balancer.py index 0e832ab82..98196b312 100644 --- a/openstack/tests/unit/load_balancer/test_load_balancer.py +++ b/openstack/tests/unit/load_balancer/test_load_balancer.py @@ -11,24 +11,29 @@ # under the License. import testtools +import uuid from openstack.load_balancer.v2 import load_balancer IDENTIFIER = 'IDENTIFIER' EXAMPLE = { 'admin_state_up': True, - 'created_at': '3', + 'created_at': '2017-07-17T12:14:57.233772', 'description': 'fake_description', + 'flavor': uuid.uuid4(), 'id': IDENTIFIER, - 'listeners': [{'id', '4'}], + 'listeners': [{'id', uuid.uuid4()}], 'name': 'test_load_balancer', - 'operating_status': '6', - 'provisioning_status': '7', - 'project_id': '8', - 'vip_address': '9', - 'vip_subnet_id': '10', - 'vip_port_id': '11', - 'pools': [{'id', '13'}], + 'operating_status': 'ONLINE', + 'pools': [{'id', uuid.uuid4()}], + 'project_id': uuid.uuid4(), + 'provider': 'fake_provider', + 'provisioning_status': 'ACTIVE', + 'updated_at': '2017-07-17T12:16:57.233772', + 'vip_address': '192.0.2.5', + 'vip_network_id': uuid.uuid4(), + 'vip_port_id': uuid.uuid4(), + 'vip_subnet_id': uuid.uuid4(), } @@ -38,13 +43,15 @@ class TestLoadBalancer(testtools.TestCase): test_load_balancer = load_balancer.LoadBalancer() self.assertEqual('loadbalancer', test_load_balancer.resource_key) self.assertEqual('loadbalancers', test_load_balancer.resources_key) - self.assertEqual('/loadbalancers', test_load_balancer.base_path) - self.assertEqual('load_balancer', + self.assertEqual('/v2.0/lbaas/loadbalancers', + test_load_balancer.base_path) + self.assertEqual('load-balancer', test_load_balancer.service.service_type) self.assertTrue(test_load_balancer.allow_create) self.assertTrue(test_load_balancer.allow_get) self.assertTrue(test_load_balancer.allow_delete) self.assertTrue(test_load_balancer.allow_list) + self.assertTrue(test_load_balancer.allow_update) def test_make_it(self): test_load_balancer = load_balancer.LoadBalancer(**EXAMPLE) @@ -52,18 +59,23 @@ class TestLoadBalancer(testtools.TestCase): self.assertEqual(EXAMPLE['created_at'], test_load_balancer.created_at), self.assertEqual(EXAMPLE['description'], test_load_balancer.description) + self.assertEqual(EXAMPLE['flavor'], test_load_balancer.flavor) self.assertEqual(EXAMPLE['id'], test_load_balancer.id) self.assertEqual(EXAMPLE['listeners'], test_load_balancer.listeners) self.assertEqual(EXAMPLE['name'], test_load_balancer.name) self.assertEqual(EXAMPLE['operating_status'], test_load_balancer.operating_status) + self.assertEqual(EXAMPLE['pools'], test_load_balancer.pools) + self.assertEqual(EXAMPLE['project_id'], test_load_balancer.project_id) + self.assertEqual(EXAMPLE['provider'], test_load_balancer.provider) self.assertEqual(EXAMPLE['provisioning_status'], test_load_balancer.provisioning_status) - self.assertEqual(EXAMPLE['project_id'], test_load_balancer.project_id) + self.assertEqual(EXAMPLE['updated_at'], test_load_balancer.updated_at), self.assertEqual(EXAMPLE['vip_address'], test_load_balancer.vip_address) - self.assertEqual(EXAMPLE['vip_subnet_id'], - test_load_balancer.vip_subnet_id) + self.assertEqual(EXAMPLE['vip_network_id'], + test_load_balancer.vip_network_id) self.assertEqual(EXAMPLE['vip_port_id'], test_load_balancer.vip_port_id) - self.assertEqual(EXAMPLE['pools'], test_load_balancer.pools) + self.assertEqual(EXAMPLE['vip_subnet_id'], + test_load_balancer.vip_subnet_id) diff --git a/openstack/tests/unit/load_balancer/test_load_balancer_service.py b/openstack/tests/unit/load_balancer/test_load_balancer_service.py index dd11ae55f..6ad82dfc5 100644 --- a/openstack/tests/unit/load_balancer/test_load_balancer_service.py +++ b/openstack/tests/unit/load_balancer/test_load_balancer_service.py @@ -19,10 +19,10 @@ class TestLoadBalancingService(testtools.TestCase): def test_service(self): sot = lb_service.LoadBalancerService() - self.assertEqual('load_balancer', sot.service_type) + self.assertEqual('load-balancer', sot.service_type) self.assertEqual('public', sot.interface) self.assertIsNone(sot.region) self.assertIsNone(sot.service_name) self.assertEqual(1, len(sot.valid_versions)) self.assertEqual('v2', sot.valid_versions[0].module) - self.assertEqual('v2', sot.valid_versions[0].path) + self.assertEqual('v2.0', sot.valid_versions[0].path) diff --git a/openstack/tests/unit/load_balancer/test_proxy.py b/openstack/tests/unit/load_balancer/test_proxy.py index 97cdec134..267ea0a98 100644 --- a/openstack/tests/unit/load_balancer/test_proxy.py +++ b/openstack/tests/unit/load_balancer/test_proxy.py @@ -40,3 +40,7 @@ class TestLoadBalancerProxy(test_proxy_base2.TestProxyBase): def test_load_balancer_find(self): self.verify_find(self.proxy.find_load_balancer, lb.LoadBalancer) + + def test_load_balancer_update(self): + self.verify_update(self.proxy.update_load_balancer, + lb.LoadBalancer) diff --git a/openstack/tests/unit/load_balancer/test_version.py b/openstack/tests/unit/load_balancer/test_version.py index 1b77eda26..8461c4e51 100644 --- a/openstack/tests/unit/load_balancer/test_version.py +++ b/openstack/tests/unit/load_balancer/test_version.py @@ -29,7 +29,7 @@ class TestVersion(testtools.TestCase): self.assertEqual('version', sot.resource_key) self.assertEqual('versions', sot.resources_key) self.assertEqual('/', sot.base_path) - self.assertEqual('load_balancer', sot.service.service_type) + self.assertEqual('load-balancer', sot.service.service_type) self.assertFalse(sot.allow_create) self.assertFalse(sot.allow_get) self.assertFalse(sot.allow_update) diff --git a/openstack/tests/unit/test_profile.py b/openstack/tests/unit/test_profile.py index 272918171..c11d05f2a 100644 --- a/openstack/tests/unit/test_profile.py +++ b/openstack/tests/unit/test_profile.py @@ -27,7 +27,7 @@ class TestProfile(base.TestCase): 'identity', 'image', 'key-manager', - 'load_balancer', + 'load-balancer', 'messaging', 'metering', 'network', @@ -46,7 +46,7 @@ class TestProfile(base.TestCase): self.assertEqual('v1', prof.get_filter('database').version) self.assertEqual('v3', prof.get_filter('identity').version) self.assertEqual('v2', prof.get_filter('image').version) - self.assertEqual('v2', prof.get_filter('load_balancer').version) + self.assertEqual('v2', prof.get_filter('load-balancer').version) self.assertEqual('v2', prof.get_filter('network').version) self.assertEqual('v1', prof.get_filter('object-store').version) self.assertEqual('v1', prof.get_filter('orchestration').version) diff --git a/post_test_hook.sh b/post_test_hook.sh index 567dd1f7d..13421d423 100755 --- a/post_test_hook.sh +++ b/post_test_hook.sh @@ -14,7 +14,11 @@ cat /etc/openstack/clouds.yaml cd ${DIR} echo '=functional==============================================' -tox -e functional +if [[ -n "$1" ]]; then + tox -e functional -- $1 +else + tox -e functional +fi FUNCTIONAL_RESULT=\$? echo '=examples================================================' tox -e examples