From f6b01f86b23d8d502a9141bbd6bcdd9fc2733561 Mon Sep 17 00:00:00 2001 From: Nakul Dahiwade Date: Wed, 29 Mar 2017 16:58:30 +0000 Subject: [PATCH] Introduce Member for Octavia (load balancing) This patch introduces Resource for Member. Co-Authored-By: Michael Johnson Change-Id: Ie325270096bff313f11cabe2cba7129dd02acd6f --- doc/source/users/proxies/load_balancer_v2.rst | 12 ++ .../users/resources/load_balancer/index.rst | 1 + .../resources/load_balancer/v2/member.rst | 12 ++ openstack/load_balancer/v2/_proxy.py | 112 ++++++++++++++++++ openstack/load_balancer/v2/member.py | 69 +++++++++++ .../load_balancer/v2/test_load_balancer.py | 54 +++++++++ .../tests/unit/load_balancer/test_member.py | 62 ++++++++++ .../tests/unit/load_balancer/test_proxy.py | 47 ++++++++ 8 files changed, 369 insertions(+) create mode 100644 doc/source/users/resources/load_balancer/v2/member.rst create mode 100644 openstack/load_balancer/v2/member.py create mode 100644 openstack/tests/unit/load_balancer/test_member.py diff --git a/doc/source/users/proxies/load_balancer_v2.rst b/doc/source/users/proxies/load_balancer_v2.rst index c7a3bbb4a..0c728c07d 100644 --- a/doc/source/users/proxies/load_balancer_v2.rst +++ b/doc/source/users/proxies/load_balancer_v2.rst @@ -45,3 +45,15 @@ Pool Operations .. automethod:: openstack.load_balancer.v2._proxy.Proxy.get_pool .. automethod:: openstack.load_balancer.v2._proxy.Proxy.pools .. automethod:: openstack.load_balancer.v2._proxy.Proxy.update_pool + +Member Operations +^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.load_balancer.v2._proxy.Proxy + + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.create_member + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.delete_member + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.find_member + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.get_member + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.members + .. automethod:: openstack.load_balancer.v2._proxy.Proxy.update_member diff --git a/doc/source/users/resources/load_balancer/index.rst b/doc/source/users/resources/load_balancer/index.rst index 784ec547c..e007d7af7 100644 --- a/doc/source/users/resources/load_balancer/index.rst +++ b/doc/source/users/resources/load_balancer/index.rst @@ -7,3 +7,4 @@ Load Balancer Resources v2/load_balancer v2/listener v2/pool + v2/member diff --git a/doc/source/users/resources/load_balancer/v2/member.rst b/doc/source/users/resources/load_balancer/v2/member.rst new file mode 100644 index 000000000..1fff01f07 --- /dev/null +++ b/doc/source/users/resources/load_balancer/v2/member.rst @@ -0,0 +1,12 @@ +openstack.load_balancer.v2.member +================================= + +.. automodule:: openstack.load_balancer.v2.member + +The Member Class +---------------- + +The ``Member`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.load_balancer.v2.member.Member + :members: diff --git a/openstack/load_balancer/v2/_proxy.py b/openstack/load_balancer/v2/_proxy.py index db199604f..2f27c786e 100644 --- a/openstack/load_balancer/v2/_proxy.py +++ b/openstack/load_balancer/v2/_proxy.py @@ -12,6 +12,7 @@ from openstack.load_balancer.v2 import listener as _listener from openstack.load_balancer.v2 import load_balancer as _lb +from openstack.load_balancer.v2 import member as _member from openstack.load_balancer.v2 import pool as _pool from openstack import proxy2 @@ -256,3 +257,114 @@ class Proxy(proxy2.BaseProxy): :rtype: :class:`~openstack.load_balancer.v2.pool.Pool` """ return self._update(_pool.Pool, pool, **attrs) + + def create_member(self, pool, **attrs): + """Create a new member from attributes + + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member will be created in. + :param dict attrs: Keyword arguments which will be used to create + a :class:`~openstack.load_balancer.v2.member.Member`, + comprised of the properties on the Member class. + + :returns: The results of member creation + :rtype: :class:`~openstack.load_balancer.v2.member.Member` + """ + poolobj = self._get_resource(_pool.Pool, pool) + return self._create(_member.Member, pool_id=poolobj.id, + **attrs) + + def delete_member(self, member, pool, ignore_missing=True): + """Delete a member + + :param member: + The member can be either the ID of a member or a + :class:`~openstack.load_balancer.v2.member.Member` instance. + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member belongs to. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the member does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent member. + + :returns: ``None`` + """ + poolobj = self._get_resource(_pool.Pool, pool) + self._delete(_member.Member, member, + ignore_missing=ignore_missing, pool_id=poolobj.id) + + def find_member(self, name_or_id, pool, ignore_missing=True): + """Find a single member + + :param str name_or_id: The name or ID of a member. + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member belongs to. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the resource does not exist. + When set to ``True``, None will be returned when + attempting to find a nonexistent resource. + + :returns: One :class:`~openstack.load_balancer.v2.member.Member` + or None + """ + poolobj = self._get_resource(_pool.Pool, pool) + return self._find(_member.Member, name_or_id, + ignore_missing=ignore_missing, pool_id=poolobj.id) + + def get_member(self, member, pool): + """Get a single member + + :param member: The member can be the ID of a member or a + :class:`~openstack.load_balancer.v2.member.Member` + instance. + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member belongs to. + + :returns: One :class:`~openstack.load_balancer.v2.member.Member` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + poolobj = self._get_resource(_pool.Pool, pool) + return self._get(_member.Member, member, + pool_id=poolobj.id) + + def members(self, pool, **query): + """Return a generator of members + + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member belongs to. + :param dict query: Optional query parameters to be sent to limit + the resources being returned. Valid parameters are: + + :returns: A generator of member objects + :rtype: :class:`~openstack.load_balancer.v2.member.Member` + """ + poolobj = self._get_resource(_pool.Pool, pool) + return self._list(_member.Member, paginated=True, + pool_id=poolobj.id, **query) + + def update_member(self, member, pool, **attrs): + """Update a member + + :param member: Either the ID of a member or a + :class:`~openstack.load_balancer.v2.member.Member` + instance. + :param pool: The pool can be either the ID of a pool or a + :class:`~openstack.load_balancer.v2.pool.Pool` instance + that the member belongs to. + :param dict attrs: The attributes to update on the member + represented by ``member``. + + :returns: The updated member + :rtype: :class:`~openstack.load_balancer.v2.member.Member` + """ + poolobj = self._get_resource(_pool.Pool, pool) + return self._update(_member.Member, member, + pool_id=poolobj.id, **attrs) diff --git a/openstack/load_balancer/v2/member.py b/openstack/load_balancer/v2/member.py new file mode 100644 index 000000000..f40b4dd83 --- /dev/null +++ b/openstack/load_balancer/v2/member.py @@ -0,0 +1,69 @@ +# 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 openstack.load_balancer import load_balancer_service as lb_service +from openstack import resource2 as resource + + +class Member(resource.Resource): + resource_key = 'member' + resources_key = 'members' + base_path = '/v2.0/lbaas/pools/%(pool_id)s/members' + service = lb_service.LoadBalancerService() + + # capabilities + allow_create = True + allow_get = True + allow_update = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'address', 'name', 'protocol_port', 'subnet_id', 'weight', + 'created_at', 'updated_at', 'provisioning_status', 'operating_status', + 'project_id', 'monitor_address', 'monitor_port', + is_admin_state_up='admin_state_up', + ) + + # Properties + #: The IP address of the member. + address = resource.Body('address') + #: Timestamp when the member was created. + created_at = resource.Body('created_at') + #: The administrative state of the member, which is up ``True`` or + #: down ``False``. *Type: bool* + is_admin_state_up = resource.Body('admin_state_up', type=bool) + #: IP address used to monitor this member + monitor_address = resource.Body('monitor_address') + #: Port used to monitor this member + monitor_port = resource.Body('monitor_port', type=int) + #: Name of the member. + name = resource.Body('name') + #: Operating status of the member. + operating_status = resource.Body('operating_status') + #: The ID of the owning pool. + pool_id = resource.URI('pool_id') + #: The provisioning status of this member. + provisioning_status = resource.Body('provisioning_status') + #: The ID of the project this member is associated with. + project_id = resource.Body('project_id') + #: The port on which the application is hosted. + protocol_port = resource.Body('protocol_port', type=int) + #: Subnet ID in which to access this member. + subnet_id = resource.Body('subnet_id') + #: Timestamp when the member was last updated. + updated_at = resource.Body('updated_at') + #: A positive integer value that indicates the relative portion of traffic + #: that this member should receive from the pool. For example, a member + #: with a weight of 10 receives five times as much traffic as a member + #: with weight of 2. + weight = resource.Body('weight', type=int) 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 34335609c..9b47ad694 100644 --- a/openstack/tests/functional/load_balancer/v2/test_load_balancer.py +++ b/openstack/tests/functional/load_balancer/v2/test_load_balancer.py @@ -15,6 +15,7 @@ import uuid from openstack.load_balancer.v2 import listener from openstack.load_balancer.v2 import load_balancer +from openstack.load_balancer.v2 import member from openstack.load_balancer.v2 import pool from openstack.tests.functional import base from openstack.tests.functional.load_balancer import base as lb_base @@ -26,16 +27,20 @@ class TestLoadBalancer(lb_base.BaseLBFunctionalTest): LB_NAME = uuid.uuid4().hex LISTENER_NAME = uuid.uuid4().hex + MEMBER_NAME = uuid.uuid4().hex POOL_NAME = uuid.uuid4().hex UPDATE_NAME = uuid.uuid4().hex LB_ID = None LISTENER_ID = None + MEMBER_ID = None POOL_ID = None VIP_SUBNET_ID = None PROJECT_ID = None PROTOCOL = 'HTTP' PROTOCOL_PORT = 80 LB_ALGORITHM = 'ROUND_ROBIN' + MEMBER_ADDRESS = '192.0.2.16' + WEIGHT = 10 # Note: Creating load balancers can be slow on some hosts due to nova # instance boot times (up to ten minutes) so we are consolidating @@ -75,11 +80,24 @@ class TestLoadBalancer(lb_base.BaseLBFunctionalTest): cls.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) + test_member = cls.conn.load_balancer.create_member( + pool=cls.POOL_ID, name=cls.MEMBER_NAME, address=cls.MEMBER_ADDRESS, + protocol_port=cls.PROTOCOL_PORT, weight=cls.WEIGHT) + assert isinstance(test_member, member.Member) + cls.assertIs(cls.MEMBER_NAME, test_member.name) + cls.MEMBER_ID = test_member.id + cls.lb_wait_for_status(test_lb, status='ACTIVE', + failures=['ERROR']) + @classmethod def tearDownClass(cls): test_lb = cls.conn.load_balancer.get_load_balancer(cls.LB_ID) cls.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) + cls.conn.load_balancer.delete_member( + cls.MEMBER_ID, cls.POOL_ID, ignore_missing=False) + cls.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) + cls.conn.load_balancer.delete_pool(cls.POOL_ID, ignore_missing=False) cls.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) @@ -180,3 +198,39 @@ class TestLoadBalancer(lb_base.BaseLBFunctionalTest): self.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) test_pool = self.conn.load_balancer.get_pool(self.POOL_ID) self.assertEqual(self.POOL_NAME, test_pool.name) + + def test_member_find(self): + test_member = self.conn.load_balancer.find_member(self.MEMBER_NAME, + self.POOL_ID) + self.assertEqual(self.MEMBER_ID, test_member.id) + + def test_member_get(self): + test_member = self.conn.load_balancer.get_member(self.MEMBER_ID, + self.POOL_ID) + self.assertEqual(self.MEMBER_NAME, test_member.name) + self.assertEqual(self.MEMBER_ID, test_member.id) + self.assertEqual(self.MEMBER_ADDRESS, test_member.address) + self.assertEqual(self.PROTOCOL_PORT, test_member.protocol_port) + self.assertEqual(self.WEIGHT, test_member.weight) + + def test_member_list(self): + names = [mb.name for mb in self.conn.load_balancer.members( + self.POOL_ID)] + self.assertIn(self.MEMBER_NAME, names) + + def test_member_update(self): + test_lb = self.conn.load_balancer.get_load_balancer(self.LB_ID) + + self.conn.load_balancer.update_member(self.MEMBER_ID, self.POOL_ID, + name=self.UPDATE_NAME) + self.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) + test_member = self.conn.load_balancer.get_member(self.MEMBER_ID, + self.POOL_ID) + self.assertEqual(self.UPDATE_NAME, test_member.name) + + self.conn.load_balancer.update_member(self.MEMBER_ID, self.POOL_ID, + name=self.MEMBER_NAME) + self.lb_wait_for_status(test_lb, status='ACTIVE', failures=['ERROR']) + test_member = self.conn.load_balancer.get_member(self.MEMBER_ID, + self.POOL_ID) + self.assertEqual(self.MEMBER_NAME, test_member.name) diff --git a/openstack/tests/unit/load_balancer/test_member.py b/openstack/tests/unit/load_balancer/test_member.py new file mode 100644 index 000000000..64446dfe9 --- /dev/null +++ b/openstack/tests/unit/load_balancer/test_member.py @@ -0,0 +1,62 @@ +# 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 testtools +import uuid + +from openstack.load_balancer.v2 import member + +IDENTIFIER = 'IDENTIFIER' +EXAMPLE = { + 'address': '192.0.2.16', + 'admin_state_up': True, + 'id': IDENTIFIER, + 'monitor_address': '192.0.2.17', + 'monitor_port': 9, + 'name': 'test_member', + 'pool_id': uuid.uuid4(), + 'project_id': uuid.uuid4(), + 'protocol_port': 5, + 'subnet_id': uuid.uuid4(), + 'weight': 7, +} + + +class TestPoolMember(testtools.TestCase): + + def test_basic(self): + test_member = member.Member() + self.assertEqual('member', test_member.resource_key) + self.assertEqual('members', test_member.resources_key) + self.assertEqual('/v2.0/lbaas/pools/%(pool_id)s/members', + test_member.base_path) + self.assertEqual('load-balancer', test_member.service.service_type) + self.assertTrue(test_member.allow_create) + self.assertTrue(test_member.allow_get) + self.assertTrue(test_member.allow_update) + self.assertTrue(test_member.allow_delete) + self.assertTrue(test_member.allow_list) + + def test_make_it(self): + test_member = member.Member(**EXAMPLE) + self.assertEqual(EXAMPLE['address'], test_member.address) + self.assertTrue(test_member.is_admin_state_up) + self.assertEqual(EXAMPLE['id'], test_member.id) + self.assertEqual(EXAMPLE['monitor_address'], + test_member.monitor_address) + self.assertEqual(EXAMPLE['monitor_port'], test_member.monitor_port) + self.assertEqual(EXAMPLE['name'], test_member.name) + self.assertEqual(EXAMPLE['pool_id'], test_member.pool_id) + self.assertEqual(EXAMPLE['project_id'], test_member.project_id) + self.assertEqual(EXAMPLE['protocol_port'], test_member.protocol_port) + self.assertEqual(EXAMPLE['subnet_id'], test_member.subnet_id) + self.assertEqual(EXAMPLE['weight'], test_member.weight) diff --git a/openstack/tests/unit/load_balancer/test_proxy.py b/openstack/tests/unit/load_balancer/test_proxy.py index 7471ea5df..5c4b1d554 100644 --- a/openstack/tests/unit/load_balancer/test_proxy.py +++ b/openstack/tests/unit/load_balancer/test_proxy.py @@ -10,14 +10,20 @@ # License for the specific language governing permissions and limitations # under the License. +import uuid + from openstack.load_balancer.v2 import _proxy from openstack.load_balancer.v2 import listener from openstack.load_balancer.v2 import load_balancer as lb +from openstack.load_balancer.v2 import member from openstack.load_balancer.v2 import pool from openstack.tests.unit import test_proxy_base2 class TestLoadBalancerProxy(test_proxy_base2.TestProxyBase): + + POOL_ID = uuid.uuid4() + def setUp(self): super(TestLoadBalancerProxy, self).setUp() self.proxy = _proxy.Proxy(self.session) @@ -96,3 +102,44 @@ class TestLoadBalancerProxy(test_proxy_base2.TestProxyBase): def test_pool_update(self): self.verify_update(self.proxy.update_pool, pool.Pool) + + def test_members(self): + self.verify_list(self.proxy.members, + member.Member, + paginated=True, + method_kwargs={'pool': self.POOL_ID}, + expected_kwargs={'pool_id': self.POOL_ID}) + + def test_member_get(self): + self.verify_get(self.proxy.get_member, + member.Member, + method_kwargs={'pool': self.POOL_ID}, + expected_kwargs={'pool_id': self.POOL_ID}) + + def test_member_create(self): + self.verify_create(self.proxy.create_member, + member.Member, + method_kwargs={'pool': self.POOL_ID}, + expected_kwargs={'pool_id': self.POOL_ID}) + + def test_member_delete(self): + self.verify_delete(self.proxy.delete_member, + member.Member, + True, + method_kwargs={'pool': self.POOL_ID}, + expected_kwargs={'pool_id': self.POOL_ID}) + + def test_member_find(self): + self._verify2('openstack.proxy2.BaseProxy._find', + self.proxy.find_member, + method_args=["MEMBER", self.POOL_ID], + expected_args=[member.Member, "MEMBER"], + expected_kwargs={"pool_id": self.POOL_ID, + "ignore_missing": True}) + + def test_member_update(self): + self._verify2('openstack.proxy2.BaseProxy._update', + self.proxy.update_member, + method_args=["MEMBER", self.POOL_ID], + expected_args=[member.Member, "MEMBER"], + expected_kwargs={"pool_id": self.POOL_ID})