diff --git a/octavia/api/v1/controllers/base.py b/octavia/api/v1/controllers/base.py index 4cbaa68b55..f08c109d8c 100644 --- a/octavia/api/v1/controllers/base.py +++ b/octavia/api/v1/controllers/base.py @@ -16,9 +16,6 @@ from oslo_config import cfg from pecan import rest from stevedore import driver as stevedore_driver -from octavia.api.v1.types import listener as listener_types -from octavia.api.v1.types import load_balancer as lb_types -from octavia.api.v1.types import pool as pool_types from octavia.db import repositories CONF = cfg.CONF @@ -35,7 +32,7 @@ class BaseController(rest.RestController): ).driver @staticmethod - def _convert_db_to_type(db_entity, to_type): + def _convert_db_to_type(db_entity, to_type, children=False): """Converts a data model into a Octavia WSME type :param db_entity: data model to convert @@ -45,18 +42,7 @@ class BaseController(rest.RestController): to_type = to_type[0] def _convert(db_obj): - api_type = to_type.from_data_model(db_obj) - if to_type == lb_types.LoadBalancerResponse: - api_type.vip = lb_types.VIP.from_data_model(db_obj.vip) - elif (to_type == pool_types.PoolResponse - and db_obj.session_persistence): - api_type.session_persistence = ( - pool_types.SessionPersistenceResponse.from_data_model( - db_obj.session_persistence)) - elif to_type == listener_types.ListenerResponse: - api_type.sni_containers = [sni_c.tls_container_id - for sni_c in db_obj.sni_containers] - return api_type + return to_type.from_data_model(db_obj, children=children) if isinstance(db_entity, list): converted = [_convert(db_obj) for db_obj in db_entity] else: diff --git a/octavia/api/v1/controllers/health_monitor.py b/octavia/api/v1/controllers/health_monitor.py index e70f55dd3b..4af8bfc5d4 100644 --- a/octavia/api/v1/controllers/health_monitor.py +++ b/octavia/api/v1/controllers/health_monitor.py @@ -24,6 +24,7 @@ from octavia.api.v1.types import health_monitor as hm_types from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions +from octavia.db import prepare as db_prepare from octavia.i18n import _LI @@ -66,8 +67,9 @@ class HealthMonitorController(base.BaseController): raise exceptions.DuplicateHealthMonitor() except exceptions.NotFound: pass - hm_dict = health_monitor.to_dict() - hm_dict['pool_id'] = self.pool_id + hm_dict = db_prepare.create_health_monitor( + health_monitor.to_dict(), self.pool_id) + # Verify load balancer is in a mutable status. If so it can be assumed # that the listener is also in a mutable status because a load balancer # will only be ACTIVE when all it's listeners as ACTIVE. diff --git a/octavia/api/v1/controllers/listener.py b/octavia/api/v1/controllers/listener.py index 7890f74473..9f9910b456 100644 --- a/octavia/api/v1/controllers/listener.py +++ b/octavia/api/v1/controllers/listener.py @@ -27,6 +27,7 @@ from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions from octavia.db import api as db_api +from octavia.db import prepare as db_prepare from octavia.i18n import _LI @@ -84,16 +85,15 @@ class ListenersController(base.BaseController): Update the load balancer db when provisioning status changes. """ try: - sni_container_ids = listener_dict.pop('sni_containers') + sni_containers = listener_dict.pop('sni_containers', []) db_listener = self.repositories.listener.create( context.session, **listener_dict) - if sni_container_ids is not None: - for container_id in sni_container_ids: - sni_dict = {'listener_id': db_listener.id, - 'tls_container_id': container_id} - self.repositories.sni.create(context.session, **sni_dict) - db_listener = self.repositories.listener.get(context.session, - id=db_listener.id) + if sni_containers: + for sni_container in sni_containers: + self.repositories.sni.create( + context.session, **sni_container) + db_listener = self.repositories.listener.get( + context.session, id=db_listener.id) except odb_exceptions.DBDuplicateEntry as de: # Setting LB back to active because this is just a validation # failure @@ -133,14 +133,8 @@ class ListenersController(base.BaseController): self._secure_data(listener) lb_repo = self.repositories.load_balancer self._test_lb_status_post(context, lb_repo) - listener_dict = listener.to_dict() - listener_dict['load_balancer_id'] = self.load_balancer_id - listener_dict['provisioning_status'] = constants.PENDING_CREATE - listener_dict['operating_status'] = constants.OFFLINE - # NOTE(blogan): Throwing away because we should not store secure data - # in the database nor should we send it to a handler. - if 'tls_termination' in listener_dict: - del listener_dict['tls_termination'] + listener_dict = db_prepare.create_listener( + listener.to_dict(), self.load_balancer_id) # This is the extra validation layer for wrong protocol or duplicate # listeners on the same load balancer. return self._validate_listeners(context, lb_repo, listener_dict) diff --git a/octavia/api/v1/controllers/load_balancer.py b/octavia/api/v1/controllers/load_balancer.py index d6e01f060b..3e62686e7b 100644 --- a/octavia/api/v1/controllers/load_balancer.py +++ b/octavia/api/v1/controllers/load_balancer.py @@ -26,6 +26,7 @@ from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions from octavia.db import api as db_api +from octavia.db import prepare as db_prepare from octavia.i18n import _LI @@ -65,19 +66,38 @@ class LoadBalancersController(base.BaseController): return self._convert_db_to_type(load_balancers, [lb_types.LoadBalancerResponse]) + def _create_load_balancer_tree(self, context, load_balancer): + prepped_lb = db_prepare.create_load_balancer_tree( + load_balancer.to_dict()) + try: + db_lb = self.repositories.create_load_balancer_tree( + context.session, prepped_lb) + except Exception: + # TODO(blogan): handle exceptions + raise + try: + LOG.info(_LI("Sending full load balancer configuration %s to " + "the handler"), db_lb.id) + self.handler.create(db_lb) + except Exception: + with excutils.save_and_reraise_exception(reraise=False): + self.repositories.load_balancer.update( + context.session, db_lb.id, + provisioning_status=constants.ERROR) + return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse, + children=True) + @wsme_pecan.wsexpose(lb_types.LoadBalancerResponse, body=lb_types.LoadBalancerPOST, status_code=202) def post(self, load_balancer): """Creates a load balancer.""" context = pecan.request.context.get('octavia_context') - lb_dict = load_balancer.to_dict() - vip_dict = lb_dict.pop('vip') - lb_dict['provisioning_status'] = constants.PENDING_CREATE - lb_dict['operating_status'] = constants.OFFLINE - lb_dict['project_id'] = lb_dict.get('project_id') or context.project_id + if load_balancer.listeners: + return self._create_load_balancer_tree(context, load_balancer) + lb_dict = db_prepare.create_load_balancer(load_balancer.to_dict()) try: db_lb = self.repositories.create_load_balancer_and_vip( - context.session, lb_dict, vip_dict) + context.session, lb_dict, lb_dict.pop('vip', {})) except odb_exceptions.DBDuplicateEntry: raise exceptions.IDAlreadyExists() # Handler will be responsible for sending to controller diff --git a/octavia/api/v1/controllers/member.py b/octavia/api/v1/controllers/member.py index 37c20d2196..bd0c38c084 100644 --- a/octavia/api/v1/controllers/member.py +++ b/octavia/api/v1/controllers/member.py @@ -25,6 +25,7 @@ from octavia.api.v1.types import member as member_types from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions +from octavia.db import prepare as db_prepare from octavia.i18n import _LI @@ -65,9 +66,8 @@ class MembersController(base.BaseController): def post(self, member): """Creates a pool member on a pool.""" context = pecan.request.context.get('octavia_context') - member_dict = member.to_dict() - member_dict['pool_id'] = self.pool_id - member_dict['operating_status'] = constants.OFFLINE + member_dict = db_prepare.create_member(member.to_dict(), self.pool_id) + # Verify load balancer is in a mutable status. If so it can be assumed # that the listener is also in a mutable status because a load balancer # will only be ACTIVE when all its listeners as ACTIVE. diff --git a/octavia/api/v1/controllers/pool.py b/octavia/api/v1/controllers/pool.py index 10f6e90ca3..1c5ead613b 100644 --- a/octavia/api/v1/controllers/pool.py +++ b/octavia/api/v1/controllers/pool.py @@ -27,6 +27,7 @@ from octavia.api.v1.types import pool as pool_types from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions +from octavia.db import prepare as db_prepare from octavia.i18n import _LI @@ -131,9 +132,8 @@ class PoolsController(base.BaseController): # will only be ACTIVE when all it's listeners as ACTIVE. self._test_lb_status(context.session) - pool_dict = pool.to_dict() + pool_dict = db_prepare.create_pool(pool.to_dict()) sp_dict = pool_dict.pop('session_persistence', None) - pool_dict['operating_status'] = constants.OFFLINE return self._validate_create_pool(context.session, sp_dict, pool_dict) diff --git a/octavia/api/v1/types/base.py b/octavia/api/v1/types/base.py index 29c8e843a4..8a8edfbae7 100644 --- a/octavia/api/v1/types/base.py +++ b/octavia/api/v1/types/base.py @@ -40,10 +40,11 @@ class IPAddressType(wtypes.UserType): class BaseType(wtypes.Base): @classmethod - def from_data_model(cls, data_model): + def from_data_model(cls, data_model, children=False): """Converts data_model to Octavia WSME type. :param data_model: data model to convert from + :param children: convert child data models """ return cls(**data_model.to_dict()) @@ -62,6 +63,9 @@ class BaseType(wtypes.Base): continue if value and isinstance(value, BaseType): value = value.to_dict() + if value and isinstance(value, list): + value = [val.to_dict() if isinstance(val, BaseType) else val + for val in value] if isinstance(value, wtypes.UnsetType): if render_unsets: value = None diff --git a/octavia/api/v1/types/listener.py b/octavia/api/v1/types/listener.py index 739af8873b..3ebd557e57 100644 --- a/octavia/api/v1/types/listener.py +++ b/octavia/api/v1/types/listener.py @@ -15,6 +15,7 @@ from wsme import types as wtypes from octavia.api.v1.types import base +from octavia.api.v1.types import pool class TLSTermination(base.BaseType): @@ -38,6 +39,27 @@ class ListenerResponse(base.BaseType): tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255)) sni_containers = [wtypes.StringType(max_length=255)] project_id = wtypes.wsattr(wtypes.UuidType()) + default_pool = wtypes.wsattr(pool.PoolResponse) + + @classmethod + def from_data_model(cls, data_model, children=False): + listener = super(ListenerResponse, cls).from_data_model( + data_model, children=children) + # NOTE(blogan): we should show sni_containers for every call to show + # a listener + listener.sni_containers = [sni_c.tls_container_id + for sni_c in data_model.sni_containers] + if not children: + # NOTE(blogan): do not show default_pool if the request does not + # want to see children + del listener.default_pool + return listener + if data_model.default_pool: + listener.default_pool = pool.PoolResponse.from_data_model( + data_model.default_pool, children=children) + if not listener.default_pool: + del listener.default_pool + return listener class ListenerPOST(base.BaseType): @@ -53,6 +75,7 @@ class ListenerPOST(base.BaseType): tls_termination = wtypes.wsattr(TLSTermination) sni_containers = [wtypes.StringType(max_length=255)] project_id = wtypes.wsattr(wtypes.UuidType()) + default_pool = wtypes.wsattr(pool.PoolPOST) class ListenerPUT(base.BaseType): diff --git a/octavia/api/v1/types/load_balancer.py b/octavia/api/v1/types/load_balancer.py index 6dc5b38aa9..4b2f005f83 100644 --- a/octavia/api/v1/types/load_balancer.py +++ b/octavia/api/v1/types/load_balancer.py @@ -15,6 +15,7 @@ from wsme import types as wtypes from octavia.api.v1.types import base +from octavia.api.v1.types import listener class VIP(base.BaseType): @@ -34,6 +35,26 @@ class LoadBalancerResponse(base.BaseType): enabled = wtypes.wsattr(bool) vip = wtypes.wsattr(VIP) project_id = wtypes.wsattr(wtypes.UuidType()) + listeners = wtypes.wsattr([listener.ListenerResponse]) + + @classmethod + def from_data_model(cls, data_model, children=False): + lb = super(LoadBalancerResponse, cls).from_data_model( + data_model, children=children) + # NOTE(blogan): VIP is technically a child but its the main piece of + # a load balancer so it makes sense to show it no matter what. + lb.vip = VIP.from_data_model(data_model.vip) + if not children: + # NOTE(blogan): don't show listeners if the request does not want + # to see children + del lb.listeners + return lb + lb.listeners = [ + listener.ListenerResponse.from_data_model( + listener_dm, children=children) + for listener_dm in data_model.listeners + ] + return lb class LoadBalancerPOST(base.BaseType): @@ -44,6 +65,7 @@ class LoadBalancerPOST(base.BaseType): enabled = wtypes.wsattr(bool, default=True) vip = wtypes.wsattr(VIP, mandatory=True) project_id = wtypes.wsattr(wtypes.UuidType()) + listeners = wtypes.wsattr([listener.ListenerPOST], default=[]) class LoadBalancerPUT(base.BaseType): diff --git a/octavia/api/v1/types/pool.py b/octavia/api/v1/types/pool.py index 4b5fe2bbc7..14f4b26619 100644 --- a/octavia/api/v1/types/pool.py +++ b/octavia/api/v1/types/pool.py @@ -15,6 +15,8 @@ from wsme import types as wtypes from octavia.api.v1.types import base +from octavia.api.v1.types import health_monitor +from octavia.api.v1.types import member class SessionPersistenceResponse(base.BaseType): @@ -46,6 +48,36 @@ class PoolResponse(base.BaseType): lb_algorithm = wtypes.wsattr(wtypes.text) session_persistence = wtypes.wsattr(SessionPersistenceResponse) project_id = wtypes.wsattr(wtypes.UuidType()) + health_monitor = wtypes.wsattr(health_monitor.HealthMonitorResponse) + members = wtypes.wsattr([member.MemberResponse]) + + @classmethod + def from_data_model(cls, data_model, children=False): + pool = super(PoolResponse, cls).from_data_model( + data_model, children=children) + # NOTE(blogan): we should show session persistence on every request + # to show a pool + if data_model.session_persistence: + pool.session_persistence = ( + SessionPersistenceResponse.from_data_model( + data_model.session_persistence)) + if not children: + # NOTE(blogan): do not show members or health_monitor if the + # request does not want to see children + del pool.members + del pool.health_monitor + return pool + pool.members = [ + member.MemberResponse.from_data_model(member_dm, children=children) + for member_dm in data_model.members + ] + if data_model.health_monitor: + pool.health_monitor = ( + health_monitor.HealthMonitorResponse.from_data_model( + data_model.health_monitor, children=children)) + if not pool.health_monitor: + del pool.health_monitor + return pool class PoolPOST(base.BaseType): @@ -58,6 +90,8 @@ class PoolPOST(base.BaseType): lb_algorithm = wtypes.wsattr(wtypes.text, mandatory=True) session_persistence = wtypes.wsattr(SessionPersistencePOST) project_id = wtypes.wsattr(wtypes.UuidType()) + health_monitor = wtypes.wsattr(health_monitor.HealthMonitorPOST) + members = wtypes.wsattr([member.MemberPOST]) class PoolPUT(base.BaseType): diff --git a/octavia/db/prepare.py b/octavia/db/prepare.py new file mode 100644 index 0000000000..a7812942ef --- /dev/null +++ b/octavia/db/prepare.py @@ -0,0 +1,91 @@ +# Copyright 2015 Rackspace +# +# 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 oslo_utils import uuidutils + +from octavia.common import constants + + +def create_load_balancer_tree(lb_dict): + listeners = lb_dict.pop('listeners', []) + prepped_lb = create_load_balancer(lb_dict) + prepped_lb['listeners'] = [] + for listener_dict in listeners: + pool = listener_dict.pop('default_pool', None) + prepped_listener = create_listener(listener_dict, prepped_lb.get('id')) + if pool: + hm = pool.pop('health_monitor', None) + members = pool.pop('members', []) or [] + prepped_pool = create_pool(pool) + pool_id = prepped_pool.get('id') + if hm: + prepped_hm = create_health_monitor(hm, pool_id) + prepped_pool['health_monitor'] = prepped_hm + prepped_pool['members'] = [] + for member_dict in members: + prepped_pool['members'].append( + create_member(member_dict, pool_id)) + prepped_listener['default_pool'] = prepped_pool + prepped_lb['listeners'].append(prepped_listener) + return prepped_lb + + +def create_load_balancer(lb_dict): + if not lb_dict.get('id'): + lb_dict['id'] = uuidutils.generate_uuid() + if lb_dict.get('vip'): + lb_dict['vip']['load_balancer_id'] = lb_dict.get('id') + lb_dict['provisioning_status'] = constants.PENDING_CREATE + lb_dict['operating_status'] = constants.OFFLINE + return lb_dict + + +def create_listener(listener_dict, lb_id): + if not listener_dict.get('id'): + listener_dict['id'] = uuidutils.generate_uuid() + listener_dict['load_balancer_id'] = lb_id + listener_dict['provisioning_status'] = constants.PENDING_CREATE + listener_dict['operating_status'] = constants.OFFLINE + # NOTE(blogan): Throwing away because we should not store secure data + # in the database nor should we send it to a handler. + if 'tls_termination' in listener_dict: + del listener_dict['tls_termination'] + sni_container_ids = listener_dict.pop('sni_containers', []) or [] + sni_containers = [{'listener_id': listener_dict.get('id'), + 'tls_container_id': sni_container_id} + for sni_container_id in sni_container_ids] + listener_dict['sni_containers'] = sni_containers + return listener_dict + + +def create_pool(pool_dict): + if not pool_dict.get('id'): + pool_dict['id'] = uuidutils.generate_uuid() + if pool_dict.get('session_persistence'): + pool_dict['session_persistence']['pool_id'] = pool_dict.get('id') + if 'members' in pool_dict and not pool_dict.get('members'): + del pool_dict['members'] + pool_dict['operating_status'] = constants.OFFLINE + return pool_dict + + +def create_member(member_dict, pool_id): + member_dict['pool_id'] = pool_id + member_dict['operating_status'] = constants.OFFLINE + return member_dict + + +def create_health_monitor(hm_dict, pool_id): + hm_dict['pool_id'] = pool_id + return hm_dict diff --git a/octavia/db/repositories.py b/octavia/db/repositories.py index 311e7bb27a..8eb123eac8 100644 --- a/octavia/db/repositories.py +++ b/octavia/db/repositories.py @@ -21,7 +21,6 @@ import datetime from oslo_config import cfg from oslo_log import log as logging -from oslo_utils import uuidutils from octavia.common import constants from octavia.common import exceptions @@ -134,6 +133,37 @@ class Repositories(object): self.amphorahealth = AmphoraHealthRepository() self.vrrpgroup = VRRPGroupRepository() + def _create_lb_and_vip_transaction(self, session, lb_dict, vip_dict): + with session.begin(subtransactions=True): + lb = models.LoadBalancer(**lb_dict) + session.add(lb) + vip_dict['load_balancer_id'] = lb_dict['id'] + vip = models.Vip(**vip_dict) + session.add(vip) + return lb + + def create_load_balancer_tree(self, session, lb_dict): + listener_dicts = lb_dict.pop('listeners', []) + vip_dict = lb_dict.pop('vip') + with session.begin(subtransactions=True): + lb_dm = self._create_lb_and_vip_transaction( + session, lb_dict, vip_dict) + for listener_dict in listener_dicts: + pool_dict = listener_dict.pop('default_pool', None) + if pool_dict: + hm_dict = pool_dict.pop('health_monitor', None) + member_dicts = pool_dict.pop('members', []) + pool_dm = self.pool.create(session, **pool_dict) + if hm_dict: + hm_dict['pool_id'] = pool_dm.id + self.health_monitor.create(session, **hm_dict) + for member_dict in member_dicts: + member_dict['pool_id'] = pool_dm.id + self.member.create(session, **member_dict) + listener_dict['default_pool_id'] = pool_dm.id + self.listener.create(session, **listener_dict) + return self.load_balancer.get(session, id=lb_dm.id) + def create_load_balancer_and_vip(self, session, lb_dict, vip_dict): """Inserts load balancer and vip entities into the database. @@ -145,14 +175,9 @@ class Repositories(object): :param vip_dict: Dictionary representation of a vip :returns: octava.common.data_models.LoadBalancer """ - with session.begin(): - if not lb_dict.get('id'): - lb_dict['id'] = uuidutils.generate_uuid() - lb = models.LoadBalancer(**lb_dict) - session.add(lb) - vip_dict['load_balancer_id'] = lb_dict['id'] - vip = models.Vip(**vip_dict) - session.add(vip) + with session.begin(subtransactions=True): + lb = self._create_lb_and_vip_transaction( + session, lb_dict, vip_dict) return self.load_balancer.get(session, id=lb.id) def create_pool_on_listener(self, session, listener_id, @@ -166,11 +191,8 @@ class Repositories(object): :returns: octavia.common.data_models.Pool """ with session.begin(subtransactions=True): - if not pool_dict.get('id'): - pool_dict['id'] = uuidutils.generate_uuid() db_pool = self.pool.create(session, **pool_dict) if sp_dict: - sp_dict['pool_id'] = pool_dict['id'] self.session_persistence.create(session, **sp_dict) self.listener.update(session, listener_id, default_pool_id=pool_dict['id']) diff --git a/octavia/tests/functional/api/v1/test_load_balancer.py b/octavia/tests/functional/api/v1/test_load_balancer.py index dc00a051b8..0cfb3f99cf 100644 --- a/octavia/tests/functional/api/v1/test_load_balancer.py +++ b/octavia/tests/functional/api/v1/test_load_balancer.py @@ -11,6 +11,9 @@ # 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 copy + from oslo_utils import uuidutils from octavia.common import constants @@ -225,3 +228,233 @@ class TestLoadBalancer(base.BaseAPITest): def test_delete_bad_lb_id(self): path = self.LB_PATH.format(lb_id='bad_uuid') self.delete(path, status=404) + + +class TestLoadBalancerGraph(base.BaseAPITest): + + def setUp(self): + super(TestLoadBalancerGraph, self).setUp() + self._project_id = uuidutils.generate_uuid() + + def _assert_graphs_equal(self, expected_graph, observed_graph): + observed_graph_copy = copy.deepcopy(observed_graph) + obs_lb_id = observed_graph_copy.pop('id') + self.assertTrue(uuidutils.is_uuid_like(obs_lb_id)) + expected_listeners = expected_graph.pop('listeners', []) + observed_listeners = observed_graph_copy.pop('listeners', []) + self.assertEqual(expected_graph, observed_graph_copy) + for observed_listener in observed_listeners: + self.assertTrue(uuidutils.is_uuid_like( + observed_listener.pop('id'))) + default_pool = observed_listener.get('default_pool') + if default_pool: + self.assertTrue(default_pool.get('id')) + default_pool.pop('id') + hm = default_pool.get('healthmonitor') + if hm: + self.assertTrue(hm.get('id')) + hm.pop('id') + for member in default_pool.get('members', []): + self.assertTrue(member.get('id')) + member.pop('id') + self.assertIn(observed_listener, expected_listeners) + + def _get_lb_bodies(self, create_listeners, expected_listeners): + create_lb = { + 'name': 'lb1', + 'project_id': self._project_id, + 'vip': {}, + 'listeners': create_listeners + } + expected_lb = { + 'description': None, + 'enabled': True, + 'provisioning_status': constants.PENDING_CREATE, + 'operating_status': constants.OFFLINE + } + expected_lb.update(create_lb) + expected_lb['listeners'] = expected_listeners + expected_lb['vip'] = {'ip_address': None, 'port_id': None, + 'subnet_id': None} + return create_lb, expected_lb + + def _get_listener_bodies(self, name='listener1', protocol_port=80, + create_default_pool=None, + expected_default_pool=None): + create_listener = { + 'name': name, + 'protocol_port': protocol_port, + 'protocol': constants.PROTOCOL_HTTP, + 'project_id': self._project_id + } + if create_default_pool: + create_listener['default_pool'] = create_default_pool + expected_listener = { + 'description': None, + 'tls_certificate_id': None, + 'sni_containers': [], + 'connection_limit': None, + 'enabled': True, + 'provisioning_status': constants.PENDING_CREATE, + 'operating_status': constants.OFFLINE + } + expected_listener.update(create_listener) + if expected_default_pool: + expected_listener['default_pool'] = expected_default_pool + return create_listener, expected_listener + + def _get_pool_bodies(self, name='pool1', create_members=None, + expected_members=None, create_hm=None, + expected_hm=None): + create_pool = { + 'name': name, + 'protocol': constants.PROTOCOL_HTTP, + 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, + 'project_id': self._project_id + } + if create_members: + create_pool['members'] = create_members + if create_hm: + create_pool['health_monitor'] = create_hm + expected_pool = { + 'description': None, + 'session_persistence': None, + 'members': [], + 'enabled': True, + 'operating_status': constants.OFFLINE + } + expected_pool.update(create_pool) + if expected_members: + expected_pool['members'] = expected_members + if expected_hm: + expected_pool['health_monitor'] = expected_hm + return create_pool, expected_pool + + def _get_member_bodies(self): + create_member = { + 'ip_address': '10.0.0.1', + 'protocol_port': 80, + 'project_id': self._project_id + } + expected_member = { + 'weight': 1, + 'enabled': True, + 'subnet_id': None, + 'operating_status': constants.OFFLINE + } + expected_member.update(create_member) + return create_member, expected_member + + def _get_hm_bodies(self): + create_hm = { + 'type': constants.HEALTH_MONITOR_PING, + 'delay': 1, + 'timeout': 1, + 'fall_threshold': 1, + 'rise_threshold': 1, + 'project_id': self._project_id + } + expected_hm = { + 'http_method': None, + 'url_path': None, + 'expected_codes': None, + 'enabled': True + } + expected_hm.update(create_hm) + return create_hm, expected_hm + + def test_with_one_listener(self): + create_listener, expected_listener = self._get_listener_bodies() + create_lb, expected_lb = self._get_lb_bodies([create_listener], + [expected_listener]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_many_listeners(self): + create_listener1, expected_listener1 = self._get_listener_bodies() + create_listener2, expected_listener2 = self._get_listener_bodies( + name='listener2', protocol_port=81 + ) + create_lb, expected_lb = self._get_lb_bodies( + [create_listener1, create_listener2], + [expected_listener1, expected_listener2]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_one_listener_one_pool(self): + create_pool, expected_pool = self._get_pool_bodies() + create_listener, expected_listener = self._get_listener_bodies( + create_default_pool=create_pool, + expected_default_pool=expected_pool + ) + create_lb, expected_lb = self._get_lb_bodies([create_listener], + [expected_listener]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_many_listeners_one_pool(self): + create_pool1, expected_pool1 = self._get_pool_bodies() + create_pool2, expected_pool2 = self._get_pool_bodies(name='pool2') + create_listener1, expected_listener1 = self._get_listener_bodies( + create_default_pool=create_pool1, + expected_default_pool=expected_pool1 + ) + create_listener2, expected_listener2 = self._get_listener_bodies( + create_default_pool=create_pool2, + expected_default_pool=expected_pool2, + name='listener2', protocol_port=81 + ) + create_lb, expected_lb = self._get_lb_bodies( + [create_listener1, create_listener2], + [expected_listener1, expected_listener2]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_one_listener_one_member(self): + create_member, expected_member = self._get_member_bodies() + create_pool, expected_pool = self._get_pool_bodies( + create_members=[create_member], + expected_members=[expected_member]) + create_listener, expected_listener = self._get_listener_bodies( + create_default_pool=create_pool, + expected_default_pool=expected_pool) + create_lb, expected_lb = self._get_lb_bodies([create_listener], + [expected_listener]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_one_listener_one_hm(self): + create_hm, expected_hm = self._get_hm_bodies() + create_pool, expected_pool = self._get_pool_bodies( + create_hm=create_hm, + expected_hm=expected_hm) + create_listener, expected_listener = self._get_listener_bodies( + create_default_pool=create_pool, + expected_default_pool=expected_pool) + create_lb, expected_lb = self._get_lb_bodies([create_listener], + [expected_listener]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) + + def test_with_one_of_everything(self): + create_member, expected_member = self._get_member_bodies() + create_hm, expected_hm = self._get_hm_bodies() + create_pool, expected_pool = self._get_pool_bodies( + create_members=[create_member], + expected_members=[expected_member], + create_hm=create_hm, + expected_hm=expected_hm) + create_listener, expected_listener = self._get_listener_bodies( + create_default_pool=create_pool, + expected_default_pool=expected_pool) + create_lb, expected_lb = self._get_lb_bodies([create_listener], + [expected_listener]) + response = self.post(self.LBS_PATH, create_lb) + api_lb = response.json + self._assert_graphs_equal(expected_lb, api_lb) diff --git a/octavia/tests/functional/db/test_repositories.py b/octavia/tests/functional/db/test_repositories.py index 30d16ede33..dd5b5e72c8 100644 --- a/octavia/tests/functional/db/test_repositories.py +++ b/octavia/tests/functional/db/test_repositories.py @@ -109,7 +109,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'operating_status': constants.OFFLINE, 'topology': constants.TOPOLOGY_ACTIVE_STANDBY, 'vrrp_group': None, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} vip = {'ip_address': '10.0.0.1', 'port_id': uuidutils.generate_uuid(), 'subnet_id': uuidutils.generate_uuid()} @@ -129,7 +130,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool) @@ -148,9 +150,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE, - 'cookie_name': 'cookie_monster'} + 'cookie_name': 'cookie_monster', + 'pool_id': pool['id']} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool, sp_dict=sp) @@ -176,7 +180,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool) update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'} @@ -196,9 +201,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE, - 'cookie_name': 'cookie_monster'} + 'cookie_name': 'cookie_monster', + 'pool_id': pool['id']} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool, sp_dict=sp) @@ -224,7 +231,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool) @@ -244,7 +252,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool) @@ -258,9 +267,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'description': 'desc1', 'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN, 'enabled': True, 'operating_status': constants.ONLINE, - 'project_id': uuidutils.generate_uuid()} + 'project_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid()} sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE, - 'cookie_name': 'cookie_monster'} + 'cookie_name': 'cookie_monster', + 'pool_id': pool['id']} pool_dm = self.repos.create_pool_on_listener(self.session, self.listener.id, pool, sp_dict=sp)