Implementing simple operator API

Setup executable to use wsgiref.simple_server
Simple setup of pecan
Defined controllers that define the API resources
Implemented all controllers to manipulate database and send to handler
Added repository methods required for creating multiple items in one transaction
Defined a few API exceptions based of wsme exceptions
Defined the wsme types that define the resource response and request bodies
Defined an abstract handler that all handlers should subclass
Defined a simple handler that is responsible for sending to controller
Added some wsme type tests

Implements: bp/operator-api

Change-Id: I0d91934db47a6e45f0c9ac22089f8689957bd239
This commit is contained in:
Brandon Logan 2014-09-03 16:26:23 -05:00
parent 9df9ff9137
commit 9786021205
62 changed files with 4967 additions and 162 deletions

0
bin/__init__.py Normal file
View File

39
bin/octavia-api.py Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env python
# Copyright 2014 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.
import sys
from wsgiref import simple_server
from oslo.config import cfg
from octavia.api import app as api_app
from octavia.common import service as octavia_service
from octavia.i18n import _LI
from octavia.openstack.common import log as logging
LOG = logging.getLogger(__name__)
if __name__ == '__main__':
octavia_service.prepare_service(sys.argv)
app = api_app.setup_app()
host, port = cfg.CONF.bind_host, cfg.CONF.bind_port
LOG.info(_LI("Starting API server on %(host)s:%(port)s") %
{"host": host, "port": port})
srv = simple_server.make_server(host, port, app)
srv.serve_forever()

View File

@ -3,6 +3,8 @@
# verbose = False
# Print debugging output (set logging level to DEBUG instead of default WARNING level).
# debug = False
# bind_host = 0.0.0.0
# bind_port = 9191
[database]
# This line MUST be changed to actually run the plugin.

0
octavia/api/__init__.py Normal file
View File

40
octavia/api/app.py Normal file
View File

@ -0,0 +1,40 @@
# Copyright 2014 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.
import pecan
from octavia.api import config as app_config
from octavia.api.v1 import hooks
def get_pecan_config():
"""Returns the pecan config."""
filename = app_config.__file__.replace('.pyc', '.py')
return pecan.configuration.conf_from_file(filename)
def setup_app(pecan_config=None, debug=False):
"""Creates and returns a pecan wsgi app."""
app_hooks = [hooks.ContextHook()]
if not pecan_config:
pecan_config = get_pecan_config()
pecan.configuration.set_config(dict(pecan_config), overwrite=True)
return pecan.make_app(
pecan_config.app.root,
debug=debug,
hooks=app_hooks,
wsme=pecan_config.wsme
)

28
octavia/api/config.py Normal file
View File

@ -0,0 +1,28 @@
# Copyright 2014 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.
# Pecan Application Configurations
# See https://pecan.readthedocs.org/en/latest/configuration.html#application-configuration # noqa
app = {
'root': 'octavia.api.root_controller.RootController',
'modules': ['octavia.api'],
'debug': False
}
# WSME Configurations
# See https://wsme.readthedocs.org/en/latest/integrate.html#configuration
wsme = {
# Keeping debug True for now so we can easily troubleshoot.
'debug': True
}

View File

@ -0,0 +1,32 @@
# Copyright 2014 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 pecan import rest
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1 import controllers
class RootController(rest.RestController):
"""The controller in which the pecan wsgi app should be created with."""
v1 = controllers.V1Controller()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
# TODO(blogan): once a decision is made on how to do versions, do that
# here
return {'versions': [{'status': 'CURRENT',
'updated': '2014-12-11T00:00:00Z',
'id': 'v1'}]}

View File

View File

@ -0,0 +1,29 @@
# Copyright 2014 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 wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
from octavia.api.v1.controllers import load_balancer
class V1Controller(base.BaseController):
loadbalancers = load_balancer.LoadBalancersController()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
# TODO(blogan): decide what exactly should be here, if anything
return "V1"

View File

@ -0,0 +1,52 @@
# Copyright 2014 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 pecan import rest
from octavia.api.v1.handlers.controller_simulator import handler
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
class BaseController(rest.RestController):
def __init__(self):
self.handler = handler.SimulatedControllerHandler()
self.repositories = repositories.Repositories()
def _convert_db_to_type(self, db_entity, to_type):
"""Converts a data model into a Octavia WSME type
:param db_entity: data model to convert
:param to_type: converts db_entity to this time
"""
if isinstance(to_type, list):
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))
return api_type
if isinstance(db_entity, list):
converted = [_convert(db_obj) for db_obj in db_entity]
else:
converted = _convert(db_entity)
return converted

View File

@ -0,0 +1,205 @@
# Copyright 2014 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.
import logging
from oslo.db import exception as odb_exceptions
from oslo.utils import excutils
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
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 api as db_api
from octavia.i18n import _LI
LOG = logging.getLogger(__name__)
class HealthMonitorController(base.BaseController):
def __init__(self, load_balancer_id, listener_id, pool_id):
super(HealthMonitorController, self).__init__()
self.load_balancer_id = load_balancer_id
self.listener_id = listener_id
self.pool_id = pool_id
self.handler = self.handler.health_monitor
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse)
def get_all(self):
"""Gets a single health monitor's details."""
# NOTE(blogan): since a pool can only have one health monitor
# we are using the get_all method to only get the single health monitor
session = db_api.get_session()
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
if not db_hm:
LOG.info(_LI("Health Monitor for Pool %s was not found") %
self.pool_id)
raise exceptions.NotFound(
resource=data_models.HealthMonitor._name(), id=id)
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse,
body=hm_types.HealthMonitorPOST, status_code=202)
def post(self, health_monitor):
"""Creates a health monitor on a pool."""
session = db_api.get_session()
try:
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
if db_hm:
raise exceptions.DuplicateHealthMonitor()
except exceptions.NotFound:
pass
hm_dict = health_monitor.to_dict()
hm_dict['pool_id'] = 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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Health Monitor for Pool %s cannot be updated "
"because the Load Balancer is immutable.") %
self.pool_id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
try:
db_hm = self.repositories.health_monitor.create(session, **hm_dict)
except odb_exceptions.DBError:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
raise exceptions.InvalidOption(value=hm_dict.get('type'),
option='type')
try:
LOG.info(_LI("Sending Creation of Health Monitor for Pool %s to "
"handler") % self.pool_id)
self.handler.create(db_hm)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse,
body=hm_types.HealthMonitorPUT, status_code=202)
def put(self, health_monitor):
"""Updates a health monitor.
Updates a health monitor on a pool if it exists. Only one health
monitor is allowed per pool so there is no need for a health monitor
id.
"""
session = db_api.get_session()
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
if not db_hm:
LOG.info(_LI("Health Monitor for Pool %s was not found") %
self.pool_id)
raise exceptions.NotFound(
resource=data_models.HealthMonitor._name(), id=id)
hm_dict = health_monitor.to_dict(render_unsets=False)
# 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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Health Monitor for Pool %s cannot be updated "
"because the Load Balancer is immutable.") %
self.pool_id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
try:
self.repositories.health_monitor.update(
session, self.pool_id, **hm_dict)
except odb_exceptions.DBError:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
raise exceptions.InvalidOption(value=hm_dict.get('type'),
option='type')
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
try:
LOG.info(_LI("Sending Update of Health Monitor for Pool %s to "
"handler") % self.pool_id)
self.handler.update(db_hm)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
@wsme_pecan.wsexpose(None, status_code=202)
def delete(self):
"""Deletes a health monitor."""
session = db_api.get_session()
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
if not db_hm:
LOG.info(_LI("Health Monitor for Pool %s cannot be updated "
"because the Load Balancer is immutable.") %
self.pool_id)
raise exceptions.NotFound(
resource=data_models.HealthMonitor._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
db_hm = self.repositories.health_monitor.get(session,
pool_id=self.pool_id)
try:
LOG.info(_LI("Sending Deletion of Health Monitor for Pool %s to "
"handler") % self.pool_id)
self.handler.delete(db_hm)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_hm = self.repositories.health_monitor.get(
session, pool_id=self.pool_id)
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)

View File

@ -0,0 +1,240 @@
# Copyright 2014 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.
import logging
from oslo.db import exception as odb_exceptions
from oslo.utils import excutils
import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
from octavia.api.v1.controllers import pool
from octavia.api.v1.types import listener as listener_types
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.i18n import _LI
LOG = logging.getLogger(__name__)
class ListenersController(base.BaseController):
def __init__(self, load_balancer_id):
super(ListenersController, self).__init__()
self.load_balancer_id = load_balancer_id
self.handler = self.handler.listener
def _secure_data(self, listener):
# TODO(blogan): Handle this data when certificate management code is
# available
listener.tls_termination = wtypes.Unset
@wsme_pecan.wsexpose(listener_types.ListenerResponse, wtypes.text)
def get_one(self, id):
"""Gets a single listener's details."""
session = db_api.get_session()
db_listener = self.repositories.listener.get(
session, load_balancer_id=self.load_balancer_id, id=id)
if not db_listener:
LOG.info(_LI("Listener %s not found.") % id)
raise exceptions.NotFound(
resource=data_models.Listener._name(), id=id)
return self._convert_db_to_type(db_listener,
listener_types.ListenerResponse)
@wsme_pecan.wsexpose([listener_types.ListenerResponse])
def get_all(self):
"""Lists all listeners on a load balancer."""
session = db_api.get_session()
db_listeners = self.repositories.listener.get_all(
session, load_balancer_id=self.load_balancer_id)
return self._convert_db_to_type(db_listeners,
[listener_types.ListenerResponse])
@wsme_pecan.wsexpose(listener_types.ListenerResponse,
body=listener_types.ListenerPOST, status_code=202)
def post(self, listener):
"""Creates a listener on a load balancer."""
self._secure_data(listener)
session = db_api.get_session()
lb_repo = self.repositories.load_balancer
if not lb_repo.test_and_set_provisioning_status(
session, self.load_balancer_id, constants.PENDING_UPDATE):
db_lb = lb_repo.get(session, id=self.load_balancer_id)
LOG.info(_LI("Load Balancer %s is immutable.") % db_lb.id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
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']
# This is the extra validation layer for wrong protocol or duplicate
# listeners on the same load balancer.
try:
db_listener = self.repositories.listener.create(
session, **listener_dict)
except odb_exceptions.DBDuplicateEntry:
# Setting LB back to active because this is just a validation
# failure
lb_repo.update(session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
raise exceptions.DuplicateListenerEntry(
port=listener_dict.get('protocol_port'))
except odb_exceptions.DBError:
# Setting LB back to active because this is just a validation
# failure
lb_repo.update(session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
raise exceptions.InvalidOption(value=listener_dict.get('protocol'),
option='protocol')
# Handler will be responsible for sending to controller
try:
LOG.info(_LI("Sending Creation of Listener %s to handler") %
db_listener.id)
self.handler.create(db_listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, db_listener.id,
provisioning_status=constants.ERROR)
db_listener = self.repositories.listener.get(
session, id=db_listener.id)
return self._convert_db_to_type(db_listener,
listener_types.ListenerResponse)
@wsme_pecan.wsexpose(listener_types.ListenerResponse, wtypes.text,
body=listener_types.ListenerPUT, status_code=202)
def put(self, id, listener):
"""Updates a listener on a load balancer."""
self._secure_data(listener)
session = db_api.get_session()
old_db_listener = self.repositories.listener.get(session, id=id)
if not old_db_listener:
LOG.info(_LI("Listener %s not found.") % id)
raise exceptions.NotFound(
resource=data_models.Listener._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, id, constants.PENDING_UPDATE,
constants.PENDING_UPDATE):
LOG.info(_LI("Load Balancer %s is immutable.") %
self.load_balancer_id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
listener_dict = listener.to_dict(render_unsets=False)
listener_dict['operating_status'] = old_db_listener.operating_status
# 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']
try:
self.repositories.listener.update(session, id, **listener_dict)
except odb_exceptions.DBDuplicateEntry:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, id, provisioning_status=constants.ACTIVE)
raise exceptions.DuplicateListenerEntry(
port=listener_dict.get('protocol_port'))
except odb_exceptions.DBError:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, id, provisioning_status=constants.ACTIVE)
raise exceptions.InvalidOption(value=listener_dict.get('protocol'),
option='protocol')
db_listener = self.repositories.listener.get(session, id=id)
try:
LOG.info(_LI("Sending Update of Listener %s to handler") %
db_listener.id)
self.handler.update(db_listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, db_listener.id,
provisioning_status=constants.ERROR)
db_lb = self.repositories.listener.get(session, id=db_listener.id)
return self._convert_db_to_type(db_lb, listener_types.ListenerResponse)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
def delete(self, id):
"""Deletes a listener from a load balancer."""
session = db_api.get_session()
db_listener = self.repositories.listener.get(session, id=id)
if not db_listener:
LOG.info(_LI("Listener %s not found.") % id)
raise exceptions.NotFound(
resource=data_models.Listener._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, id, constants.PENDING_UPDATE,
constants.PENDING_DELETE):
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
db_listener = self.repositories.listener.get(session, id=id)
try:
LOG.info(_LI("Sending Deletion of Listener %s to handler") %
db_listener.id)
self.handler.delete(db_listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, db_listener.id,
provisioning_status=constants.ERROR)
db_listener = self.repositories.listener.get(
session, id=db_listener.id)
return self._convert_db_to_type(db_listener,
listener_types.ListenerResponse)
@pecan.expose()
def _lookup(self, listener_id, *remainder):
"""Overriden pecan _lookup method for custom routing.
Verifies that the listener passed in the url exists, and if so decides
which controller, if any, should control be passed.
"""
session = db_api.get_session()
if listener_id and len(remainder) and remainder[0] == 'pools':
remainder = remainder[1:]
db_listener = self.repositories.listener.get(
session, id=listener_id)
if not db_listener:
LOG.info(_LI("Listener %s not found.") % listener_id)
raise exceptions.NotFound(
resource=data_models.Listener._name(), id=listener_id)
return pool.PoolsController(load_balancer_id=self.load_balancer_id,
listener_id=db_listener.id), remainder

View File

@ -0,0 +1,163 @@
# Copyright 2014 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 excutils
import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
from octavia.api.v1.controllers import listener
from octavia.api.v1.types import load_balancer as lb_types
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.i18n import _LI
from octavia.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class LoadBalancersController(base.BaseController):
def __init__(self):
super(LoadBalancersController, self).__init__()
self.handler = self.handler.load_balancer
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse, wtypes.text)
def get_one(self, id):
"""Gets a single load balancer's details."""
session = db_api.get_session()
load_balancer = self.repositories.load_balancer.get(
session, id=id)
if not load_balancer:
LOG.info(_LI("Load Balancer %s was not found.") % id)
raise exceptions.NotFound(
resource=data_models.LoadBalancer._name(), id=id)
return self._convert_db_to_type(load_balancer,
lb_types.LoadBalancerResponse)
@wsme_pecan.wsexpose([lb_types.LoadBalancerResponse], wtypes.text)
def get_all(self, tenant_id=None):
"""Lists all listeners on a load balancer."""
# tenant_id is an optional query parameter
session = db_api.get_session()
load_balancers = self.repositories.load_balancer.get_all(
session, tenant_id=tenant_id)
return self._convert_db_to_type(load_balancers,
[lb_types.LoadBalancerResponse])
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse,
body=lb_types.LoadBalancerPOST, status_code=202)
def post(self, load_balancer):
"""Creates a load balancer."""
session = db_api.get_session()
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
db_lb = self.repositories.create_load_balancer_and_vip(
session, lb_dict, vip_dict)
# Handler will be responsible for sending to controller
try:
LOG.info(_LI("Sending created Load Balancer %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(
session, db_lb.id, provisioning_status=constants.ERROR)
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse,
wtypes.text, status_code=202,
body=lb_types.LoadBalancerPUT)
def put(self, id, load_balancer):
"""Updates a load balancer."""
session = db_api.get_session()
# Purely to make lines smaller length
lb_repo = self.repositories.load_balancer
old_db_lb = self.repositories.load_balancer.get(session, id=id)
if not old_db_lb:
LOG.info(_LI("Load Balancer %s was not found.") % id)
raise exceptions.NotFound(
resource=data_models.LoadBalancer._name(), id=id)
# Check load balancer is in a mutable status
if not lb_repo.test_and_set_provisioning_status(
session, id, constants.PENDING_UPDATE):
LOG.info(_LI("Load Balancer %s is immutable.") % id)
raise exceptions.ImmutableObject(resource=old_db_lb._name(),
id=id)
lb_dict = load_balancer.to_dict(render_unsets=False)
lb_dict['operating_status'] = old_db_lb.operating_status
self.repositories.load_balancer.update(
session, id, **lb_dict)
db_lb = self.repositories.load_balancer.get(session, id=id)
try:
LOG.info(_LI("Sending updated Load Balancer %s to the handler") %
db_lb.id)
self.handler.update(db_lb)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
session, db_lb.id, provisioning_status=constants.ERROR)
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
def delete(self, id):
"""Deletes a load balancer."""
session = db_api.get_session()
# Purely to make lines smaller length
lb_repo = self.repositories.load_balancer
db_lb = self.repositories.load_balancer.get(session, id=id)
if not db_lb:
LOG.info(_LI("Load Balancer %s was not found.") % id)
raise exceptions.NotFound(
resource=data_models.LoadBalancer._name(), id=id)
# Check load balancer is in a mutable status
if not lb_repo.test_and_set_provisioning_status(
session, id, constants.PENDING_DELETE):
LOG.info(_LI("Load Balancer %s is immutable.") % id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=id)
db_lb = self.repositories.load_balancer.get(session, id=id)
try:
LOG.info(_LI("Sending deleted Load Balancer %s to the handler") %
db_lb.id)
self.handler.delete(db_lb)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.load_balancer.update(
session, db_lb.id, provisioning_status=constants.ERROR)
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
@pecan.expose()
def _lookup(self, lb_id, *remainder):
"""Overriden pecan _lookup method for custom routing.
Verifies that the load balancer passed in the url exists, and if so
decides which controller, if any, should control be passed.
"""
session = db_api.get_session()
if lb_id and len(remainder) and remainder[0] == 'listeners':
remainder = remainder[1:]
db_lb = self.repositories.load_balancer.get(session, id=lb_id)
if not db_lb:
LOG.info(_LI("Load Balancer %s was not found.") % lb_id)
raise exceptions.NotFound(
resource=data_models.LoadBalancer._name(), id=lb_id)
return listener.ListenersController(
load_balancer_id=db_lb.id), remainder

View File

@ -0,0 +1,193 @@
# Copyright 2014 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.
import logging
import oslo.db.exception as oslo_exc
from oslo.utils import excutils
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
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 api as db_api
from octavia.i18n import _LI
LOG = logging.getLogger(__name__)
class MembersController(base.BaseController):
def __init__(self, load_balancer_id, listener_id, pool_id):
super(MembersController, self).__init__()
self.load_balancer_id = load_balancer_id
self.listener_id = listener_id
self.pool_id = pool_id
self.handler = self.handler.member
@wsme_pecan.wsexpose(member_types.MemberResponse, wtypes.text)
def get(self, id):
"""Gets a single pool member's details."""
session = db_api.get_session()
db_member = self.repositories.member.get(session, id=id)
if not db_member:
LOG.info(_LI("Member %s not found") % id)
raise exceptions.NotFound(
resource=data_models.Member._name(), id=id)
return self._convert_db_to_type(db_member, member_types.MemberResponse)
@wsme_pecan.wsexpose([member_types.MemberResponse])
def get_all(self):
"""Lists all pool members of a pool."""
session = db_api.get_session()
db_members = self.repositories.member.get_all(
session, pool_id=self.pool_id)
return self._convert_db_to_type(db_members,
[member_types.MemberResponse])
@wsme_pecan.wsexpose(member_types.MemberResponse,
body=member_types.MemberPOST, status_code=202)
def post(self, member):
"""Creates a pool member on a pool."""
session = db_api.get_session()
member_dict = member.to_dict()
member_dict['pool_id'] = self.pool_id
member_dict['operating_status'] = constants.OFFLINE
# 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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Member cannot be created because it's Load "
"Balancer is in an immutable state."))
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
try:
db_member = self.repositories.member.create(session, **member_dict)
except oslo_exc.DBDuplicateEntry:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
raise exceptions.DuplicateMemberEntry(
ip_address=member_dict.get('ip_address'),
port=member_dict.get('protocol_port'))
try:
LOG.info(_LI("Sending Creation of Member %s to handler") %
db_member.id)
self.handler.create(db_member)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_member = self.repositories.member.get(session, id=db_member.id)
return self._convert_db_to_type(db_member, member_types.MemberResponse)
@wsme_pecan.wsexpose(member_types.MemberResponse,
wtypes.text, body=member_types.MemberPUT,
status_code=202)
def put(self, id, member):
"""Updates a pool member."""
session = db_api.get_session()
old_db_member = self.repositories.member.get(session, id=id)
if not old_db_member:
LOG.info(_LI("Member %s cannot be updated because it's Load "
"Balancer is in an immutable state.") % id)
LOG.info(_LI("Member %s not found") % id)
raise exceptions.NotFound(
resource=data_models.Member._name(), id=id)
member_dict = member.to_dict(render_unsets=False)
member_dict['operating_status'] = old_db_member.operating_status
# 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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
try:
self.repositories.member.update(session, id, **member_dict)
except oslo_exc.DBDuplicateEntry:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
raise exceptions.DuplicateMemberEntry(
ip_address=member_dict.get('ip_address'),
port=member_dict.get('protocol_port'))
db_member = self.repositories.member.get(session, id=id)
try:
LOG.info(_LI("Sending Update of Member %s to handler") %
db_member.id)
self.handler.update(db_member)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_member = self.repositories.member.get(session, id=db_member.id)
return self._convert_db_to_type(db_member, member_types.MemberResponse)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
def delete(self, id):
"""Deletes a pool member."""
session = db_api.get_session()
db_member = self.repositories.member.get(session, id=id)
if not db_member:
LOG.info(_LI("Member %s not found") % id)
raise exceptions.NotFound(
resource=data_models.Member._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Member %s cannot be deleted because it's Load "
"Balancer is in an immutable state.") % id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
db_member = self.repositories.member.get(session, id=id)
try:
LOG.info(_LI("Sending Deletion of Member %s to handler") %
db_member.id)
self.handler.delete(db_member)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_member = self.repositories.member.get(session, id=id)
return self._convert_db_to_type(db_member, member_types.MemberResponse)

View File

@ -0,0 +1,241 @@
# Copyright 2014 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.
import logging
from oslo.db import exception as odb_exceptions
from oslo.utils import excutils
import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base
from octavia.api.v1.controllers import health_monitor
from octavia.api.v1.controllers import member
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 api as db_api
from octavia.i18n import _LI
LOG = logging.getLogger(__name__)
class PoolsController(base.BaseController):
def __init__(self, load_balancer_id, listener_id):
super(PoolsController, self).__init__()
self.load_balancer_id = load_balancer_id
self.listener_id = listener_id
self.handler = self.handler.pool
@wsme_pecan.wsexpose(pool_types.PoolResponse, wtypes.text)
def get(self, id):
"""Gets a pool's details."""
session = db_api.get_session()
db_pool = self.repositories.pool.get(session, id=id)
if not db_pool:
LOG.info(_LI("Pool %s not found.") % id)
raise exceptions.NotFound(resource=data_models.Pool._name(), id=id)
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
@wsme_pecan.wsexpose([pool_types.PoolResponse])
def get_all(self):
"""Lists all pools on a listener."""
session = db_api.get_session()
default_pool = self.repositories.listener.get(
session, id=self.listener_id).default_pool
if default_pool:
default_pool = [default_pool]
else:
default_pool = []
return self._convert_db_to_type(default_pool,
[pool_types.PoolResponse])
@wsme_pecan.wsexpose(pool_types.PoolResponse, body=pool_types.PoolPOST,
status_code=202)
def post(self, pool):
"""Creates a pool on a listener.
This does not allow more than one pool to be on a listener so once one
is created, another cannot be created until the first one has been
deleted.
"""
session = db_api.get_session()
if self.repositories.listener.has_pool(session, self.listener_id):
raise exceptions.DuplicatePoolEntry()
# 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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Pool cannot be created because the Load "
"Balancer is in an immutable state"))
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
pool_dict = pool.to_dict()
sp_dict = pool_dict.pop('session_persistence', None)
pool_dict['operating_status'] = constants.OFFLINE
try:
db_pool = self.repositories.create_pool_on_listener(
session, self.listener_id, pool_dict, sp_dict=sp_dict)
except odb_exceptions.DBError:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
# TODO(blogan): will have to do separate validation protocol
# before creation or update since the exception messages
# do not give any information as to what constraint failed
raise exceptions.InvalidOption(value='', option='')
try:
LOG.info(_LI("Sending Creation of Pool %s to handler") %
db_pool.id)
self.handler.create(db_pool)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_pool = self.repositories.pool.get(session, id=db_pool.id)
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
@wsme_pecan.wsexpose(pool_types.PoolResponse, wtypes.text,
body=pool_types.PoolPUT, status_code=202)
def put(self, id, pool):
"""Updates a pool on a listener."""
session = db_api.get_session()
old_db_pool = self.repositories.pool.get(session, id=id)
if not old_db_pool:
LOG.info(_LI("Pool %s not found.") % id)
raise exceptions.NotFound(resource=data_models.Pool._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Pool %s cannot be updated because the Load "
"Balancer is in an immutable state") % id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
pool_dict = pool.to_dict(render_unsets=False)
pool_dict['operating_status'] = old_db_pool.operating_status
sp_dict = pool_dict.pop('session_persistence', None)
try:
self.repositories.update_pool_on_listener(session, id, pool_dict,
sp_dict)
except odb_exceptions.DBError:
# Setting LB and Listener back to active because this is just a
# validation failure
self.repositories.load_balancer.update(
session, self.load_balancer_id,
provisioning_status=constants.ACTIVE)
self.repositories.listener.update(
session, self.listener_id,
provisioning_status=constants.ACTIVE)
# TODO(blogan): will have to do separate validation protocol
# before creation or update since the exception messages
# do not give any information as to what constraint failed
raise exceptions.InvalidOption(value='', option='')
db_pool = self.repositories.pool.get(session, id=id)
try:
LOG.info(_LI("Sending Update of Pool %s to handler") %
db_pool.id)
self.handler.update(db_pool)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
db_pool = self.repositories.pool.get(session, id=db_pool.id)
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
def delete(self, id):
"""Deletes a pool from a listener."""
session = db_api.get_session()
db_pool = self.repositories.pool.get(session, id=id)
if not db_pool:
LOG.info(_LI("Pool %s not found.") % id)
raise exceptions.NotFound(resource=data_models.Pool._name(), id=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.
if not self.repositories.test_and_set_lb_and_listener_prov_status(
session, self.load_balancer_id, self.listener_id,
constants.PENDING_UPDATE, constants.PENDING_UPDATE):
LOG.info(_LI("Pool %s cannot be deleted because the Load "
"Balancer is in an immutable state") % id)
lb_repo = self.repositories.load_balancer
db_lb = lb_repo.get(session, id=self.load_balancer_id)
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=self.load_balancer_id)
db_pool = self.repositories.pool.get(session, id=id)
try:
LOG.info(_LI("Sending Deletion of Pool %s to handler") %
db_pool.id)
self.handler.delete(db_pool)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
self.repositories.listener.update(
session, self.listener_id,
operating_status=constants.ERROR)
self.repositories.pool.update(
session, db_pool.id,
operating_status=constants.ERROR)
db_pool = self.repositories.pool.get(session, id=db_pool.id)
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
@pecan.expose()
def _lookup(self, pool_id, *remainder):
"""Overriden pecan _lookup method for custom routing.
Verifies that the pool passed in the url exists, and if so decides
which controller, if any, should control be passed.
"""
session = db_api.get_session()
if pool_id and len(remainder) and remainder[0] == 'members':
remainder = remainder[1:]
db_pool = self.repositories.pool.get(session, id=pool_id)
if not db_pool:
LOG.info(_LI("Pool %s not found.") % pool_id)
raise exceptions.NotFound(resource=data_models.Pool._name(),
id=pool_id)
return member.MembersController(
load_balancer_id=self.load_balancer_id,
listener_id=self.listener_id,
pool_id=db_pool.id), remainder
if pool_id and len(remainder) and remainder[0] == 'healthmonitor':
remainder = remainder[1:]
db_pool = self.repositories.pool.get(session, id=pool_id)
if not db_pool:
LOG.info(_LI("Pool %s not found.") % pool_id)
raise exceptions.NotFound(resource=data_models.Pool._name(),
id=pool_id)
return health_monitor.HealthMonitorController(
load_balancer_id=self.load_balancer_id,
listener_id=self.listener_id,
pool_id=db_pool.id), remainder

View File

View File

@ -0,0 +1,62 @@
# Copyright 2014 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.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class BaseObjectHandler(object):
"""Base class for any object handler."""
@abc.abstractmethod
def create(self, data_model):
"""Begins process of actually creating data_model."""
pass
@abc.abstractmethod
def update(self, data_model):
"""Begins process of actually updating data_model."""
pass
@abc.abstractmethod
def delete(self, data_model):
"""Begins process of actually deleting data_model."""
pass
class NotImplementedObjectHandler(BaseObjectHandler):
"""Default Object Handler to force implementation of subclasses.
Helper class to make any subclass of AbstractHandler explode if it
is missing any of the required object managers.
"""
def update(self, data_model):
raise NotImplementedError()
def delete(self, data_model):
raise NotImplementedError()
def create(self, data_model):
raise NotImplementedError()
@six.add_metaclass(abc.ABCMeta)
class BaseHandler(object):
"""Base class for all handlers."""
load_balancer = NotImplementedObjectHandler()
listener = NotImplementedObjectHandler()
pool = NotImplementedObjectHandler()
health_monitor = NotImplementedObjectHandler()
member = NotImplementedObjectHandler()

View File

@ -0,0 +1,249 @@
# Copyright 2014 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.
"""
This is just a handler that will simulate successful operations a controller
should perform. There is nothing useful about this other than database
entity status management.
"""
import threading
import time
from octavia.api.v1.handlers import abstract_handler
from octavia.common import constants
from octavia.common import data_models
from octavia.db import api as db_api
import octavia.db.repositories as repos
from octavia.i18n import _LI
from octavia.openstack.common import log as logging
LOG = logging.getLogger(__name__)
ASYNC_TIME = 1
def validate_input(expected, actual):
if not isinstance(actual, expected):
raise InvalidHandlerInputObject(obj_type=actual.__class__)
def simulate_controller(data_model, delete=False):
"""Simulates a successful controller operator for a data model.
:param data_model: data model to simulate controller operation
:param delete: deletes from the database
"""
repo = repos.Repositories()
def controller(model, delete):
def session():
return db_api.get_session()
time.sleep(ASYNC_TIME)
LOG.info(_LI("Simulating controller operation for %(entity)s...") %
{"entity": model.__class__.__name__})
if isinstance(model, data_models.Member):
if delete:
repo.member.delete(session(), id=model.id)
else:
repo.member.update(session(), model.id,
operating_status=constants.ONLINE)
repo.listener.update(session(), model.pool.listener.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
repo.load_balancer.update(session(),
model.pool.listener.load_balancer.id,
provisioning_status=constants.ACTIVE)
elif isinstance(model, data_models.Pool):
if delete:
repo.pool.delete(session(), id=model.id)
else:
repo.pool.update(session(), model.id,
operating_status=constants.ONLINE)
repo.listener.update(session(), model.listener.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
repo.load_balancer.update(session(),
model.listener.load_balancer.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
elif isinstance(model, data_models.Listener):
if delete:
repo.listener.update(session(), model.id,
operating_status=constants.OFFLINE,
provisioning_status=constants.DELETED)
else:
repo.listener.update(session(), model.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
repo.load_balancer.update(session(),
model.load_balancer.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
elif isinstance(model, data_models.LoadBalancer):
if delete:
repo.load_balancer.update(
session(), id=model.id, operating_status=constants.OFFLINE,
provisioning_status=constants.DELETED)
else:
repo.load_balancer.update(session(), id=model.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
else:
if delete:
repo.health_monitor.delete(session(), pool_id=model.pool.id)
repo.listener.update(session(), model.pool.listener.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
repo.load_balancer.update(session(),
model.pool.listener.load_balancer.id,
operating_status=constants.ONLINE,
provisioning_status=constants.ACTIVE)
LOG.info(_LI("Simulated Controller Handler Thread Complete"))
thread = threading.Thread(target=controller, args=(data_model, delete))
thread.start()
class InvalidHandlerInputObject(Exception):
message = "Invalid Input Object %(obj_type)"
def __init__(self, **kwargs):
message = self.message % kwargs
super(InvalidHandlerInputObject, self).__init__(message=message)
class LoadBalancerHandler(abstract_handler.BaseObjectHandler):
def create(self, load_balancer):
validate_input(data_models.LoadBalancer, load_balancer)
LOG.info(_LI("%(entity)s handling the creation of "
"load balancer %(id)s") %
{"entity": self.__class__.__name__, "id": load_balancer.id})
simulate_controller(load_balancer)
def update(self, load_balancer):
validate_input(data_models.LoadBalancer, load_balancer)
LOG.info(_LI("%(entity)s handling the update of "
"load balancer %(id)s") %
{"entity": self.__class__.__name__, "id": load_balancer.id})
simulate_controller(load_balancer)
def delete(self, load_balancer):
validate_input(data_models.LoadBalancer, load_balancer)
LOG.info(_LI("%(entity)s handling the deletion of "
"load balancer %(id)s") %
{"entity": self.__class__.__name__, "id": load_balancer.id})
simulate_controller(load_balancer, delete=True)
class ListenerHandler(abstract_handler.BaseObjectHandler):
def create(self, listener):
validate_input(data_models.Listener, listener)
LOG.info(_LI("%(entity)s handling the creation of listener %(id)s") %
{"entity": self.__class__.__name__, "id": listener.id})
simulate_controller(listener)
def update(self, listener):
validate_input(data_models.Listener, listener)
LOG.info(_LI("%(entity)s handling the update of listener %(id)s") %
{"entity": self.__class__.__name__, "id": listener.id})
simulate_controller(listener)
def delete(self, listener):
validate_input(data_models.Listener, listener)
LOG.info(_LI("%(entity)s handling the deletion of listener %(id)s") %
{"entity": self.__class__.__name__, "id": listener.id})
simulate_controller(listener, delete=True)
class PoolHandler(abstract_handler.BaseObjectHandler):
def create(self, pool):
validate_input(data_models.Pool, pool)
LOG.info(_LI("%(entity)s handling the creation of pool %(id)s") %
{"entity": self.__class__.__name__, "id": pool.id})
simulate_controller(pool)
def update(self, pool):
validate_input(data_models.Pool, pool)
LOG.info(_LI("%(entity)s handling the update of pool %(id)s") %
{"entity": self.__class__.__name__, "id": pool.id})
simulate_controller(pool)
def delete(self, pool):
validate_input(data_models.Pool, pool)
LOG.info(_LI("%(entity)s handling the deletion of pool %(id)s") %
{"entity": self.__class__.__name__, "id": pool.id})
simulate_controller(pool, delete=True)
class HealthMonitorHandler(abstract_handler.BaseObjectHandler):
def create(self, health_monitor):
validate_input(data_models.HealthMonitor, health_monitor)
LOG.info(_LI("%(entity)s handling the creation of health monitor "
"on pool %(id)s") %
{"entity": self.__class__.__name__,
"id": health_monitor.pool_id})
simulate_controller(health_monitor)
def update(self, health_monitor):
validate_input(data_models.HealthMonitor, health_monitor)
LOG.info(_LI("%(entity)s handling the update of health monitor "
"on pool %(id)s") %
{"entity": self.__class__.__name__,
"id": health_monitor.pool_id})
simulate_controller(health_monitor)
def delete(self, health_monitor):
validate_input(data_models.HealthMonitor, health_monitor)
LOG.info(_LI("%(entity)s handling the deletion of health monitor "
"on pool %(id)s") %
{"entity": self.__class__.__name__,
"id": health_monitor.pool_id})
simulate_controller(health_monitor, delete=True)
class MemberHandler(abstract_handler.BaseObjectHandler):
def create(self, member):
validate_input(data_models.Member, member)
LOG.info(_LI("%(entity)s handling the creation of member %(id)s") %
{"entity": self.__class__.__name__, "id": member.id})
simulate_controller(member)
def update(self, member):
validate_input(data_models.Member, member)
LOG.info(_LI("%(entity)s handling the update of member %(id)s") %
{"entity": self.__class__.__name__, "id": member.id})
simulate_controller(member)
def delete(self, member):
validate_input(data_models.Member, member)
LOG.info(_LI("%(entity)s handling the deletion of member %(id)s") %
{"entity": self.__class__.__name__, "id": member.id})
simulate_controller(member, delete=True)
class SimulatedControllerHandler(abstract_handler.BaseHandler):
"""Handler that simulates database calls of a successful controller."""
load_balancer = LoadBalancerHandler()
listener = ListenerHandler()
pool = PoolHandler()
member = MemberHandler()
health_monitor = HealthMonitorHandler()

30
octavia/api/v1/hooks.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright 2014 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 pecan import hooks
from octavia.common import context
class ContextHook(hooks.PecanHook):
def on_route(self, state):
user_id = state.request.headers.get('X-User-Id')
user_id = state.request.headers.get('X-User', user_id)
tenant = state.request.headers.get('X-Tenant-Id')
tenant = state.request.headers.get('X-Tenant', tenant)
auth_token = state.request.headers.get('X-Auth-Token')
state.request.context = context.Context(user_id=user_id,
tenant_id=tenant,
auth_token=auth_token)

View File

View File

@ -0,0 +1,67 @@
# Copyright 2014 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 wsme import types as wtypes
class IPAddressType(wtypes.UserType):
basetype = str
name = 'ipaddress'
@staticmethod
def validate(value):
"""Validates whether value is an IPv4 or IPv6 address."""
try:
wtypes.IPv4AddressType.validate(value)
return value
except ValueError:
try:
wtypes.IPv6AddressType.validate(value)
return value
except ValueError:
error = 'Value should be IPv4 or IPv6 format'
raise ValueError(error)
class BaseType(wtypes.Base):
@classmethod
def from_data_model(cls, data_model):
"""Converts data_model to Octavia WSME type.
:param data_model: data model to convert from
"""
return cls(**data_model.to_dict())
def to_dict(self, render_unsets=True):
"""Converts Octavia WSME type to dictionary.
:param render_unsets: If True, will convert items that are WSME Unset
types to None. If False, does not add the item
"""
ret_dict = {}
for attr in dir(self):
if attr.startswith('_'):
continue
value = getattr(self, attr, None)
if value and callable(value):
continue
if value and isinstance(value, BaseType):
value = value.to_dict()
if isinstance(value, wtypes.UnsetType):
if render_unsets:
value = None
else:
continue
ret_dict[attr] = value
return ret_dict

View File

@ -0,0 +1,56 @@
# Copyright 2014 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 wsme import types as wtypes
from octavia.api.v1.types import base
class HealthMonitorResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
type = wtypes.wsattr(wtypes.text)
delay = wtypes.wsattr(wtypes.IntegerType())
timeout = wtypes.wsattr(wtypes.IntegerType())
fall_threshold = wtypes.wsattr(wtypes.IntegerType())
rise_threshold = wtypes.wsattr(wtypes.IntegerType())
http_method = wtypes.wsattr(wtypes.text)
url_path = wtypes.wsattr(wtypes.text)
expected_codes = wtypes.wsattr(wtypes.text)
enabled = wtypes.wsattr(bool)
class HealthMonitorPOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
type = wtypes.wsattr(wtypes.text, mandatory=True)
delay = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
timeout = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
fall_threshold = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
rise_threshold = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
http_method = wtypes.wsattr(wtypes.text)
url_path = wtypes.wsattr(wtypes.text)
expected_codes = wtypes.wsattr(wtypes.text)
enabled = wtypes.wsattr(bool, default=True)
class HealthMonitorPUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
type = wtypes.wsattr(wtypes.text)
delay = wtypes.wsattr(wtypes.IntegerType())
timeout = wtypes.wsattr(wtypes.IntegerType())
fall_threshold = wtypes.wsattr(wtypes.IntegerType())
rise_threshold = wtypes.wsattr(wtypes.IntegerType())
http_method = wtypes.wsattr(wtypes.text)
url_path = wtypes.wsattr(wtypes.text)
expected_codes = wtypes.wsattr(wtypes.text)
enabled = wtypes.wsattr(bool)

View File

@ -0,0 +1,62 @@
# Copyright 2014 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 wsme import types as wtypes
from octavia.api.v1.types import base
class TLSTermination(base.BaseType):
certificate = wtypes.wsattr(wtypes.StringType())
intermediate_certificate = wtypes.wsattr(wtypes.StringType())
private_key = wtypes.wsattr(wtypes.StringType())
passphrase = wtypes.wsattr(wtypes.StringType())
class ListenerResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
id = wtypes.wsattr(wtypes.UuidType())
name = wtypes.wsattr(wtypes.StringType())
description = wtypes.wsattr(wtypes.StringType())
provisioning_status = wtypes.wsattr(wtypes.StringType())
operating_status = wtypes.wsattr(wtypes.StringType())
enabled = wtypes.wsattr(bool)
protocol = wtypes.wsattr(wtypes.text)
protocol_port = wtypes.wsattr(wtypes.IntegerType())
connection_limit = wtypes.wsattr(wtypes.IntegerType())
tls_certificate_id = wtypes.wsattr(wtypes.UuidType())
class ListenerPOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255))
enabled = wtypes.wsattr(bool, default=True)
protocol = wtypes.wsattr(wtypes.StringType(), mandatory=True)
protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
connection_limit = wtypes.wsattr(wtypes.IntegerType())
tls_certificate_id = wtypes.wsattr(wtypes.UuidType())
tls_termination = wtypes.wsattr(TLSTermination)
class ListenerPUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255))
enabled = wtypes.wsattr(bool)
protocol = wtypes.wsattr(wtypes.StringType())
protocol_port = wtypes.wsattr(wtypes.IntegerType())
connection_limit = wtypes.wsattr(wtypes.IntegerType())
tls_certificate_id = wtypes.wsattr(wtypes.UuidType())
tls_termination = wtypes.wsattr(TLSTermination)

View File

@ -0,0 +1,52 @@
# Copyright 2014 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 wsme import types as wtypes
from octavia.api.v1.types import base
class VIP(base.BaseType):
"""Defines the response and acceptable POST request attributes."""
ip_address = wtypes.wsattr(base.IPAddressType())
net_port_id = wtypes.wsattr(wtypes.UuidType())
subnet_id = wtypes.wsattr(wtypes.UuidType())
floating_ip_id = wtypes.wsattr(wtypes.UuidType())
floating_ip_network_id = wtypes.wsattr(wtypes.UuidType())
class LoadBalancerResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
id = wtypes.wsattr(wtypes.UuidType())
name = wtypes.wsattr(wtypes.StringType())
description = wtypes.wsattr(wtypes.StringType())
provisioning_status = wtypes.wsattr(wtypes.StringType())
operating_status = wtypes.wsattr(wtypes.StringType())
enabled = wtypes.wsattr(bool)
vip = wtypes.wsattr(VIP)
class LoadBalancerPOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255))
enabled = wtypes.wsattr(bool, default=True)
vip = wtypes.wsattr(VIP, mandatory=True)
class LoadBalancerPUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255))
enabled = wtypes.wsattr(bool)

View File

@ -0,0 +1,44 @@
# Copyright 2014 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 wsme import types as wtypes
from octavia.api.v1.types import base
class MemberResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
id = wtypes.wsattr(wtypes.UuidType())
operating_status = wtypes.wsattr(wtypes.StringType())
enabled = wtypes.wsattr(bool)
ip_address = wtypes.wsattr(base.IPAddressType())
protocol_port = wtypes.wsattr(wtypes.IntegerType())
weight = wtypes.wsattr(wtypes.IntegerType())
subnet_id = wtypes.wsattr(wtypes.UuidType())
class MemberPOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
enabled = wtypes.wsattr(bool, default=True)
ip_address = wtypes.wsattr(base.IPAddressType(), mandatory=True)
protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
weight = wtypes.wsattr(wtypes.IntegerType(), default=1)
subnet_id = wtypes.wsattr(wtypes.UuidType())
class MemberPUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
protocol_port = wtypes.wsattr(wtypes.IntegerType())
enabled = wtypes.wsattr(bool)
weight = wtypes.wsattr(wtypes.IntegerType())

View File

@ -0,0 +1,67 @@
# Copyright 2014 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 wsme import types as wtypes
from octavia.api.v1.types import base
class SessionPersistenceResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
type = wtypes.wsattr(wtypes.text)
cookie_name = wtypes.wsattr(wtypes.text)
class SessionPersistencePOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
type = wtypes.wsattr(wtypes.text, mandatory=True)
cookie_name = wtypes.wsattr(wtypes.text)
class SessionPersistencePUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
type = wtypes.wsattr(wtypes.text)
cookie_name = wtypes.wsattr(wtypes.text)
class PoolResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
id = wtypes.wsattr(wtypes.UuidType())
name = wtypes.wsattr(wtypes.StringType())
description = wtypes.wsattr(wtypes.StringType())
operating_status = wtypes.wsattr(wtypes.StringType())
enabled = wtypes.wsattr(bool)
protocol = wtypes.wsattr(wtypes.text)
lb_algorithm = wtypes.wsattr(wtypes.text)
session_persistence = wtypes.wsattr(SessionPersistenceResponse)
class PoolPOST(base.BaseType):
"""Defines mandatory and optional attributes of a POST request."""
name = wtypes.wsattr(wtypes.StringType(max_length=255))
description = wtypes.wsattr(wtypes.StringType(max_length=255))
enabled = wtypes.wsattr(bool, default=True)
protocol = wtypes.wsattr(wtypes.text, mandatory=True)
lb_algorithm = wtypes.wsattr(wtypes.text, mandatory=True)
session_persistence = wtypes.wsattr(SessionPersistencePOST)
class PoolPUT(base.BaseType):
"""Defines attributes that are acceptable of a PUT request."""
name = wtypes.wsattr(wtypes.StringType())
description = wtypes.wsattr(wtypes.StringType())
enabled = wtypes.wsattr(bool)
protocol = wtypes.wsattr(wtypes.text)
lb_algorithm = wtypes.wsattr(wtypes.text)
session_persistence = wtypes.wsattr(SessionPersistencePUT)

View File

@ -44,6 +44,7 @@ DELETED = 'DELETED'
ERROR = 'ERROR'
SUPPORTED_PROVISIONING_STATUSES = (ACTIVE, PENDING_DELETE, PENDING_CREATE,
PENDING_UPDATE, DELETED, ERROR)
MUTABLE_STATUSES = (ACTIVE)
ONLINE = 'ONLINE'
OFFLINE = 'OFFLINE'

29
octavia/common/context.py Normal file
View File

@ -0,0 +1,29 @@
# Copyright 2014 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 octavia.db import api as db_api
from octavia.openstack.common import context as common_context
class Context(common_context.RequestContext):
def __init__(self, user_id, tenant_id, is_admin=False, auth_token=None):
super(Context, self).__init__(tenant=tenant_id, auth_token=auth_token,
is_admin=is_admin, user=user_id)
self._session = None
@property
def session(self):
if self._session is None:
self._session = db_api.get_session()
return self._session

View File

@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
class BaseDataModel(object):
# NOTE(brandon-logan) This does not discover dicts for relationship
# attributes.
def to_dict(self):
"""Converts a data model to a dictionary."""
ret = {}
for attr in self.__dict__:
if attr.startswith('_'):
@ -34,6 +37,12 @@ class BaseDataModel(object):
return self.to_dict() == other.to_dict()
return False
@classmethod
def _name(cls):
"""Returns class name in a more human readable form."""
# Split the class name up by capitalized words
return ' '.join(re.findall('[A-Z][^A-Z]*', cls.__name__))
class SessionPersistence(BaseDataModel):
@ -122,7 +131,7 @@ class Listener(BaseDataModel):
default_pool_id=None, load_balancer_id=None, protocol=None,
protocol_port=None, connection_limit=None,
enabled=None, provisioning_status=None, operating_status=None,
default_tls_container_id=None, stats=None, default_pool=None,
tls_certificate_id=None, stats=None, default_pool=None,
load_balancer=None, sni_containers=None):
self.id = id
self.tenant_id = tenant_id
@ -136,7 +145,7 @@ class Listener(BaseDataModel):
self.enabled = enabled
self.provisioning_status = provisioning_status
self.operating_status = operating_status
self.default_tls_container_id = default_tls_container_id
self.tls_certificate_id = tls_certificate_id
self.stats = stats
self.default_pool = default_pool
self.load_balancer = load_balancer

View File

@ -18,6 +18,7 @@ Octavia base exception handling.
"""
from oslo.utils import excutils
from webob import exc
class OctaviaException(Exception):
@ -47,16 +48,31 @@ class OctaviaException(Exception):
return False
class BadRequest(OctaviaException):
message = _('Bad %(resource)s request: %(msg)s')
# NOTE(blogan) Using webob exceptions here because WSME exceptions a very
# limited at this point and they do not work well in _lookup methods in the
# controllers
class APIException(exc.HTTPClientError):
msg = "Something unknown went wrong"
code = 500
def __init__(self, **kwargs):
self.msg = self.msg % kwargs
super(APIException, self).__init__(detail=self.msg)
class NotFound(OctaviaException):
message = _('%(resource)s not found.')
class NotFound(APIException):
msg = _('%(resource)s %(id)s not found.')
code = 404
class NotAuthorized(OctaviaException):
message = _("Not authorized.")
class NotAuthorized(APIException):
msg = _("Not authorized.")
code = 401
class InvalidOption(APIException):
msg = _("%(value)s is not a valid option for %(option)s")
code = 400
class MissingArguments(OctaviaException):
@ -69,3 +85,30 @@ class CertificateStorageException(OctaviaException):
class CertificateGenerationException(OctaviaException):
message = _('Could not sign the certificate request: %(msg)s')
class DuplicateListenerEntry(APIException):
msg = _("Another Listener on this Load Balancer "
"is already using protocol_port %(port)d")
code = 409
class DuplicateMemberEntry(APIException):
msg = _("Another member on this pool is already using ip %(ip_address)s "
"on protocol_port %(port)d")
code = 409
class DuplicateHealthMonitor(APIException):
msg = _("This pool already has a health monitor")
code = 409
class DuplicatePoolEntry(APIException):
msg = _("This listener already has a default pool")
code = 409
class ImmutableObject(APIException):
msg = _("%(resource)s %(id)s is immutable and cannot be updated.")
code = 409

42
octavia/common/service.py Normal file
View File

@ -0,0 +1,42 @@
# Copyright 2014 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.config import cfg
from octavia.common import config
from octavia.i18n import _LI
from octavia.openstack.common import log
LOG = log.getLogger(__name__)
def prepare_service(argv=None):
"""Sets global config from config file and sets up logging."""
argv = argv or []
config.init(argv[1:])
LOG.info(_LI('Starting Octavia API server'))
cfg.set_defaults(log.log_opts,
default_log_levels=['amqp=WARN',
'amqplib=WARN',
'qpid.messaging=INFO',
'sqlalchemy=WARN',
'keystoneclient=INFO',
'stevedore=INFO',
'eventlet.wsgi.server=WARN',
'iso8601=WARN',
'paramiko=WARN',
'requests=WARN',
'ironic.openstack.common=WARN',
])
config.setup_logging(cfg.CONF)

View File

@ -21,7 +21,7 @@ _FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(cfg.CONF)
_FACADE = db_session.EngineFacade.from_config(cfg.CONF, sqlite_fk=True)
return _FACADE
@ -30,6 +30,7 @@ def get_engine():
return facade.get_engine()
def get_session(**kwargs):
def get_session(expire_on_commit=True):
"""Helper method to grab session."""
facade = _create_facade_lazily()
return facade.get_session(**kwargs)
return facade.get_session(expire_on_commit=expire_on_commit)

View File

@ -12,19 +12,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from octavia.openstack.common import uuidutils
from oslo.db.sqlalchemy import models
import sqlalchemy as sa
from sqlalchemy.ext import declarative
from sqlalchemy.orm import collections
from octavia.openstack.common import uuidutils
class OctaviaBase(models.ModelBase):
__data_model__ = None
def to_data_model(self, calling_cls=None):
def to_data_model(self, _calling_cls=None):
"""Converts to a data model.
:param _calling_cls: Used only for internal recursion of this method.
Should not be called from the outside.
"""
if not self.__data_model__:
raise NotImplementedError
dm_kwargs = {}
@ -35,16 +40,17 @@ class OctaviaBase(models.ModelBase):
for attr_name in attr_names:
attr = getattr(self, attr_name)
if isinstance(attr, OctaviaBase):
if attr.__class__ != calling_cls:
if attr.__class__ != _calling_cls:
dm_kwargs[attr_name] = attr.to_data_model(
calling_cls=self.__class__)
_calling_cls=self.__class__)
elif isinstance(attr, collections.InstrumentedList):
dm_kwargs[attr_name] = []
for item in attr:
if isinstance(item, OctaviaBase):
if attr.__class__ != calling_cls:
if attr.__class__ != _calling_cls:
dm_kwargs[attr_name].append(
item.to_data_model(calling_cls=self.__class__))
item.to_data_model(
_calling_cls=self.__class__))
else:
dm_kwargs[attr_name].append(item)
return self.__data_model__(**dm_kwargs)

View File

@ -279,7 +279,7 @@ def upgrade():
sa.Column(u'protocol_port', sa.Integer(), nullable=False),
sa.Column(u'connection_limit', sa.Integer(), nullable=True),
sa.Column(u'load_balancer_id', sa.String(36), nullable=True),
sa.Column(u'default_tls_container_id', sa.String(36), nullable=True),
sa.Column(u'tls_certificate_id', sa.String(36), nullable=True),
sa.Column(u'default_pool_id', sa.String(36), nullable=True),
sa.Column(u'provisioning_status', sa.String(16), nullable=False),
sa.Column(u'operating_status', sa.String(16), nullable=False),

View File

@ -0,0 +1,38 @@
# Copyright 2014 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.
"""update member address column
Revision ID: 4faaa983e7a9
Revises: 13500e2e978d
Create Date: 2014-09-29 11:22:16.565071
"""
# revision identifiers, used by Alembic.
revision = '4faaa983e7a9'
down_revision = '13500e2e978d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.alter_column(u'member', u'address', new_column_name=u'ip_address',
existing_type=sa.String(64))
def downgrade():
op.alter_column(u'member', u'ip_address', new_column_name=u'address',
existing_type=sa.String(64))

View File

@ -115,7 +115,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
__tablename__ = "member"
__table_args__ = (
sa.UniqueConstraint('pool_id', 'ip_address', 'protocol_port',
name='uq_member_pool_id_ip_address_protocol_port'),
name='uq_member_pool_id_address_protocol_port'),
)
pool_id = sa.Column(
@ -123,7 +123,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
sa.ForeignKey("pool.id", name="fk_member_pool_id"),
nullable=False)
subnet_id = sa.Column(sa.String(36), nullable=True)
ip_address = sa.Column(sa.String(64), nullable=False)
ip_address = sa.Column('ip_address', sa.String(64), nullable=False)
protocol_port = sa.Column(sa.Integer, nullable=False)
weight = sa.Column(sa.Integer, nullable=True)
operating_status = sa.Column(
@ -137,8 +137,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
cascade="delete"))
class HealthMonitor(base_models.BASE, base_models.IdMixin,
base_models.TenantMixin):
class HealthMonitor(base_models.BASE):
__data_model__ = data_models.HealthMonitor
@ -261,7 +260,7 @@ class Listener(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
sa.String(36),
sa.ForeignKey("load_balancer.id", name="fk_listener_load_balancer_id"),
nullable=True)
default_tls_container_id = sa.Column(sa.String(36), nullable=True)
tls_certificate_id = sa.Column(sa.String(36), nullable=True)
default_pool_id = sa.Column(
sa.String(36),
sa.ForeignKey("pool.id", name="fk_listener_pool_id"),

View File

@ -17,8 +17,10 @@ Defines interface for DB access that Resource or Octavia Controllers may
reference
"""
from octavia.common import constants
from octavia.common import exceptions
from octavia.db import models
from octavia.openstack.common import uuidutils
class BaseRepository(object):
@ -26,36 +28,77 @@ class BaseRepository(object):
model_class = None
def create(self, session, **model_kwargs):
with session.begin():
"""Base create method for a database entity.
:param session: A Sql Alchemy database session.
:param model_kwargs: Attributes of the model to insert.
:returns: octavia.common.data_model
"""
with session.begin(subtransactions=True):
model = self.model_class(**model_kwargs)
session.add(model)
return model.to_data_model()
def delete(self, session, **filters):
"""Deletes an entity from the database.
:param session: A Sql Alchemy database session.
:param filters: Filters to decide which entity should be deleted.
:returns: None
"""
model = session.query(self.model_class).filter_by(**filters).first()
with session.begin():
with session.begin(subtransactions=True):
session.delete(model)
session.flush()
def delete_batch(self, session, ids=None):
"""Batch deletes by entity ids."""
ids = ids or []
[self.delete(session, id) for id in ids]
def update(self, session, id, **model_kwargs):
with session.begin():
"""Updates an entity in the database.
:param session: A Sql Alchemy database session.
:param model_kwargs: Entity attributes that should be updates.
:returns: octavia.common.data_model
"""
with session.begin(subtransactions=True):
session.query(self.model_class).filter_by(
id=id).update(model_kwargs)
def get(self, session, **filters):
"""Retrieves an entity from the database.
:param session: A Sql Alchemy database session.
:param filters: Filters to decide which entity should be retrieved.
:returns: octavia.common.data_model
"""
model = session.query(self.model_class).filter_by(**filters).first()
if not model:
raise exceptions.NotFound(resource=self.model_class.__name__)
return
return model.to_data_model()
def get_all(self, session, **filters):
"""Retrieves a list of entities from the database.
:param session: A Sql Alchemy database session.
:param filters: Filters to decide which entities should be retrieved.
:returns: [octavia.common.data_model]
"""
model_list = session.query(self.model_class).filter_by(**filters).all()
data_model_list = [model.to_data_model() for model in model_list]
return data_model_list
def exists(self, session, id):
"""Determines whether an entity exists in the database by its id.
:param session: A Sql Alchemy database session.
:param id: id of entity to check for existance.
:returns: octavia.common.data_model
"""
return bool(session.query(self.model_class).filter_by(id=id).first())
class Repositories(object):
@ -71,18 +114,128 @@ class Repositories(object):
self.amphora = AmphoraRepository()
self.sni = SNIRepository()
def create_load_balancer_and_vip(self, session, load_balancer_dict,
vip_dict):
"""Inserts load balancer and vip entities into the database.
Inserts load balancer and vip entities into the database in one
transaction and returns the data model of the load balancer.
:param session: A Sql Alchemy database session.
:param load_balancer_dict: Dictionary representation of a load balancer
:param vip_dict: Dictionary representation of a vip
:returns: octava.common.data_models.LoadBalancer
"""
with session.begin():
load_balancer_dict['id'] = uuidutils.generate_uuid()
lb = models.LoadBalancer(**load_balancer_dict)
session.add(lb)
vip_dict['load_balancer_id'] = load_balancer_dict['id']
vip = models.Vip(**vip_dict)
session.add(vip)
return self.load_balancer.get(session, id=lb.id)
def create_pool_on_listener(self, session, listener_id,
pool_dict, sp_dict=None):
"""Inserts a pool and session persistence entity into the database.
:param session: A Sql Alchemy database session.
:param listener_id: id of the listener the pool will be referenced by
:param pool_dict: Dictionary representation of a pool
:param sp_dict: Dictionary representation of a session persistence
:returns: octavia.common.data_models.Pool
"""
with session.begin(subtransactions=True):
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'])
return self.pool.get(session, id=db_pool.id)
def update_pool_on_listener(self, session, pool_id, pool_dict, sp_dict):
"""Updates a pool and session persistence entity in the database.
:param session: A Sql Alchemy database session.
:param pool_dict: Dictionary representation of a pool
:param sp_dict: Dictionary representation of a session persistence
:returns: octavia.common.data_models.Pool
"""
with session.begin(subtransactions=True):
self.pool.update(session, pool_id, **pool_dict)
if sp_dict:
if self.session_persistence.exists(session, pool_id):
self.session_persistence.update(session, pool_id,
**sp_dict)
else:
sp_dict['pool_id'] = pool_id
self.session_persistence.create(session, **sp_dict)
db_pool = self.pool.get(session, id=pool_id)
if db_pool.session_persistence is not None and not sp_dict:
self.session_persistence.delete(session, pool_id=pool_id)
db_pool = self.pool.get(session, id=pool_id)
return db_pool
def test_and_set_lb_and_listener_prov_status(self, session, lb_id,
listener_id, lb_prov_status,
listener_prov_status):
"""Tests and sets a load balancer and listener provisioning status.
Puts a lock on the load balancer table to check the status of a
load balancer. If the status is ACTIVE then the status of the load
balancer and listener is updated and the method returns True. If the
status is not ACTIVE, then nothing is done and False is returned.
:param session: A Sql Alchemy database session.
:param lb_id: id of Load Balancer
:param listener_id: id of a Listener
:param lb_prov_status: Status to set Load Balancer and Listener if
check passes.
:returns: bool
"""
success = self.load_balancer.test_and_set_provisioning_status(
session, lb_id, lb_prov_status)
self.listener.update(session, listener_id,
provisioning_status=listener_prov_status)
return success
class LoadBalancerRepository(BaseRepository):
model_class = models.LoadBalancer
def test_and_set_provisioning_status(self, session, id, status):
"""Tests and sets a load balancer and provisioning status.
Puts a lock on the load balancer table to check the status of a
load balancer. If the status is ACTIVE then the status of the load
balancer is updated and the method returns True. If the
status is not ACTIVE, then nothing is done and False is returned.
:param session: A Sql Alchemy database session.
:param id: id of Load Balancer
:param status: Status to set Load Balancer if check passes.
:returns: bool
"""
with session.begin(subtransactions=True):
lb = session.query(self.model_class).with_for_update().filter_by(
id=id).one()
if lb.provisioning_status not in constants.MUTABLE_STATUSES:
return False
lb.provisioning_status = status
session.add(lb)
return True
class VipRepository(BaseRepository):
model_class = models.Vip
def update(self, session, load_balancer_id, **model_kwargs):
with session.begin():
"""Updates a vip entity in the database by load_balancer_id."""
with session.begin(subtransactions=True):
session.query(self.model_class).filter_by(
load_balancer_id=load_balancer_id).update(model_kwargs)
@ -92,7 +245,8 @@ class HealthMonitorRepository(BaseRepository):
model_class = models.HealthMonitor
def update(self, session, pool_id, **model_kwargs):
with session.begin():
"""Updates a health monitor entity in the database by pool_id."""
with session.begin(subtransactions=True):
session.query(self.model_class).filter_by(
pool_id=pool_id).update(model_kwargs)
@ -102,10 +256,16 @@ class SessionPersistenceRepository(BaseRepository):
model_class = models.SessionPersistence
def update(self, session, pool_id, **model_kwargs):
with session.begin():
"""Updates a session persistence entity in the database by pool_id."""
with session.begin(subtransactions=True):
session.query(self.model_class).filter_by(
pool_id=pool_id).update(model_kwargs)
def exists(self, session, pool_id):
"""Checks if session persistence exists on a pool."""
return bool(session.query(self.model_class).filter_by(
pool_id=pool_id).first())
class PoolRepository(BaseRepository):
@ -117,6 +277,7 @@ class MemberRepository(BaseRepository):
model_class = models.Member
def delete_members(self, session, member_ids):
"""Batch deletes members from a pool."""
self.delete_batch(session, member_ids)
@ -124,13 +285,19 @@ class ListenerRepository(BaseRepository):
model_class = models.Listener
def has_pool(self, session, id):
"""Checks if a listener has a pool."""
listener = self.get(session, id=id)
return bool(listener.default_pool)
class ListenerStatisticsRepository(BaseRepository):
model_class = models.ListenerStatistics
def update(self, session, listener_id, **model_kwargs):
with session.begin():
"""Updates a listener's statistics by a listener's id."""
with session.begin(subtransactions=True):
session.query(self.model_class).filter_by(
listener_id=listener_id).update(model_kwargs)
@ -140,7 +307,8 @@ class AmphoraRepository(BaseRepository):
model_class = models.Amphora
def associate(self, session, load_balancer_id, amphora_id):
with session.begin():
"""Associates an amphora with a load balancer."""
with session.begin(subtransactions=True):
load_balancer = session.query(models.LoadBalancer).filter_by(
id=load_balancer_id).first()
amphora = session.query(self.model_class).filter_by(
@ -154,9 +322,10 @@ class SNIRepository(BaseRepository):
def update(self, session, listener_id=None, tls_container_id=None,
**model_kwargs):
"""Updates an SNI entity in the database."""
if not listener_id and tls_container_id:
raise exceptions.MissingArguments
with session.begin():
with session.begin(subtransactions=True):
if listener_id:
session.query(self.model_class).filter_by(
listener_id=listener_id).update(model_kwargs)

View File

View File

View File

@ -0,0 +1,216 @@
# Copyright 2014 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.
import logging
import mock
import pecan
import pecan.testing
from octavia.api import config as pconfig
from octavia.common import constants
from octavia.db import api as db_api
from octavia.db import repositories
from octavia.tests.functional.db import base as base_db_test
LOG = logging.getLogger(__name__)
class BaseAPITest(base_db_test.OctaviaDBTestBase):
BASE_PATH = '/v1'
LBS_PATH = '/loadbalancers'
LB_PATH = LBS_PATH + '/{lb_id}'
LISTENERS_PATH = LB_PATH + '/listeners'
LISTENER_PATH = LISTENERS_PATH + '/{listener_id}'
POOLS_PATH = LISTENER_PATH + '/pools'
POOL_PATH = POOLS_PATH + '/{pool_id}'
MEMBERS_PATH = POOL_PATH + '/members'
MEMBER_PATH = MEMBERS_PATH + '/{member_id}'
HM_PATH = POOL_PATH + '/healthmonitor'
def setUp(self):
super(BaseAPITest, self).setUp()
self.lb_repo = repositories.LoadBalancerRepository()
self.listener_repo = repositories.ListenerRepository()
self.pool_repo = repositories.PoolRepository()
self.member_repo = repositories.MemberRepository()
patcher = mock.patch('octavia.api.v1.handlers.controller_simulator.'
'handler.SimulatedControllerHandler')
patcher.start()
self.app = self._make_app()
def reset_pecan():
patcher.stop()
pecan.set_config({}, overwrite=True)
self.addCleanup(reset_pecan)
def _make_app(self):
return pecan.testing.load_test_app({'app': pconfig.app,
'wsme': pconfig.wsme})
def _get_full_path(self, path):
return ''.join([self.BASE_PATH, path])
def delete(self, path, headers=None, status=202, expect_errors=False):
headers = headers or {}
full_path = self._get_full_path(path)
response = self.app.delete(full_path,
headers=headers,
status=status,
expect_errors=expect_errors)
return response
def post(self, path, body, headers=None, status=202, expect_errors=False):
headers = headers or {}
full_path = self._get_full_path(path)
response = self.app.post_json(full_path,
params=body,
headers=headers,
status=status,
expect_errors=expect_errors)
return response
def put(self, path, body, headers=None, status=202, expect_errors=False):
headers = headers or {}
full_path = self._get_full_path(path)
response = self.app.put_json(full_path,
params=body,
headers=headers,
status=status,
expect_errors=expect_errors)
return response
def get(self, path, params=None, headers=None, status=200,
expect_errors=False):
full_path = self._get_full_path(path)
response = self.app.get(full_path,
params=params,
headers=headers,
status=status,
expect_errors=expect_errors)
return response
def create_load_balancer(self, vip, **optionals):
req_dict = {'vip': vip}
req_dict.update(optionals)
response = self.post(self.LBS_PATH, req_dict)
return response.json
def create_listener(self, lb_id, protocol, protocol_port, **optionals):
req_dict = {'protocol': protocol, 'protocol_port': protocol_port}
req_dict.update(optionals)
path = self.LISTENERS_PATH.format(lb_id=lb_id)
response = self.post(path, req_dict)
return response.json
def create_pool(self, lb_id, listener_id, protocol, lb_algorithm,
**optionals):
req_dict = {'protocol': protocol, 'lb_algorithm': lb_algorithm}
req_dict.update(optionals)
path = self.POOLS_PATH.format(lb_id=lb_id, listener_id=listener_id)
response = self.post(path, req_dict)
return response.json
def create_member(self, lb_id, listener_id, pool_id, ip_address,
protocol_port, **optionals):
req_dict = {'ip_address': ip_address, 'protocol_port': protocol_port}
req_dict.update(optionals)
path = self.MEMBERS_PATH.format(lb_id=lb_id, listener_id=listener_id,
pool_id=pool_id)
response = self.post(path, req_dict)
return response.json
def create_health_monitor(self, lb_id, listener_id, pool_id, type,
delay, timeout, fall_threshold, rise_threshold,
**optionals):
req_dict = {'type': type,
'delay': delay,
'timeout': timeout,
'fall_threshold': fall_threshold,
'rise_threshold': rise_threshold}
req_dict.update(optionals)
path = self.HM_PATH.format(lb_id=lb_id, listener_id=listener_id,
pool_id=pool_id)
response = self.post(path, req_dict)
return response.json
def _set_lb_and_children_statuses(self, lb_id, prov_status, op_status):
self.lb_repo.update(db_api.get_session(), lb_id,
provisioning_status=prov_status,
operating_status=op_status)
lb_listeners = self.listener_repo.get_all(db_api.get_session(),
load_balancer_id=lb_id)
for listener in lb_listeners:
if listener.default_pool_id:
self.pool_repo.update(db_api.get_session(),
listener.default_pool_id,
operating_status=op_status)
for member in listener.default_pool.members:
self.member_repo.update(db_api.get_session(), member.id,
operating_status=op_status)
self.listener_repo.update(db_api.get_session(), listener.id,
provisioning_status=prov_status,
operating_status=op_status)
def set_lb_status(self, lb_id, status=constants.ACTIVE):
if status == constants.DELETED:
op_status = constants.OFFLINE
elif status == constants.ACTIVE:
op_status = constants.ONLINE
else:
db_lb = self.lb_repo.get(db_api.get_session(), id=lb_id)
op_status = db_lb.operating_status
self._set_lb_and_children_statuses(lb_id, status, op_status)
return self.get(self.LB_PATH.format(lb_id=lb_id)).json
def assert_final_lb_statuses(self, lb_id, delete=False):
expected_prov_status = constants.ACTIVE
expected_op_status = constants.ONLINE
if delete:
expected_prov_status = constants.DELETED
expected_op_status = constants.OFFLINE
self.set_lb_status(lb_id, status=expected_prov_status)
self.assert_correct_lb_status(lb_id, expected_prov_status,
expected_op_status)
def assert_final_listener_statuses(self, lb_id, listener_id, delete=False):
expected_prov_status = constants.ACTIVE
expected_op_status = constants.ONLINE
if delete:
expected_prov_status = constants.DELETED
expected_op_status = constants.OFFLINE
self.set_lb_status(lb_id, status=expected_prov_status)
self.assert_correct_listener_status(lb_id, listener_id,
expected_prov_status,
expected_op_status)
def assert_correct_lb_status(self, lb_id, provisioning_status,
operating_status):
api_lb = self.get(self.LB_PATH.format(lb_id=lb_id)).json
self.assertEqual(provisioning_status,
api_lb.get('provisioning_status'))
self.assertEqual(operating_status,
api_lb.get('operating_status'))
def assert_correct_listener_status(self, lb_id, listener_id,
provisioning_status, operating_status):
api_listener = self.get(self.LISTENER_PATH.format(
lb_id=lb_id, listener_id=listener_id)).json
self.assertEqual(provisioning_status,
api_listener.get('provisioning_status'))
self.assertEqual(operating_status,
api_listener.get('operating_status'))

View File

@ -0,0 +1,197 @@
# Copyright 2014 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 octavia.common import constants
from octavia.tests.functional.api.v1 import base
class TestHealthMonitor(base.BaseAPITest):
def setUp(self):
super(TestHealthMonitor, self).setUp()
self.lb = self.create_load_balancer({})
self.set_lb_status(self.lb.get('id'))
self.listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
self.pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.hm_path = self.HM_PATH.format(lb_id=self.lb.get('id'),
listener_id=self.listener.get('id'),
pool_id=self.pool.get('id'))
def test_get(self):
api_hm = self.create_health_monitor(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.get(self.hm_path)
response_body = response.json
self.assertEqual(api_hm, response_body)
def test_bad_get(self):
self.get(self.hm_path, status=404)
def test_create(self):
api_hm = self.create_health_monitor(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assertEqual(constants.HEALTH_MONITOR_HTTP, api_hm.get('type'))
self.assertEqual(1, api_hm.get('delay'))
self.assertEqual(1, api_hm.get('timeout'))
self.assertEqual(1, api_hm.get('fall_threshold'))
self.assertEqual(1, api_hm.get('rise_threshold'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_create(self):
hm_json = {'name': 'test1'}
self.post(self.hm_path, hm_json, status=400)
def test_duplicate_create(self):
api_hm = self.create_health_monitor(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
self.set_lb_status(lb_id=self.lb.get('id'))
self.post(self.hm_path, api_hm, status=409)
def test_update(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
self.set_lb_status(lb_id=self.lb.get('id'))
new_hm = {'type': constants.HEALTH_MONITOR_HTTPS}
self.put(self.hm_path, new_hm)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_update(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
new_hm = {'type': 'bad_type', 'delay': 2}
self.set_lb_status(self.lb.get('id'))
self.put(self.hm_path, new_hm, status=400)
def test_delete(self):
api_hm = self.create_health_monitor(self.lb.get('id'),
self.listener.get('id'),
self.pool['id'],
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.get(self.hm_path)
self.assertEqual(api_hm, response.json)
self.delete(self.hm_path)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_delete(self):
self.delete(self.hm_path, status=404)
def test_create_when_lb_pending_update(self):
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.post(self.hm_path, body={'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1, 'timeout': 1,
'fall_threshold': 1,
'rise_threshold': 1}, status=409)
def test_update_when_lb_pending_update(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.put(self.hm_path, body={'rise_threshold': 2}, status=409)
def test_delete_when_lb_pending_update(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.delete(self.hm_path, status=409)
def test_create_when_lb_pending_delete(self):
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.post(self.hm_path, body={'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1, 'timeout': 1,
'fall_threshold': 1,
'rise_threshold': 1}, status=409)
def test_update_when_lb_pending_delete(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.put(self.hm_path, body={'rise_threshold': 2}, status=409)
def test_delete_when_lb_pending_delete(self):
self.create_health_monitor(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.delete(self.hm_path, status=409)

View File

@ -0,0 +1,302 @@
# Copyright 2014 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 octavia.common import constants
from octavia.openstack.common import uuidutils
from octavia.tests.functional.api.v1 import base
class TestListener(base.BaseAPITest):
def setUp(self):
super(TestListener, self).setUp()
self.lb = self.create_load_balancer({})
self.set_lb_status(self.lb.get('id'))
self.listeners_path = self.LISTENERS_PATH.format(
lb_id=self.lb.get('id'))
def test_get_all(self):
listener1 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
listener2 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 81)
self.set_lb_status(self.lb.get('id'))
listener3 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 82)
self.set_lb_status(self.lb.get('id'))
response = self.get(self.listeners_path)
api_listeners = response.json
self.assertEqual(3, len(api_listeners))
listener1['provisioning_status'] = constants.ACTIVE
listener1['operating_status'] = constants.ONLINE
listener2['provisioning_status'] = constants.ACTIVE
listener2['operating_status'] = constants.ONLINE
listener3['provisioning_status'] = constants.ACTIVE
listener3['operating_status'] = constants.ONLINE
self.assertIn(listener1, api_listeners)
self.assertIn(listener2, api_listeners)
self.assertIn(listener3, api_listeners)
def test_get_all_bad_lb_id(self):
path = self.LISTENERS_PATH.format(lb_id='SEAN-CONNERY')
self.get(path, status=404)
def test_get(self):
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
listener_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))
response = self.get(listener_path)
api_lb = response.json
expected = {'name': None, 'description': None, 'enabled': True,
'operating_status': constants.OFFLINE,
'provisioning_status': constants.PENDING_CREATE,
'connection_limit': None}
listener.update(expected)
self.assertEqual(listener, api_lb)
def test_get_bad_listener_id(self):
listener_path = self.LISTENER_PATH.format(lb_id=self.lb.get('id'),
listener_id='SEAN-CONNERY')
self.get(listener_path, status=404)
def test_create(self):
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10,
'tls_certificate_id': uuidutils.generate_uuid()}
response = self.post(self.listeners_path, lb_listener)
listener_api = response.json
extra_expects = {'provisioning_status': constants.PENDING_CREATE,
'operating_status': constants.OFFLINE}
lb_listener.update(extra_expects)
self.assertTrue(uuidutils.is_uuid_like(listener_api.get('id')))
lb_listener['id'] = listener_api.get('id')
self.assertEqual(lb_listener, listener_api)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_final_lb_statuses(self.lb.get('id'))
self.assert_final_listener_statuses(self.lb.get('id'),
listener_api.get('id'))
def test_create_defaults(self):
defaults = {'name': None, 'description': None, 'enabled': True,
'connection_limit': None, 'tls_certificate_id': None}
lb_listener = {'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80}
response = self.post(self.listeners_path, lb_listener)
listener_api = response.json
extra_expects = {'provisioning_status': constants.PENDING_CREATE,
'operating_status': constants.OFFLINE}
lb_listener.update(extra_expects)
lb_listener.update(defaults)
self.assertTrue(uuidutils.is_uuid_like(listener_api.get('id')))
lb_listener['id'] = listener_api.get('id')
self.assertEqual(lb_listener, listener_api)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_final_lb_statuses(self.lb.get('id'))
self.assert_final_listener_statuses(self.lb.get('id'),
listener_api.get('id'))
def test_update(self):
tls_uuid = uuidutils.generate_uuid()
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_TCP, 80,
name='listener1', description='desc1',
enabled=False, connection_limit=10,
tls_certificate_id=tls_uuid)
self.set_lb_status(self.lb.get('id'))
new_listener = {'name': 'listener2', 'enabled': True}
listener_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))
response = self.put(listener_path, new_listener)
api_listener = response.json
update_expect = {'name': 'listener2', 'enabled': True,
'provisioning_status': constants.PENDING_UPDATE,
'operating_status': constants.ONLINE}
listener.update(update_expect)
self.assertEqual(listener, api_listener)
response = self.get(listener_path)
api_listener = response.json
self.assertEqual(listener, api_listener)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_final_lb_statuses(self.lb.get('id'))
self.assert_final_listener_statuses(self.lb.get('id'),
api_listener.get('id'))
def test_update_bad_listener_id(self):
listener_path = self.LISTENER_PATH.format(lb_id=self.lb.get('id'),
listener_id='SEAN-CONNERY')
self.put(listener_path, body={}, status=404)
def test_create_listeners_same_port(self):
listener1 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_TCP, 80)
self.set_lb_status(self.lb.get('id'))
listener2_post = {'protocol': listener1.get('protocol'),
'protocol_port': listener1.get('protocol_port')}
self.post(self.listeners_path, listener2_post, status=409)
def test_update_listeners_same_port(self):
listener1 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_TCP, 80)
self.set_lb_status(self.lb.get('id'))
listener2 = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_TCP, 81)
self.set_lb_status(self.lb.get('id'))
listener2_put = {'protocol': listener1.get('protocol'),
'protocol_port': listener1.get('protocol_port')}
listener2_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener2.get('id'))
self.put(listener2_path, listener2_put, status=409)
def test_delete(self):
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
listener_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))
self.delete(listener_path)
response = self.get(listener_path)
api_listener = response.json
expected = {'name': None, 'description': None, 'enabled': True,
'operating_status': constants.ONLINE,
'provisioning_status': constants.PENDING_DELETE,
'connection_limit': None}
listener.update(expected)
self.assertEqual(listener, api_listener)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_final_lb_statuses(self.lb.get('id'))
self.assert_final_listener_statuses(self.lb.get('id'),
api_listener.get('id'),
delete=True)
def test_delete_bad_listener_id(self):
listener_path = self.LISTENER_PATH.format(lb_id=self.lb.get('id'),
listener_id='SEAN-CONNERY')
self.delete(listener_path, status=404)
def test_create_listener_bad_protocol(self):
lb_listener = {'protocol': 'SEAN_CONNERY',
'protocol_port': 80}
self.post(self.listeners_path, lb_listener, status=400)
def test_update_listener_bad_protocol(self):
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_TCP, 80)
self.set_lb_status(self.lb.get('id'))
new_listener = {'protocol': 'SEAN_CONNERY',
'protocol_port': 80}
listener_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))
self.put(listener_path, new_listener, status=400)
def test_update_pending_create(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10}
self.post(self.LISTENERS_PATH.format(lb_id=lb.get('id')),
lb_listener, status=409)
def test_delete_pending_update(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
self.set_lb_status(lb.get('id'))
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10}
api_listener = self.post(
self.LISTENERS_PATH.format(lb_id=lb.get('id')), lb_listener).json
self.delete(self.LISTENER_PATH.format(
lb_id=lb.get('id'), listener_id=api_listener.get('id')),
status=409)
def test_update_pending_update(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
self.set_lb_status(lb.get('id'))
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10}
api_listener = self.post(
self.LISTENERS_PATH.format(lb_id=lb.get('id')), lb_listener).json
self.set_lb_status(lb.get('id'))
self.put(self.LB_PATH.format(lb_id=lb.get('id')), {'name': 'hi'})
self.put(self.LISTENER_PATH.format(
lb_id=lb.get('id'), listener_id=api_listener.get('id')),
{}, status=409)
def test_update_pending_delete(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
self.set_lb_status(lb.get('id'))
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10}
api_listener = self.post(
self.LISTENERS_PATH.format(lb_id=lb.get('id')), lb_listener).json
self.set_lb_status(lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')))
self.put(self.LISTENER_PATH.format(
lb_id=lb.get('id'), listener_id=api_listener.get('id')),
{}, status=409)
def test_delete_pending_delete(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
self.set_lb_status(lb.get('id'))
lb_listener = {'name': 'listener1', 'description': 'desc1',
'enabled': False, 'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80, 'connection_limit': 10}
api_listener = self.post(
self.LISTENERS_PATH.format(lb_id=lb.get('id')), lb_listener).json
self.set_lb_status(lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')))
self.delete(self.LISTENER_PATH.format(
lb_id=lb.get('id'), listener_id=api_listener.get('id')),
status=409)
def test_create_with_tls_termination_data(self):
tls = {'certificate': 'blah', 'intermediate_certificate': 'blah',
'private_key': 'blah', 'passphrase': 'blah'}
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80,
tls_termination=tls)
self.assertIsNone(listener.get('tls_termination'))
get_listener = self.get(self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))).json
self.assertIsNone(get_listener.get('tls_termination'))
def test_update_with_tls_termination_data(self):
tls = {'certificate': 'blah', 'intermediate_certificate': 'blah',
'private_key': 'blah', 'passphrase': 'blah'}
listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
listener_path = self.LISTENER_PATH.format(
lb_id=self.lb.get('id'), listener_id=listener.get('id'))
listener = self.put(listener_path, {'tls_termination': tls}).json
self.assertIsNone(listener.get('tls_termination'))
get_listener = self.get(listener_path).json
self.assertIsNone(get_listener.get('tls_termination'))

View File

@ -0,0 +1,208 @@
# Copyright 2014 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 octavia.common import constants
from octavia.openstack.common import uuidutils
from octavia.tests.functional.api.v1 import base
class TestLoadBalancer(base.BaseAPITest):
def test_empty_list(self):
response = self.get(self.LBS_PATH)
api_list = response.json
self.assertEqual([], api_list)
def test_create(self):
lb_json = {'name': 'test1', 'vip': {}}
response = self.post(self.LBS_PATH, lb_json)
api_lb = response.json
self.assertTrue(uuidutils.is_uuid_like(api_lb.get('id')))
self.assertEqual(lb_json.get('name'), api_lb.get('name'))
self.assertEqual(constants.PENDING_CREATE,
api_lb.get('provisioning_status'))
self.assertEqual(constants.OFFLINE,
api_lb.get('operating_status'))
self.assertTrue(api_lb.get('enabled'))
self.assert_final_lb_statuses(api_lb.get('id'))
def test_create_without_vip(self):
lb_json = {'name': 'test1'}
self.post(self.LB_PATH, lb_json, status=400)
def test_get_all(self):
lb1 = self.create_load_balancer({}, name='lb1')
lb2 = self.create_load_balancer({}, name='lb2')
lb3 = self.create_load_balancer({}, name='lb3')
response = self.get(self.LBS_PATH)
lbs = response.json
lb_id_names = [(lb.get('id'), lb.get('name')) for lb in lbs]
self.assertEqual(3, len(lbs))
self.assertIn((lb1.get('id'), lb1.get('name')), lb_id_names)
self.assertIn((lb2.get('id'), lb2.get('name')), lb_id_names)
self.assertIn((lb3.get('id'), lb3.get('name')), lb_id_names)
def test_get(self):
vip = {'ip_address': '10.0.0.1',
'floating_ip_id': uuidutils.generate_uuid(),
'net_port_id': uuidutils.generate_uuid(),
'subnet_id': uuidutils.generate_uuid(),
'floating_ip_network_id': uuidutils.generate_uuid()}
lb = self.create_load_balancer(vip, name='lb1',
description='test1_desc',
enabled=False)
response = self.get(self.LB_PATH.format(lb_id=lb.get('id')))
self.assertEqual('lb1', response.json.get('name'))
self.assertEqual('test1_desc', response.json.get('description'))
self.assertFalse(response.json.get('enabled'))
self.assertEqual(vip, response.json.get('vip'))
def test_get_bad_lb_id(self):
path = self.LB_PATH.format(lb_id='SEAN-CONNERY')
self.get(path, status=404)
def test_create_with_vip(self):
vip = {'ip_address': '10.0.0.1',
'floating_ip_id': uuidutils.generate_uuid(),
'net_port_id': uuidutils.generate_uuid(),
'subnet_id': uuidutils.generate_uuid(),
'floating_ip_network_id': uuidutils.generate_uuid()}
lb_json = {'name': 'test1', 'description': 'test1_desc',
'vip': vip, 'enabled': False}
response = self.post(self.LBS_PATH, lb_json)
api_lb = response.json
self.assertTrue(uuidutils.is_uuid_like(api_lb.get('id')))
self.assertEqual(lb_json.get('name'), api_lb.get('name'))
self.assertEqual(lb_json.get('description'), api_lb.get('description'))
self.assertEqual(constants.PENDING_CREATE,
api_lb['provisioning_status'])
self.assertEqual(constants.OFFLINE,
api_lb['operating_status'])
self.assertEqual(vip, api_lb.get('vip'))
self.assertEqual(lb_json.get('enabled'), api_lb.get('enabled'))
self.assert_final_lb_statuses(api_lb.get('id'))
def test_create_with_long_name(self):
lb_json = {'name': 'n' * 256, 'vip': {}}
self.post(self.LBS_PATH, lb_json, status=400)
def test_create_with_long_description(self):
lb_json = {'description': 'n' * 256, 'vip': {}}
self.post(self.LBS_PATH, lb_json, status=400)
def test_create_with_nonuuid_vip_attributes(self):
lb_json = {'vip': {'floating_ip_id': 'HI'}}
self.post(self.LBS_PATH, lb_json, status=400)
def test_update(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_json = {'name': 'lb2'}
lb = self.set_lb_status(lb.get('id'))
response = self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json)
api_lb = response.json
r_vip = api_lb.get('vip')
self.assertIsNone(r_vip.get('floating_ip_id'))
self.assertEqual('lb2', api_lb.get('name'))
self.assertEqual('desc1', api_lb.get('description'))
self.assertFalse(api_lb.get('enabled'))
self.assertEqual(constants.PENDING_UPDATE,
api_lb.get('provisioning_status'))
self.assertEqual(lb.get('operational_status'),
api_lb.get('operational_status'))
self.assert_final_lb_statuses(api_lb.get('id'))
def test_update_with_vip(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_json = {'vip': {'floating_ip_id': '1234'}}
lb = self.set_lb_status(lb.get('id'))
response = self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json)
api_lb = response.json
r_vip = api_lb.get('vip')
self.assertIsNone(r_vip.get('floating_ip_id'))
self.assertEqual('lb1', api_lb.get('name'))
self.assertEqual('desc1', api_lb.get('description'))
self.assertFalse(api_lb.get('enabled'))
self.assertEqual(constants.PENDING_UPDATE,
api_lb.get('provisioning_status'))
self.assertEqual(lb.get('operational_status'),
api_lb.get('operational_status'))
self.assert_final_lb_statuses(api_lb.get('id'))
def test_update_bad_lb_id(self):
path = self.LB_PATH.format(lb_id='SEAN-CONNERY')
self.put(path, body={}, status=404)
def test_update_pending_create(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_json = {'vip': {'floating_ip_id': '1234'}}
self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json, status=409)
def test_delete_pending_create(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
self.delete(self.LB_PATH.format(lb_id=lb.get('id')), status=409)
def test_update_pending_update(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_json = {'vip': {'floating_ip_id': '1234'}}
lb = self.set_lb_status(lb.get('id'))
self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json)
self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json, status=409)
def test_delete_pending_update(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb_json = {'vip': {'floating_ip_id': '1234'}}
lb = self.set_lb_status(lb.get('id'))
self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json)
self.delete(self.LB_PATH.format(lb_id=lb.get('id')), status=409)
def test_update_pending_delete(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb = self.set_lb_status(lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')))
lb_json = {'vip': {'floating_ip_id': '1234'}}
self.put(self.LB_PATH.format(lb_id=lb.get('id')), lb_json, status=409)
def test_delete_pending_delete(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb = self.set_lb_status(lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')), status=409)
def test_delete(self):
lb = self.create_load_balancer({}, name='lb1', description='desc1',
enabled=False)
lb = self.set_lb_status(lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=lb.get('id')))
response = self.get(self.LB_PATH.format(lb_id=lb.get('id')))
api_lb = response.json
self.assertEqual('lb1', api_lb.get('name'))
self.assertEqual('desc1', api_lb.get('description'))
self.assertFalse(api_lb.get('enabled'))
self.assertEqual(constants.PENDING_DELETE,
api_lb.get('provisioning_status'))
self.assertEqual(lb.get('operational_status'),
api_lb.get('operational_status'))
self.assert_final_lb_statuses(api_lb.get('id'), delete=True)
def test_delete_bad_lb_id(self):
path = self.LB_PATH.format(lb_id='bad_uuid')
self.delete(path, status=404)

View File

@ -0,0 +1,250 @@
# Copyright 2014 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 octavia.common import constants
from octavia.openstack.common import uuidutils
from octavia.tests.functional.api.v1 import base
class TestMember(base.BaseAPITest):
def setUp(self):
super(TestMember, self).setUp()
self.lb = self.create_load_balancer({})
self.set_lb_status(self.lb.get('id'))
self.listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
self.pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.members_path = self.MEMBERS_PATH.format(
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'),
pool_id=self.pool.get('id'))
self.member_path = self.members_path + '/{member_id}'
def test_get(self):
api_member = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
response = self.get(self.member_path.format(
member_id=api_member.get('id')))
response_body = response.json
self.assertEqual(api_member, response_body)
def test_bad_get(self):
self.get(self.member_path.format(member_id=uuidutils.generate_uuid()),
status=404)
def test_get_all(self):
api_m_1 = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
self.set_lb_status(self.lb.get('id'))
api_m_2 = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.2', 80)
self.set_lb_status(self.lb.get('id'))
# Original objects didn't have the updated operating status that exists
# in the DB.
api_m_1['operating_status'] = constants.ONLINE
api_m_2['operating_status'] = constants.ONLINE
response = self.get(self.members_path)
response_body = response.json
self.assertIsInstance(response_body, list)
self.assertEqual(2, len(response_body))
self.assertIn(api_m_1, response_body)
self.assertIn(api_m_2, response_body)
def test_empty_get_all(self):
response = self.get(self.members_path)
response_body = response.json
self.assertIsInstance(response_body, list)
self.assertEqual(0, len(response_body))
def test_create(self):
api_member = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
self.assertEqual('10.0.0.1', api_member.get('ip_address'))
self.assertEqual(80, api_member.get('protocol_port'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_create(self):
api_member = {'name': 'test1'}
self.post(self.members_path, api_member, status=400)
def test_duplicate_create(self):
member = {'ip_address': '10.0.0.1', 'protocol_port': 80}
self.post(self.members_path, member, status=202)
self.set_lb_status(self.lb.get('id'))
self.post(self.members_path, member, status=409)
def test_update(self):
api_member = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
self.set_lb_status(self.lb.get('id'))
new_member = {'protocol_port': 88}
self.put(self.member_path.format(member_id=api_member.get('id')),
new_member, status=202)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
response = self.get(self.member_path.format(
member_id=api_member.get('id')))
response_body = response.json
self.assertEqual(88, response_body.get('protocol_port'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_update(self):
api_member = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
new_member = {'protocol_port': 'ten'}
self.put(self.member_path.format(member_id=api_member.get('id')),
new_member, expect_errors=True)
def test_duplicate_update(self):
member = {'ip_address': '10.0.0.1', 'protocol_port': 80}
self.post(self.members_path, member)
self.set_lb_status(self.lb.get('id'))
member['protocol_port'] = 81
response = self.post(self.members_path, member)
self.set_lb_status(self.lb.get('id'))
member2 = response.json
member['protocol_port'] = 80
self.put(self.member_path.format(member_id=member2.get('id')),
member, status=409)
def test_delete(self):
api_member = self.create_member(self.lb.get('id'),
self.listener.get('id'),
self.pool.get('id'),
'10.0.0.1', 80)
self.set_lb_status(self.lb.get('id'))
response = self.get(self.member_path.format(
member_id=api_member.get('id')))
api_member['operating_status'] = constants.ONLINE
self.assertEqual(api_member, response.json)
self.delete(self.member_path.format(member_id=api_member.get('id')))
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_delete(self):
self.delete(self.member_path.format(
member_id=uuidutils.generate_uuid()), status=404)
def test_create_when_lb_pending_update(self):
self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.2",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.post(self.members_path,
body={'ip_address': '10.0.0.1', 'protocol_port': 80},
status=409)
def test_update_when_lb_pending_update(self):
member = self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.1",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.put(self.member_path.format(member_id=member.get('id')),
body={'protocol_port': 88}, status=409)
def test_delete_when_lb_pending_update(self):
member = self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.1",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.delete(self.member_path.format(member_id=member.get('id')),
status=409)
def test_create_when_lb_pending_delete(self):
self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.1",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.post(self.members_path,
body={'ip_address': '10.0.0.2', 'protocol_port': 88},
status=409)
def test_update_when_lb_pending_delete(self):
member = self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.1",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.put(self.member_path.format(member_id=member.get('id')),
body={'protocol_port': 88}, status=409)
def test_delete_when_lb_pending_delete(self):
member = self.create_member(self.lb.get('id'), self.listener.get('id'),
self.pool.get('id'), ip_address="10.0.0.1",
protocol_port=80)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.delete(self.member_path.format(member_id=member.get('id')),
status=409)

View File

@ -0,0 +1,390 @@
# Copyright 2014 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 octavia.common import constants
from octavia.openstack.common import uuidutils
from octavia.tests.functional.api.v1 import base
class TestPool(base.BaseAPITest):
def setUp(self):
super(TestPool, self).setUp()
self.lb = self.create_load_balancer({})
self.set_lb_status(self.lb.get('id'))
self.listener = self.create_listener(self.lb.get('id'),
constants.PROTOCOL_HTTP, 80)
self.set_lb_status(self.lb.get('id'))
self.pools_path = self.POOLS_PATH.format(
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'))
self.pool_path = self.pools_path + '/{pool_id}'
def test_get(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(lb_id=self.lb.get('id'))
api_pool['operating_status'] = constants.ONLINE
response = self.get(self.pool_path.format(pool_id=api_pool.get('id')))
response_body = response.json
self.assertEqual(api_pool, response_body)
def test_bad_get(self):
self.get(self.pool_path.format(pool_id=uuidutils.generate_uuid()),
status=404)
def test_get_all(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.get(self.pools_path)
response_body = response.json
self.assertIsInstance(response_body, list)
self.assertEqual(1, len(response_body))
self.assertEqual(api_pool.get('id'), response_body[0].get('id'))
def test_empty_get_all(self):
response = self.get(self.pools_path)
response_body = response.json
self.assertIsInstance(response_body, list)
self.assertEqual(0, len(response_body))
def test_create(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assertEqual(constants.PROTOCOL_HTTP, api_pool.get('protocol'))
self.assertEqual(constants.LB_ALGORITHM_ROUND_ROBIN,
api_pool.get('lb_algorithm'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_create(self):
api_pool = {'name': 'test1'}
self.post(self.pools_path, api_pool, status=400)
def test_duplicate_create(self):
pool = {'protocol': constants.PROTOCOL_HTTP,
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN}
self.post(self.pools_path, pool)
self.post(self.pools_path, pool, status=409)
def test_create_bad_protocol(self):
pool = {'protocol': 'STUPID_PROTOCOL',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN}
self.post(self.pools_path, pool, status=400)
def test_update(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(lb_id=self.lb.get('id'))
new_pool = {'name': 'new_name'}
self.put(self.pool_path.format(pool_id=api_pool.get('id')),
new_pool, status=202)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
response = self.get(self.pool_path.format(pool_id=api_pool.get('id')))
response_body = response.json
self.assertEqual('new_name', response_body.get('name'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_update(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
new_pool = {'enabled': 'one'}
self.put(self.pool_path.format(pool_id=api_pool.get('id')),
new_pool, status=400)
def test_delete(self):
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(lb_id=self.lb.get('id'))
api_pool['operating_status'] = constants.ONLINE
response = self.get(self.pool_path.format(
pool_id=api_pool.get('id')))
self.assertEqual(api_pool, response.json)
self.delete(self.pool_path.format(pool_id=api_pool.get('id')))
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_bad_delete(self):
self.delete(self.pool_path.format(
pool_id=uuidutils.generate_uuid()), status=404)
def test_create_with_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
session_persistence=sp)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
response = self.get(self.pool_path.format(
pool_id=api_pool.get('id')))
response_body = response.json
sess_p = response_body.get('session_persistence')
self.assertIsNotNone(sess_p)
self.assertEqual(constants.SESSION_PERSISTENCE_HTTP_COOKIE,
sess_p.get('type'))
self.assertEqual('test_cookie_name', sess_p.get('cookie_name'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_create_with_bad_session_persistence(self):
sp = {"type": "persistence_type",
"cookie_name": "test_cookie_name"}
pool = {'protocol': constants.PROTOCOL_HTTP,
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'session_persistence': sp}
self.post(self.pools_path, pool, status=400)
def test_add_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.put(self.pool_path.format(pool_id=api_pool.get('id')),
body={'session_persistence': sp})
api_pool = response.json
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assertEqual(sp, api_pool.get('session_persistence'))
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_update_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
session_persistence=sp)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.get(self.pool_path.format(
pool_id=api_pool.get('id')))
response_body = response.json
sess_p = response_body.get('session_persistence')
sess_p['cookie_name'] = 'new_test_cookie_name'
api_pool = self.put(self.pool_path.format(pool_id=api_pool.get('id')),
body={'session_persistence': sess_p}).json
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assertEqual(sess_p, api_pool.get('session_persistence'))
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_update_bad_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
session_persistence=sp)
self.set_lb_status(lb_id=self.lb.get('id'))
response = self.get(self.pool_path.format(
pool_id=api_pool.get('id')))
response_body = response.json
sess_p = response_body.get('session_persistence')
sess_p['type'] = 'persistence_type'
self.put(self.pool_path.format(pool_id=api_pool.get('id')),
body={'session_persistence': sess_p}, status=400)
def test_delete_with_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
session_persistence=sp)
self.set_lb_status(lb_id=self.lb.get('id'))
self.delete(self.pool_path.format(pool_id=api_pool.get('id')),
status=202)
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_delete_session_persistence(self):
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": "test_cookie_name"}
api_pool = self.create_pool(self.lb.get('id'),
self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
session_persistence=sp)
self.set_lb_status(lb_id=self.lb.get('id'))
sp = {'session_persistence': None}
api_pool = self.put(self.pool_path.format(pool_id=api_pool.get('id')),
body=sp, status=202).json
self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.PENDING_UPDATE,
constants.ONLINE)
self.assertEqual(None, api_pool.get('session_persistence'))
self.set_lb_status(self.lb.get('id'))
self.assert_correct_lb_status(self.lb.get('id'),
constants.ACTIVE,
constants.ONLINE)
self.assert_correct_listener_status(self.lb.get('id'),
self.listener.get('id'),
constants.ACTIVE, constants.ONLINE)
def test_create_when_lb_pending_update(self):
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.post(self.pools_path,
body={'protocol': constants.PROTOCOL_HTTP,
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN},
status=409)
def test_update_when_lb_pending_update(self):
pool = self.create_pool(self.lb.get('id'), self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.put(self.pool_path.format(pool_id=pool.get('id')),
body={'protocol': constants.PROTOCOL_HTTPS},
status=409)
def test_delete_when_lb_pending_update(self):
pool = self.create_pool(self.lb.get('id'), self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
body={'name': 'test_name_change'})
self.delete(self.pool_path.format(pool_id=pool.get('id')), status=409)
def test_create_when_lb_pending_delete(self):
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.post(self.pools_path,
body={'protocol': constants.PROTOCOL_HTTP,
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN},
status=409)
def test_update_when_lb_pending_delete(self):
pool = self.create_pool(self.lb.get('id'), self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.put(self.pool_path.format(pool_id=pool.get('id')),
body={'protocol': constants.PROTOCOL_HTTPS},
status=409)
def test_delete_when_lb_pending_delete(self):
pool = self.create_pool(self.lb.get('id'), self.listener.get('id'),
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN)
self.set_lb_status(self.lb.get('id'))
self.delete(self.LB_PATH.format(lb_id=self.lb.get('id')))
self.delete(self.pool_path.format(pool_id=pool.get('id')), status=409)

View File

View File

@ -12,9 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from oslo.db import options as db_options
from oslo.db.sqlalchemy import test_base
from octavia.common import constants
from octavia.db import api as db_api
from octavia.db import base_models
from octavia.db import models
@ -23,24 +26,27 @@ class OctaviaDBTestBase(test_base.DbTestCase):
def setUp(self):
super(OctaviaDBTestBase, self).setUp()
# NOTE(blogan): doing this for now because using the engine and
# session set up in the fixture for test_base.DbTestCase does not work
# with the API functional tests. Need to investigate more if this
# becomes a problem
cfg.CONF.register_opts(db_options.database_opts, 'database')
cfg.CONF.set_override('connection', 'sqlite://', group='database')
# needed for closure
engine = self.engine
base_models.BASE.metadata.create_all(bind=engine)
self._seed_lookup_tables()
engine = db_api.get_engine()
session = db_api.get_session()
base_models.BASE.metadata.create_all(engine)
self._seed_lookup_tables(session)
def unregister_models():
def clear_tables():
"""Unregister all data models."""
base_models.BASE.metadata.drop_all(bind=engine)
base_models.BASE.metadata.drop_all(engine)
self.addCleanup(unregister_models)
self.addCleanup(clear_tables)
self.session = self._get_session()
self.session = session
def _get_session(self):
return self.sessionmaker(bind=self.engine, expire_on_commit=True)
def _seed_lookup_tables(self):
session = self._get_session()
def _seed_lookup_tables(self, session):
self._seed_lookup_table(
session, constants.SUPPORTED_PROVISIONING_STATUSES,
models.ProvisioningStatus)

View File

@ -15,13 +15,14 @@
from octavia.common import constants
from octavia.common import data_models
from octavia.db import models
from octavia.tests.unit.db import base
from octavia.openstack.common import uuidutils
from octavia.tests.functional.db import base
class ModelTestMixin(object):
FAKE_UUID_1 = '0123456789012345678901234567890123456'
FAKE_UUID_2 = '1234567890123456789012345678901234567'
FAKE_UUID_1 = uuidutils.generate_uuid()
FAKE_UUID_2 = uuidutils.generate_uuid()
def _insert(self, session, model_cls, model_kwargs):
with session.begin():
@ -69,9 +70,7 @@ class ModelTestMixin(object):
return self._insert(session, models.SessionPersistence, kwargs)
def create_health_monitor(self, session, pool_id, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'pool_id': pool_id,
kwargs = {'pool_id': pool_id,
'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1,
'timeout': 1,
@ -115,7 +114,7 @@ class ModelTestMixin(object):
def create_amphora(self, session, **overrides):
kwargs = {'id': self.FAKE_UUID_1,
'host_id': self.FAKE_UUID_1,
'status': constants.ONLINE}
'status': constants.ACTIVE}
kwargs.update(overrides)
return self._insert(session, models.Amphora, kwargs)
@ -361,26 +360,27 @@ class HealthMonitorModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_update(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
health_monitor_id = health_monitor.id
health_monitor.name = 'test1'
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor_id).first()
models.HealthMonitor).filter_by(
pool_id=health_monitor.pool_id).first()
self.assertEqual('test1', new_health_monitor.name)
def test_delete(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
health_monitor_id = health_monitor.id
with self.session.begin():
self.session.delete(health_monitor)
self.session.flush()
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor_id).first()
models.HealthMonitor).filter_by(
pool_id=health_monitor.pool_id).first()
self.assertIsNone(new_health_monitor)
def test_pool_relationship(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor.id).first()
models.HealthMonitor).filter_by(
pool_id=health_monitor.pool_id).first()
self.assertIsNotNone(new_health_monitor.pool)
self.assertTrue(isinstance(new_health_monitor.pool, models.Pool))
@ -478,10 +478,10 @@ class SNIModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_update(self):
sni = self.create_sni(self.session, listener_id=self.listener.id)
sni.listener_id = self.FAKE_UUID_2
sni.tls_container_id = self.FAKE_UUID_2
new_sni = self.session.query(
models.SNI).filter_by(listener_id=self.FAKE_UUID_2).first()
self.assertEqual(self.FAKE_UUID_2, new_sni.listener_id)
models.SNI).filter_by(listener_id=self.FAKE_UUID_1).first()
self.assertEqual(self.FAKE_UUID_2, new_sni.tls_container_id)
def test_delete(self):
sni = self.create_sni(self.session, listener_id=self.listener.id)
@ -592,7 +592,7 @@ class DataModelConversionTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_health_monitor_tree(self):
hm_db = self.session.query(models.HealthMonitor).filter_by(
id=self.hm.id).first()
pool_id=self.hm.pool_id).first()
self.check_health_monitor(hm_db.to_data_model())
def test_member_tree(self):
@ -735,8 +735,6 @@ class DataModelConversionTest(base.OctaviaDBTestBase, ModelTestMixin):
self.assertEqual(constants.SESSION_PERSISTENCE_HTTP_COOKIE, sp.type)
def check_health_monitor_data_model(self, hm):
self.assertEqual(self.FAKE_UUID_1, hm.tenant_id)
self.assertEqual(self.FAKE_UUID_1, hm.id)
self.assertEqual(constants.HEALTH_MONITOR_HTTP, hm.type)
self.assertEqual(1, hm.delay)
self.assertEqual(1, hm.timeout)

View File

@ -14,10 +14,9 @@
from octavia.common import constants
from octavia.common import data_models as models
from octavia.common import exceptions
from octavia.db import repositories as repo
from octavia.openstack.common import uuidutils
from octavia.tests.unit.db import base
from octavia.tests.functional.db import base
class BaseRepositoryTest(base.OctaviaDBTestBase):
@ -54,6 +53,205 @@ class BaseRepositoryTest(base.OctaviaDBTestBase):
self.assertIsInstance(member_list, list)
class AllRepositoriesTest(base.OctaviaDBTestBase):
def setUp(self):
super(AllRepositoriesTest, self).setUp()
self.repos = repo.Repositories()
self.listener = self.repos.listener.create(
self.session, protocol=constants.PROTOCOL_HTTP, protocol_port=80,
enabled=True, provisioning_status=constants.ACTIVE,
operating_status=constants.ONLINE)
def test_all_repos_has_correct_repos(self):
repo_attr_names = ('load_balancer', 'vip', 'health_monitor',
'session_persistence', 'pool', 'member', 'listener',
'listener_stats', 'amphora', 'sni')
for repo_attr in repo_attr_names:
single_repo = getattr(self.repos, repo_attr, None)
message = ("Class Repositories should have %s instance"
" variable.") % repo_attr
self.assertIsNotNone(single_repo, message=message)
message = (("instance variable, %(repo_name)s, of class "
"Repositories should be an instance of %(base)s") %
{'repo_name': repo_attr,
'base': repo.BaseRepository.__name__})
self.assertIsInstance(single_repo, repo.BaseRepository,
msg=message)
for attr in vars(self.repos):
if attr.startswith('_') or attr in repo_attr_names:
continue
possible_repo = getattr(self.repos, attr, None)
message = ('Class Repositories is not expected to have %s instance'
' variable as a repository.' % attr)
self.assertFalse(isinstance(possible_repo, repo.BaseRepository),
msg=message)
def test_create_load_balancer_and_vip(self):
lb = {'name': 'test1', 'description': 'desc1', 'enabled': True,
'provisioning_status': constants.PENDING_UPDATE,
'operating_status': constants.OFFLINE}
vip = {'floating_ip_id': uuidutils.generate_uuid(),
'floating_ip_network_id': uuidutils.generate_uuid(),
'ip_address': '10.0.0.1',
'net_port_id': uuidutils.generate_uuid(),
'subnet_id': uuidutils.generate_uuid()}
lb_dm = self.repos.create_load_balancer_and_vip(self.session, lb, vip)
lb_dm_dict = lb_dm.to_dict()
del lb_dm_dict['vip']
del lb_dm_dict['listeners']
del lb_dm_dict['amphorae']
del lb_dm_dict['tenant_id']
self.assertEqual(lb, lb_dm_dict)
vip_dm_dict = lb_dm.vip.to_dict()
vip_dm_dict['load_balancer_id'] = lb_dm.id
del vip_dm_dict['load_balancer']
self.assertEqual(vip, vip_dm_dict)
def test_create_pool_on_listener_without_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool)
pool_dm_dict = pool_dm.to_dict()
del pool_dm_dict['members']
del pool_dm_dict['health_monitor']
del pool_dm_dict['session_persistence']
del pool_dm_dict['listener']
del pool_dm_dict['tenant_id']
self.assertEqual(pool, pool_dm_dict)
new_listener = self.repos.listener.get(self.session,
id=self.listener.id)
self.assertEqual(pool_dm.id, new_listener.default_pool_id)
def test_create_pool_on_listener_with_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'cookie_monster'}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool, sp_dict=sp)
pool_dm_dict = pool_dm.to_dict()
del pool_dm_dict['members']
del pool_dm_dict['health_monitor']
del pool_dm_dict['session_persistence']
del pool_dm_dict['listener']
del pool_dm_dict['tenant_id']
self.assertEqual(pool, pool_dm_dict)
sp_dm_dict = pool_dm.session_persistence.to_dict()
del sp_dm_dict['pool']
sp['pool_id'] = pool_dm.id
self.assertEqual(sp, sp_dm_dict)
new_listener = self.repos.listener.get(self.session,
id=self.listener.id)
self.assertEqual(pool_dm.id, new_listener.default_pool_id)
new_sp = self.repos.session_persistence.get(self.session,
pool_id=pool_dm.id)
self.assertIsNotNone(new_sp)
def test_update_pool_on_listener_without_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id, pool)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
new_pool_dm = self.repos.update_pool_on_listener(
self.session, pool_dm.id, update_pool, None)
pool_dm_dict = new_pool_dm.to_dict()
del pool_dm_dict['members']
del pool_dm_dict['health_monitor']
del pool_dm_dict['session_persistence']
del pool_dm_dict['listener']
del pool_dm_dict['tenant_id']
pool.update(update_pool)
self.assertEqual(pool, pool_dm_dict)
self.assertIsNone(new_pool_dm.session_persistence)
def test_update_pool_on_listener_with_existing_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'cookie_monster'}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool, sp_dict=sp)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
update_sp = {'type': constants.SESSION_PERSISTENCE_SOURCE_IP}
new_pool_dm = self.repos.update_pool_on_listener(
self.session, pool_dm.id, update_pool, update_sp)
pool_dm_dict = new_pool_dm.to_dict()
del pool_dm_dict['members']
del pool_dm_dict['health_monitor']
del pool_dm_dict['session_persistence']
del pool_dm_dict['listener']
del pool_dm_dict['tenant_id']
pool.update(update_pool)
self.assertEqual(pool, pool_dm_dict)
sp_dm_dict = new_pool_dm.session_persistence.to_dict()
del sp_dm_dict['pool']
sp['pool_id'] = pool_dm.id
sp.update(update_sp)
self.assertEqual(sp, sp_dm_dict)
def test_update_pool_on_listener_with_nonexisting_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
update_sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'monster_cookie'}
new_pool_dm = self.repos.update_pool_on_listener(
self.session, pool_dm.id, update_pool, update_sp)
sp_dm_dict = new_pool_dm.session_persistence.to_dict()
del sp_dm_dict['pool']
update_sp['pool_id'] = pool_dm.id
update_sp.update(update_sp)
self.assertEqual(update_sp, sp_dm_dict)
def test_update_pool_on_listener_with_nonexisting_sp_delete_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
new_pool_dm = self.repos.update_pool_on_listener(
self.session, pool_dm.id, update_pool, None)
self.assertIsNone(new_pool_dm.session_persistence)
def test_update_pool_on_listener_with_existing_sp_delete_sp(self):
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
'description': 'desc1',
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'enabled': True, 'operating_status': constants.ONLINE}
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'cookie_monster'}
pool_dm = self.repos.create_pool_on_listener(self.session,
self.listener.id,
pool, sp_dict=sp)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
new_pool_dm = self.repos.update_pool_on_listener(
self.session, pool_dm.id, update_pool, None)
self.assertIsNone(new_pool_dm.session_persistence)
class PoolRepositoryTest(BaseRepositoryTest):
def create_pool(self, pool_id, tenant_id):
@ -106,8 +304,7 @@ class PoolRepositoryTest(BaseRepositoryTest):
pool = self.create_pool(pool_id=self.FAKE_UUID_1,
tenant_id=self.FAKE_UUID_2)
self.pool_repo.delete(self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
def test_delete_with_member(self):
pool = self.create_pool(pool_id=self.FAKE_UUID_1,
@ -122,16 +319,13 @@ class PoolRepositoryTest(BaseRepositoryTest):
self.assertEqual(1, len(new_pool.members))
self.assertEqual(member, new_pool.members[0])
self.pool_repo.delete(self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.member_repo.get,
self.session, id=member.id)
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
self.assertIsNone(self.member_repo.get(self.session, id=member.id))
def test_delete_with_health_monitor(self):
pool = self.create_pool(pool_id=self.FAKE_UUID_1,
tenant_id=self.FAKE_UUID_2)
hm = self.hm_repo.create(self.session, id=self.FAKE_UUID_3,
tenant_id=self.FAKE_UUID_2, pool_id=pool.id,
hm = self.hm_repo.create(self.session, pool_id=pool.id,
type=constants.HEALTH_MONITOR_HTTP,
delay=1, timeout=1, fall_threshold=1,
rise_threshold=1, enabled=True)
@ -139,10 +333,8 @@ class PoolRepositoryTest(BaseRepositoryTest):
self.assertEqual(pool, new_pool)
self.assertEqual(hm, new_pool.health_monitor)
self.pool_repo.delete(self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.hm_repo.get, self.session,
pool_id=hm.pool_id)
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
self.assertIsNone(self.hm_repo.get(self.session, pool_id=hm.pool_id))
def test_delete_with_session_persistence(self):
pool = self.create_pool(pool_id=self.FAKE_UUID_1,
@ -155,16 +347,13 @@ class PoolRepositoryTest(BaseRepositoryTest):
self.assertEqual(pool, new_pool)
self.assertEqual(sp, new_pool.session_persistence)
self.pool_repo.delete(self.session, id=new_pool.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.sp_repo.get, self.session,
pool_id=sp.pool_id)
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
self.assertIsNone(self.sp_repo.get(self.session, pool_id=sp.pool_id))
def test_delete_with_all_children(self):
pool = self.create_pool(pool_id=self.FAKE_UUID_1,
tenant_id=self.FAKE_UUID_2)
hm = self.hm_repo.create(self.session, id=self.FAKE_UUID_1,
tenant_id=self.FAKE_UUID_2, pool_id=pool.id,
hm = self.hm_repo.create(self.session, pool_id=pool.id,
type=constants.HEALTH_MONITOR_HTTP,
delay=1, timeout=1, fall_threshold=1,
rise_threshold=1, enabled=True)
@ -187,14 +376,10 @@ class PoolRepositoryTest(BaseRepositoryTest):
self.assertEqual(hm, new_pool.health_monitor)
self.assertEqual(sp, new_pool.session_persistence)
self.pool_repo.delete(self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertRaises(exceptions.NotFound, self.member_repo.get,
self.session, id=member.id)
self.assertRaises(exceptions.NotFound, self.hm_repo.get, self.session,
pool_id=hm.pool_id)
self.assertRaises(exceptions.NotFound, self.sp_repo.get, self.session,
pool_id=sp.pool_id)
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
self.assertIsNone(self.member_repo.get(self.session, id=member.id))
self.assertIsNone(self.hm_repo.get(self.session, pool_id=hm.pool_id))
self.assertIsNone(self.sp_repo.get(self.session, pool_id=sp.pool_id))
class MemberRepositoryTest(BaseRepositoryTest):
@ -261,8 +446,7 @@ class MemberRepositoryTest(BaseRepositoryTest):
member = self.create_member(self.FAKE_UUID_1, self.FAKE_UUID_2,
self.pool.id, "10.0.0.1")
self.member_repo.delete(self.session, id=member.id)
self.assertRaises(exceptions.NotFound, self.member_repo.get,
self.session, id=member.id)
self.assertIsNone(self.member_repo.get(self.session, id=member.id))
new_pool = self.pool_repo.get(self.session, id=self.pool.id)
self.assertIsNotNone(new_pool)
self.assertEqual(0, len(new_pool.members))
@ -311,8 +495,8 @@ class SessionPersistenceRepositoryTest(BaseRepositoryTest):
def test_delete(self):
sp = self.create_session_persistence(self.pool.id)
self.sp_repo.delete(self.session, pool_id=sp.pool_id)
self.assertRaises(exceptions.NotFound, self.member_repo.get,
self.session, pool_id=sp.pool_id)
self.assertIsNone(self.member_repo.get(self.session,
pool_id=sp.pool_id))
new_pool = self.pool_repo.get(self.session, id=self.pool.id)
self.assertIsNotNone(new_pool)
self.assertIsNone(new_pool.session_persistence)
@ -380,8 +564,7 @@ class ListenerRepositoryTest(BaseRepositoryTest):
def test_delete(self):
listener = self.create_listener(self.FAKE_UUID_1, 80)
self.listener_repo.delete(self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
def test_delete_with_sni(self):
listener = self.create_listener(self.FAKE_UUID_1, 80)
@ -391,10 +574,9 @@ class ListenerRepositoryTest(BaseRepositoryTest):
self.assertIsNotNone(new_listener)
self.assertEqual(sni, new_listener.sni_containers[0])
self.listener_repo.delete(self.session, id=new_listener.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.sni_repo.get, self.session,
listener_id=listener.id)
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
self.assertIsNone(self.sni_repo.get(self.session,
listener_id=listener.id))
def test_delete_with_stats(self):
listener = self.create_listener(self.FAKE_UUID_1, 80)
@ -405,10 +587,9 @@ class ListenerRepositoryTest(BaseRepositoryTest):
self.assertIsNotNone(new_listener)
self.assertEqual(stats, new_listener.stats)
self.listener_repo.delete(self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.listener_stats_repo.get,
self.session, listener_id=listener.id)
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
self.assertIsNone(self.listener_stats_repo.get(
self.session, listener_id=listener.id))
def test_delete_with_pool(self):
pool = self.pool_repo.create(
@ -423,10 +604,8 @@ class ListenerRepositoryTest(BaseRepositoryTest):
self.assertIsNotNone(new_listener)
self.assertEqual(pool, new_listener.default_pool)
self.listener_repo.delete(self.session, id=new_listener.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
def test_delete_with_all_children(self):
pool = self.pool_repo.create(
@ -448,14 +627,12 @@ class ListenerRepositoryTest(BaseRepositoryTest):
self.assertEqual(sni, new_listener.sni_containers[0])
self.assertEqual(stats, new_listener.stats)
self.listener_repo.delete(self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertRaises(exceptions.NotFound, self.sni_repo.get, self.session,
listener_id=listener.id)
self.assertRaises(exceptions.NotFound, self.listener_stats_repo.get,
self.session, listener_id=sni.listener_id)
self.assertRaises(exceptions.NotFound, self.pool_repo.get,
self.session, id=pool.id)
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
self.assertIsNone(self.sni_repo.get(self.session,
listener_id=listener.id))
self.assertIsNone(self.listener_stats_repo.get(
self.session, listener_id=sni.listener_id))
self.assertIsNone(self.pool_repo.get(self.session, id=pool.id))
class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
@ -506,8 +683,8 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
stats = self.create_listener_stats(self.listener.id)
self.listener_stats_repo.delete(self.session,
listener_id=stats.listener_id)
self.assertRaises(exceptions.NotFound, self.listener_stats_repo.get,
self.session, listener_id=stats.listener_id)
self.assertIsNone(self.listener_stats_repo.get(
self.session, listener_id=stats.listener_id))
new_listener = self.listener_repo.get(self.session,
id=self.listener.id)
self.assertIsNotNone(new_listener)
@ -527,8 +704,7 @@ class HealthMonitorRepositoryTest(BaseRepositoryTest):
def create_health_monitor(self, pool_id):
health_monitor = self.hm_repo.create(
self.session, id=self.FAKE_UUID_1, tenant_id=self.FAKE_UUID_2,
type=constants.HEALTH_MONITOR_HTTP, pool_id=pool_id,
self.session, type=constants.HEALTH_MONITOR_HTTP, pool_id=pool_id,
delay=1, timeout=1, fall_threshold=1, rise_threshold=1,
http_method="POST", url_path="http://localhost:80/index.php",
expected_codes="200", enabled=True)
@ -564,8 +740,7 @@ class HealthMonitorRepositoryTest(BaseRepositoryTest):
def test_delete(self):
hm = self.create_health_monitor(self.pool.id)
self.hm_repo.delete(self.session, pool_id=hm.pool_id)
self.assertRaises(exceptions.NotFound, self.hm_repo.get, self.session,
pool_id=hm.pool_id)
self.assertIsNone(self.hm_repo.get(self.session, pool_id=hm.pool_id))
new_pool = self.pool_repo.get(self.session, id=self.pool.id)
self.assertIsNotNone(new_pool)
self.assertIsNone(new_pool.health_monitor)
@ -617,8 +792,7 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
def test_delete(self):
lb = self.create_loadbalancer(self.FAKE_UUID_1)
self.lb_repo.delete(self.session, id=lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
def test_delete_with_amphora(self):
lb = self.create_loadbalancer(self.FAKE_UUID_1)
@ -631,8 +805,7 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertEqual(1, len(new_lb.amphorae))
self.assertEqual(amphora, new_lb.amphorae[0])
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
new_amphora = self.amphora_repo.get(self.session, id=amphora.id)
self.assertIsNotNone(new_amphora)
self.assertIsNone(new_amphora.load_balancer_id)
@ -653,8 +826,7 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertIn(amphora_1, new_lb.amphorae)
self.assertIn(amphora_2, new_lb.amphorae)
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
new_amphora_1 = self.amphora_repo.get(self.session, id=amphora_1.id)
new_amphora_2 = self.amphora_repo.get(self.session, id=amphora_2.id)
self.assertIsNotNone(new_amphora_1)
@ -671,10 +843,9 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertIsNotNone(new_lb.vip)
self.assertEqual(vip, new_lb.vip)
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertRaises(exceptions.NotFound, self.vip_repo.get, self.session,
load_balancer_id=lb.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
self.assertIsNone(self.vip_repo.get(self.session,
load_balancer_id=lb.id))
def test_delete_with_listener(self):
lb = self.create_loadbalancer(self.FAKE_UUID_1)
@ -690,10 +861,8 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertEqual(1, len(new_lb.listeners))
self.assertEqual(listener, new_lb.listeners[0])
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
def test_delete_with_many_listeners(self):
lb = self.create_loadbalancer(self.FAKE_UUID_1)
@ -717,12 +886,11 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertIn(listener_1, new_lb.listeners)
self.assertIn(listener_2, new_lb.listeners)
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener_1.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener_2.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
self.assertIsNone(self.listener_repo.get(self.session,
id=listener_1.id))
self.assertIsNone(self.listener_repo.get(self.session,
id=listener_2.id))
def test_delete_with_all_children(self):
lb = self.create_loadbalancer(self.FAKE_UUID_1)
@ -748,15 +916,35 @@ class LoadBalancerRepositoryTest(BaseRepositoryTest):
self.assertEqual(amphora, new_lb.amphorae[0])
self.assertEqual(listener, new_lb.listeners[0])
self.lb_repo.delete(self.session, id=new_lb.id)
self.assertRaises(exceptions.NotFound, self.lb_repo.get, self.session,
id=lb.id)
self.assertIsNone(self.lb_repo.get(self.session, id=lb.id))
new_amphora = self.amphora_repo.get(self.session, id=amphora.id)
self.assertIsNotNone(new_amphora)
self.assertIsNone(new_amphora.load_balancer_id)
self.assertRaises(exceptions.NotFound, self.vip_repo.get, self.session,
load_balancer_id=lb.id)
self.assertRaises(exceptions.NotFound, self.listener_repo.get,
self.session, id=listener.id)
self.assertIsNone(self.vip_repo.get(self.session,
load_balancer_id=lb.id))
self.assertIsNone(self.listener_repo.get(self.session, id=listener.id))
def test_test_and_set_provisioning_status_immutable(self):
lb_id = uuidutils.generate_uuid()
self.lb_repo.create(self.session, id=lb_id,
provisioning_status=constants.PENDING_CREATE,
operating_status=constants.OFFLINE,
enabled=True)
self.assertFalse(self.lb_repo.test_and_set_provisioning_status(
self.session, lb_id, constants.PENDING_UPDATE))
lb = self.lb_repo.get(self.session, id=lb_id)
self.assertEqual(constants.PENDING_CREATE, lb.provisioning_status)
def test_test_and_set_provisioning_status_mutable(self):
lb_id = uuidutils.generate_uuid()
self.lb_repo.create(self.session, id=lb_id,
provisioning_status=constants.ACTIVE,
operating_status=constants.OFFLINE,
enabled=True)
self.lb_repo.test_and_set_provisioning_status(
self.session, lb_id, constants.PENDING_UPDATE)
lb = self.lb_repo.get(self.session, id=lb_id)
self.assertEqual(constants.PENDING_UPDATE, lb.provisioning_status)
class VipRepositoryTest(BaseRepositoryTest):
@ -799,8 +987,8 @@ class VipRepositoryTest(BaseRepositoryTest):
vip = self.create_vip(self.lb.id)
self.vip_repo.delete(self.session,
load_balancer_id=vip.load_balancer_id)
self.assertRaises(exceptions.NotFound, self.vip_repo.get, self.session,
load_balancer_id=vip.load_balancer_id)
self.assertIsNone(self.vip_repo.get(
self.session, load_balancer_id=vip.load_balancer_id))
new_lb = self.lb_repo.get(self.session, id=self.lb.id)
self.assertIsNotNone(new_lb)
self.assertIsNone(new_lb.vip)
@ -848,8 +1036,8 @@ class SNIRepositoryTest(BaseRepositoryTest):
def test_delete(self):
sni = self.create_sni(self.listener.id)
self.sni_repo.delete(self.session, listener_id=sni.listener_id)
self.assertRaises(exceptions.NotFound, self.sni_repo.get, self.session,
listener_id=sni.listener_id)
self.assertIsNone(self.sni_repo.get(self.session,
listener_id=sni.listener_id))
new_listener = self.listener_repo.get(self.session,
id=self.listener.id)
self.assertIsNotNone(new_listener)
@ -895,8 +1083,7 @@ class AmphoraRepositoryTest(BaseRepositoryTest):
def test_delete(self):
amphora = self.create_amphora(self.FAKE_UUID_1)
self.amphora_repo.delete(self.session, id=amphora.id)
self.assertRaises(exceptions.NotFound, self.amphora_repo.get,
self.session, id=amphora.id)
self.assertIsNone(self.amphora_repo.get(self.session, id=amphora.id))
def test_associate_amphora_load_balancer(self):
amphora = self.create_amphora(self.FAKE_UUID_1)
@ -910,7 +1097,6 @@ class AmphoraRepositoryTest(BaseRepositoryTest):
amphora = self.create_amphora(self.FAKE_UUID_1)
self.amphora_repo.associate(self.session, self.lb.id, amphora.id)
self.amphora_repo.delete(self.session, id=amphora.id)
self.assertRaises(exceptions.NotFound, self.amphora_repo.get,
self.session, id=amphora.id)
self.assertIsNone(self.amphora_repo.get(self.session, id=amphora.id))
new_lb = self.lb_repo.get(self.session, id=self.lb.id)
self.assertEqual(0, len(new_lb.amphorae))

View File

View File

View File

@ -0,0 +1,225 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from octavia.api.v1.types import base as base_type
from octavia.common import constants
from octavia.openstack.common import uuidutils
from octavia.tests.unit import base
def build_body(mandatory_fields, extra_attributes):
body = {}
for key in mandatory_fields:
body[key] = mandatory_fields[key]
for key in extra_attributes:
body[key] = extra_attributes[key]
return body
class BaseTypesTest(base.TestCase):
_type = base_type.BaseType
_mandatory_fields = {}
class BaseTestUuid(base.TestCase):
def assert_uuid_attr(self, attr):
kwargs = {attr: uuidutils.generate_uuid()}
self._type(**kwargs)
def assert_uuid_attr_fail_with_integer(self, attr):
kwargs = {attr: 1}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
def assert_uuid_attr_fail_with_short_str(self, attr):
kwargs = {attr: '12345'}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
def assert_uuid_attr_fail_with_shorter_than_uuid(self, attr):
kwargs = {attr: uuidutils.generate_uuid()[1:]}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
def assert_uuid_attr_fail_with_longer_than_uuid(self, attr):
kwargs = {attr: uuidutils.generate_uuid() + "0"}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
class BaseTestString(base.TestCase):
def _default_min_max_lengths(self, min_length=None, max_length=None):
if max_length is None:
if min_length is None:
max_length = 255
min_length = 2
else:
max_length = min_length + 1
else:
if min_length is None:
min_length = max_length - 1
return min_length, max_length
def assert_string_attr(self, attr, min_length=None, max_length=None):
min_length, max_length = self._default_min_max_lengths(min_length,
max_length)
string_val = 'a' * (max_length - 1)
kwargs = {attr: string_val}
self._type(**kwargs)
def assert_string_attr_min_length(self, attr, min_length):
min_length, max_length = self._default_min_max_lengths(min_length)
string_val = 'a' * (min_length - 1)
kwargs = {attr: string_val}
# No point in testing if min_length is <= 0
if min_length > 0:
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
def assert_string_attr_max_length(self, attr, max_length=None):
min_length, max_length = self._default_min_max_lengths(max_length)
string_val = 'a' * (max_length + 1)
kwargs = {attr: string_val}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
class BaseTestBool(base.TestCase):
def assert_bool_attr(self, attr):
kwargs = {attr: True}
self.assertIsNotNone(self._type(**kwargs))
kwargs = {attr: False}
self.assertIsNotNone(self._type(**kwargs))
def assert_bool_attr_non_bool(self, attr):
kwargs = {attr: 'test'}
self.assertRaises(exc.InvalidInput, self._type, **kwargs)
class TestIdMixin(BaseTestUuid):
id_attr = 'id'
def test_id(self):
self.assert_uuid_attr(self.id_attr)
self.assert_uuid_attr_fail_with_integer(self.id_attr)
self.assert_uuid_attr_fail_with_short_str(self.id_attr)
self.assert_uuid_attr_fail_with_shorter_than_uuid(self.id_attr)
self.assert_uuid_attr_fail_with_longer_than_uuid(self.id_attr)
def test_id_readonly(self):
body = build_body(self._mandatory_fields,
{self.id_attr: uuidutils.generate_uuid()})
self.assertRaises(exc.InvalidInput, wsme_json.fromjson,
self._type, body)
class TestTenantIdMixin(BaseTestUuid):
tenant_id_attr = 'tenant_id'
def test_tenant_id(self):
self.assert_uuid_attr(self.tenant_id_attr)
self.assert_uuid_attr_fail_with_integer(self.tenant_id_attr)
self.assert_uuid_attr_fail_with_short_str(self.tenant_id_attr)
self.assert_uuid_attr_fail_with_shorter_than_uuid(self.tenant_id_attr)
self.assert_uuid_attr_fail_with_longer_than_uuid(self.tenant_id_attr)
def test_tenant_id_readonly(self):
body = build_body(self._mandatory_fields,
{self.tenant_id_attr: uuidutils.generate_uuid()})
self.assertRaises(exc.InvalidInput, wsme_json.fromjson,
self._type, body)
class TestNameMixin(BaseTestString):
name_attr = 'name'
def test_name(self):
self.assert_string_attr(self.name_attr, min_length=0, max_length=255)
self.assert_string_attr_min_length(self.name_attr, 0)
self.assert_string_attr_max_length(self.name_attr, 255)
def test_editable_name(self):
name = "Name"
body = build_body(self._mandatory_fields, {self.name_attr: name})
type_instance = wsme_json.fromjson(self._type, body)
self.assertEqual(name, type_instance.name)
class TestDescriptionMixin(BaseTestString):
description_attr = 'description'
def test_description(self):
self.assert_string_attr(self.description_attr, min_length=0,
max_length=255)
self.assert_string_attr_min_length(self.description_attr, 0)
self.assert_string_attr_max_length(self.description_attr, 255)
def test_editable_description(self):
description = "Description"
body = build_body(self._mandatory_fields,
{self.description_attr: description})
type_instance = wsme_json.fromjson(self._type, body)
self.assertEqual(description, type_instance.description)
class TestEnabledMixin(BaseTestBool):
enabled_attr = 'enabled'
def test_enabled(self):
self.assert_bool_attr(self.enabled_attr)
self.assert_bool_attr_non_bool(self.enabled_attr)
def test_default_enabled_true(self):
body = build_body(self._mandatory_fields, {})
type_instance = wsme_json.fromjson(self._type, body)
self.assertTrue(type_instance.enabled)
def test_editable_enabled(self):
body = build_body(self._mandatory_fields, {"enabled": False})
type_instance = wsme_json.fromjson(self._type, body)
self.assertFalse(type_instance.enabled)
class TestProvisioningStatusMixin(BaseTestString):
provisioning_attr = 'provisioning_status'
def test_provisioning_status(self):
self.assert_string_attr(self.provisioning_attr, min_length=0,
max_length=16)
self.assert_string_attr_min_length(self.provisioning_attr, 0)
self.assert_string_attr_max_length(self.provisioning_attr, 16)
def test_provisioning_status_readonly(self):
status = constants.ACTIVE
body = build_body(self._mandatory_fields,
{self.provisioning_attr: status})
self.assertRaises(exc.InvalidInput, wsme_json.fromjson,
self._type, body)
class TestOperatingStatusMixin(BaseTestString):
operating_attr = 'operating_status'
def test_operating_status(self):
self.assert_string_attr(self.operating_attr, min_length=0,
max_length=16)
self.assert_string_attr_min_length(self.operating_attr, 0)
self.assert_string_attr_max_length(self.operating_attr, 16)
def test_operating_status_readonly(self):
status = constants.ONLINE
body = build_body(self._mandatory_fields,
{self.operating_attr: status})
self.assertRaises(exc.InvalidInput, wsme_json.fromjson,
self._type, body)

View File

@ -0,0 +1,120 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from wsme import types as wsme_types
from octavia.api.v1.types import health_monitor as hm_type
from octavia.common import constants
from octavia.tests.unit.api.v1.types import base
class TestHealthMonitor(object):
_type = None
def test_invalid_type(self):
body = {"type": 10, "delay": 1, "timeout": 1, "fall_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_delay(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": "one",
"timeout": 1, "fall_threshold": 1}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_timeout(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": "one", "fall_threshold": 1}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_fall_threshold(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": "one"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_rise_threshold(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1, "rise_threshold": "one"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_http_method(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1, "http_method": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_url_path(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1, "url_path": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_expected_codes(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1, "expected_codes": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestHealthMonitorPOST(base.BaseTypesTest, TestHealthMonitor):
_type = hm_type.HealthMonitorPOST
def test_health_monitor(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1, "rise_threshold": 1}
hm = wsme_json.fromjson(self._type, body)
self.assertTrue(hm.enabled)
def test_type_mandatory(self):
body = {"delay": 80, "timeout": 1, "fall_threshold": 1,
"rise_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_delay_mandatory(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "timeout": 1,
"fall_threshold": 1, "rise_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_timeout_mandatory(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"fall_threshold": 1, "rise_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_fall_threshold_mandatory(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "rise_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_rise_threshold_mandatory(self):
body = {"type": constants.HEALTH_MONITOR_HTTP, "delay": 1,
"timeout": 1, "fall_threshold": 1}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestHealthMonitorPUT(base.BaseTypesTest, TestHealthMonitor):
_type = hm_type.HealthMonitorPUT
def test_health_monitor(self):
body = {"protocol": constants.PROTOCOL_HTTPS}
hm = wsme_json.fromjson(self._type, body)
self.assertEqual(wsme_types.Unset, hm.enabled)

View File

@ -0,0 +1,90 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from wsme import types as wsme_types
from octavia.api.v1.types import listener as lis_type
from octavia.common import constants
from octavia.tests.unit.api.v1.types import base
class TestListener(object):
_type = None
def test_invalid_name(self):
body = {"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80,
"name": 0}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_description(self):
body = {"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80,
"description": 0}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_enabled(self):
body = {"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80,
"enabled": "true"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_protocol(self):
body = {"protocol": 10, "protocol_port": 80}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_protocol_port(self):
body = {"protocol": constants.PROTOCOL_HTTP, "protocol_port": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_connection_limit(self):
body = {"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80,
"connection_limit": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
class TestListenerPOST(base.BaseTypesTest, TestListener):
_type = lis_type.ListenerPOST
def test_listener(self):
body = {"name": "test", "description": "test", "connection_limit": 10,
"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80}
listener = wsme_json.fromjson(self._type, body)
self.assertTrue(listener.enabled)
def test_protocol_mandatory(self):
body = {"protocol_port": 80}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_protocol_port_mandatory(self):
body = {"protocol": constants.PROTOCOL_HTTP}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestListenerPUT(base.BaseTypesTest, TestListener):
_type = lis_type.ListenerPUT
def test_listener(self):
body = {"name": "test", "description": "test", "connection_limit": 10,
"protocol": constants.PROTOCOL_HTTP, "protocol_port": 80}
listener = wsme_json.fromjson(self._type, body)
self.assertEqual(wsme_types.Unset, listener.enabled)

View File

@ -0,0 +1,115 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from wsme import types as wsme_types
from octavia.api.v1.types import load_balancer as lb_type
from octavia.openstack.common import uuidutils
from octavia.tests.unit.api.v1.types import base
class TestLoadBalancer(object):
_type = None
def test_load_balancer(self):
body = {"name": "test_name", "description": "test_description",
"vip": {}}
lb = wsme_json.fromjson(self._type, body)
self.assertTrue(lb.enabled)
def test_invalid_name(self):
body = {"name": 0}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_name_length(self):
body = {"name": "x" * 256}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_description(self):
body = {"description": 0}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_description_length(self):
body = {"name": "x" * 256}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_enabled(self):
body = {"enabled": "true"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestLoadBalancerPOST(base.BaseTypesTest, TestLoadBalancer):
_type = lb_type.LoadBalancerPOST
def test_vip_mandatory(self):
body = {"name": "test"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestLoadBalancerPUT(base.BaseTypesTest, TestLoadBalancer):
_type = lb_type.LoadBalancerPUT
def test_load_balancer(self):
body = {"name": "test_name", "description": "test_description"}
lb = wsme_json.fromjson(self._type, body)
self.assertEqual(wsme_types.Unset, lb.enabled)
class TestVip(base.BaseTypesTest):
_type = lb_type.VIP
def test_vip(self):
body = {"vip": {"ip_address": "10.0.0.1",
"net_port_id": uuidutils.generate_uuid(),
"subnet_id": uuidutils.generate_uuid(),
"floating_ip_id": uuidutils.generate_uuid(),
"floating_ip_network_id": uuidutils.generate_uuid()}}
wsme_json.fromjson(self._type, body)
def test_invalid_ip_address(self):
body = {"ip_address": uuidutils.generate_uuid()}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_net_port_id(self):
body = {"net_port_id": "invalid_uuid"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_subnet_id(self):
body = {"subnet_id": "invalid_uuid"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_floating_ip_id(self):
body = {"floating_ip_id": "invalid_uuid"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_floating_network_ip_id(self):
body = {"floating_ip_network_id": "invalid_uuid"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)

View File

@ -0,0 +1,87 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from wsme import types as wsme_types
from octavia.api.v1.types import member as member_type
from octavia.tests.unit.api.v1.types import base
class TestMemberPOST(base.BaseTypesTest):
_type = member_type.MemberPOST
def test_member(self):
body = {"ip_address": "10.0.0.1", "protocol_port": 80}
member = wsme_json.fromjson(self._type, body)
self.assertTrue(member.enabled)
self.assertEqual(1, member.weight)
self.assertEqual(wsme_types.Unset, member.subnet_id)
def test_address_mandatory(self):
body = {"name": "test"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_protocol_mandatory(self):
body = {"name": "test"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_address(self):
body = {"ip_address": "test", "protocol_port": 443}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_subnet_id(self):
body = {"ip_address": "10.0.0.1", "protocol_port": 443,
"subnet_id": "invalid_uuid"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_enabled(self):
body = {"ip_address": "10.0.0.1", "protocol_port": 443,
"enabled": "true"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_protocol_port(self):
body = {"ip_address": "10.0.0.1", "protocol_port": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_weight(self):
body = {"ip_address": "10.0.0.1", "protocol_port": 443,
"weight": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
class TestMemberPUT(base.BaseTypesTest):
_type = member_type.MemberPUT
def test_member(self):
body = {"protocol_port": 80}
member = wsme_json.fromjson(self._type, body)
self.assertEqual(wsme_types.Unset, member.weight)
self.assertEqual(wsme_types.Unset, member.enabled)
def test_invalid_protocol_port(self):
body = {"protocol_port": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)
def test_invalid_weight(self):
body = {"weight": "test"}
self.assertRaises(ValueError, wsme_json.fromjson, self._type, body)

View File

@ -0,0 +1,126 @@
# Copyright 2014 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 wsme import exc
from wsme.rest import json as wsme_json
from wsme import types as wsme_types
from octavia.api.v1.types import pool as pool_type
from octavia.common import constants
from octavia.tests.unit.api.v1.types import base
class TestSessionPersistence(object):
_type = None
def test_session_persistence(self):
body = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE}
sp = wsme_json.fromjson(self._type, body)
self.assertIsNotNone(sp.type)
def test_invalid_type(self):
body = {"type": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_cookie_name(self):
body = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
"cookie_name": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestPoolPOST(base.BaseTypesTest):
_type = pool_type.PoolPOST
def test_pool(self):
body = {"protocol": constants.PROTOCOL_HTTP,
"lb_algorithm": constants.LB_ALGORITHM_ROUND_ROBIN}
pool = wsme_json.fromjson(self._type, body)
self.assertTrue(pool.enabled)
def test_protocol_mandatory(self):
body = {"lb_algorithm": constants.LB_ALGORITHM_ROUND_ROBIN}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_lb_algorithm_mandatory(self):
body = {"protocol": constants.PROTOCOL_HTTP}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_name(self):
body = {"name": 10, "protocol": constants.PROTOCOL_HTTP,
"lb_algorithm": constants.LB_ALGORITHM_ROUND_ROBIN}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_description(self):
body = {"description": 10, "protocol": constants.PROTOCOL_HTTP,
"lb_algorithm": constants.LB_ALGORITHM_ROUND_ROBIN}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_protocol(self):
body = {"protocol": 10,
"lb_algorithm": constants.LB_ALGORITHM_ROUND_ROBIN}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_lb_algorithm(self):
body = {"protocol": constants.PROTOCOL_HTTP, "lb_algorithm": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestPoolPUT(base.BaseTypesTest):
_type = pool_type.PoolPUT
def test_pool(self):
body = {"name": "test_name"}
pool = wsme_json.fromjson(self._type, body)
self.assertEqual(wsme_types.Unset, pool.enabled)
def test_invalid_name(self):
body = {"name": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_description(self):
body = {"description": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
def test_invalid_lb_algorithm(self):
body = {"lb_algorithm": 10}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestSessionPersistencePOST(base.BaseTypesTest, TestSessionPersistence):
_type = pool_type.SessionPersistencePOST
def test_type_mandatory(self):
body = {"cookie_name": "test_name"}
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
body)
class TestSessionPersistencePUT(base.BaseTypesTest, TestSessionPersistence):
_type = pool_type.SessionPersistencePUT

View File

@ -21,8 +21,6 @@ class TestExceptions(base.TestCase):
def test_exception(self):
try:
raise exc.NotFound()
raise exc.NotFound(resource="test", id="test")
except exc.NotFound:
pass
else:
raise Exception()

View File

@ -4,6 +4,7 @@ blockdiag
docutils==0.11
nwdiag
oslosphinx
pecan>=0.7.0
pbr>=0.6,<1.0
seqdiag
sphinx
@ -35,5 +36,5 @@ python-barbicanclient>=3.0
python-keystoneclient>=0.11.1
python-novaclient>=2.17.0
posix_ipc
pyOpenSSL>=0.14
wsme==0.6.1