Neutron LBaaS: Health Monitor Behaviors

* includes initial BaseLoadBalancersBehaviors class
* commonly used methods for creating, updating and waiting
* contains metatests for de/serialzation in json/xml

Change-Id: Ic4a0704013cb8028958b7a7e3d5ffd6d6b407564
This commit is contained in:
Franklin Naval 2014-08-15 23:05:12 -05:00 committed by Franklin Naval
parent b93c7e4cd1
commit ce899248a9
3 changed files with 411 additions and 2 deletions

View File

@ -0,0 +1,176 @@
"""
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 time
from cafe.engine.behaviors import BaseBehavior
from cloudcafe.common.exceptions import BuildErrorException, TimeoutException
from cloudcafe.networking.lbaas.common.types import LBaaSStatusTypes
class BaseLoadBalancersBehaviors(BaseBehavior):
def __init__(self, lbaas_client_type, config):
super(BaseLoadBalancersBehaviors, self).__init__()
self.config = config
# This can be a be client of type: health_monitor, listener,
# load_balancer, member or pool
self.lbaas_client_type = lbaas_client_type
def create_active_lbaas_object(self, lbaas_model_type, **kwargs):
"""
@summary: Creates a LBaaS object and waits for it to become active
@param lbaas_model_type: The type of the LBaaS object.
ex: health_monitor, listener, load_balancer, member, pool
@type lbaas_model_type: str
@param kwargs: Key/value pairs to be used in
create function call
@type kwargs: dict
@return: Response object containing response and the LBaaS model
domain object
@rtype: requests.Response
"""
try:
create_func = getattr(self.lbaas_client_type,
"create_{0}".format(lbaas_model_type))
except AttributeError as ex:
error_message = ("Failed to obtain 'create' method for {type_}: "
"{message}".format(type_=lbaas_model_type,
message=ex.message))
self._log.error(error_message)
raise Exception(error_message)
else:
resp = create_func(**kwargs)
assert resp.status_code == 202
assert resp.entity is not None
lbaas_model_obj = resp.entity
resp = self.wait_for_lbaas_object_status(
lbaas_object_id=lbaas_model_obj.entity.id,
lbaas_model_type=lbaas_model_type,
desired_status=LBaaSStatusTypes.ACTIVE)
return resp
def update_lbaas_object_and_wait_for_active(
self, lbaas_model_type, **kwargs):
"""
@summary: Updates a LBaaS object and waits for it to become active
@param lbaas_object_id: The id of the LBaaS type object.
@type lbaas_object_id: str
@param lbaas_model_type: The type of the LBaaS object.
ex: health_monitor, listener, load_balancer, member, pool
@type lbaas_model_type: str
@param kwargs: Key/value pairs to be used in
create function call
@type kwargs: dict
@return: Response object containing response and the image
domain object
@rtype: requests.Response
"""
try:
update_func = getattr(self.lbaas_client_type,
"update_{0}".format(lbaas_model_type))
except AttributeError as ex:
error_message = ("Failed to obtain 'update' method for {type_}: "
"{message}".format(type_=lbaas_model_type,
message=ex.message))
self._log.error(error_message)
raise Exception(error_message)
else:
resp = update_func(**kwargs)
assert resp.status_code == 202
assert resp.entity is not None
lbaas_model_obj = resp.entity
resp = self.wait_for_lbaas_object_status(
lbaas_object_id=lbaas_model_obj.entity.id,
lbaas_model_type=lbaas_model_type,
desired_status=LBaaSStatusTypes.ACTIVE)
return resp
def wait_for_lbaas_object_status(self, lbaas_object_id, lbaas_model_type,
desired_status, interval_time=None,
timeout=None):
"""
@summary: Waits for a LBaaS type object to reach a desired status.
@param lbaas_object_id: The id of the LBaaS type object.
@type lbaas_object_id: str
@param lbaas_model_type: The type of the LBaaS object.
ex: health_monitor, listener, load_balancer, member, pool
@type lbaas_model_type: str
@param desired_status: Desired final status of the LBaaS type object.
@type desired_status: str
@param interval_time: Amount of time in seconds to wait
between polling.
@type interval_time: int
@param interval_time: Amount of time in seconds to wait
before aborting.
@type interval_time: int
@return: Response object containing response and the LBaaS type
domain object.
@rtype: requests.Response
"""
interval_time = interval_time or self.config.lbaas_status_interval
timeout = timeout or self.config.lbaas_timeout
end_time = time.time() + timeout
while time.time() < end_time:
try:
get_func = getattr(self.lbaas_client_type,
"get_{0}".format(lbaas_model_type))
except AttributeError as ex:
error_message = ("Failed to obtain 'get' method for {type_}: "
"{message}".format(type_=lbaas_model_type,
message=ex.message))
self._log.error(error_message)
raise Exception(error_message)
else:
resp = get_func(lbaas_object_id)
if not resp.ok:
raise Exception(
"Failed to get {type_} information: "
"{code} - {reason}".format(type_=lbaas_model_type,
code=resp.status_code,
reason=resp.reason))
if resp.entity is None:
raise Exception(
"Response entity was not set. "
"Response was: {0}".format(resp.content))
lbaas_object = resp.entity
if lbaas_object.status.lower() == LBaaSStatusTypes.ERROR.lower():
raise BuildErrorException(
'Failed during wait of {type_} status with id {id_} '
'entered ERROR status.'.format(type_=lbaas_model_type,
id_=lbaas_object.id))
if lbaas_object.status == desired_status:
break
time.sleep(interval_time)
else:
raise TimeoutException(
"wait for {type_} ran for {timeout} seconds and did not "
"observe {type_} {id_} reach the {status} status.".format(
type_=lbaas_model_type, timeout=timeout,
id_=lbaas_object_id, status=desired_status))
return resp

View File

@ -14,5 +14,126 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from cafe.engine.behaviors import BaseBehavior
from cloudcafe.networking.lbaas.common.behaviors import \
BaseLoadBalancersBehaviors
class HealthMonitorBehaviors(BaseLoadBalancersBehaviors):
OBJECT_MODEL = 'health_monitor'
def __init__(self, health_monitors_client, config):
super(HealthMonitorBehaviors, self).__init__(
lbaas_client_type=health_monitors_client, config=config)
def create_active_health_monitor(
self, type_, tenant_id, delay, timeout, max_retries,
http_method=None, url_path=None, expected_codes=None,
admin_state_up=None):
"""
@summary: Creates a health monitor and waits for it to become active
@param type_: Protocol used for health monitor.
e.g., HTTP, HTTPS, TCP, PING
@type type_: str
@param tenant_id: Tenant that owns the health monitor.
@type tenant_id: str
@param delay: Time in seconds between probes.
@type delay: int
@param timeout: Time in seconds to timeout each probe.
@type timeout: int
@param max_retries: Maximum consecutive health probe tries.
@type max_retries: int
@param http_method: HTTP method monitor uses to make request.
Default: "GET"
@type http_method: str
@param url_path: Path portion of URI that will be probed if
type is HTTP(S).
Default: "/"
@type url_path: str
@param expected_codes: Expected HTTP codes for a passing HTTP(S).
Default: "200"
@type expected_codes: str
@param admin_state_up: Enabled or Disabled
Default: "true"
@type admin_state_up: bool
@return: Response object containing response and the health monitor
domain object
@rtype: requests.Response
"""
kwargs = {'type_': type_, 'tenant_id': tenant_id, 'delay': delay,
'timeout': timeout, 'max_retries': max_retries,
'http_method': http_method, 'url_path': url_path,
'expected_codes': expected_codes,
'admin_state_up': admin_state_up}
resp = self.create_active_lbaas_object(
lbaas_model_type=self.OBJECT_MODEL,
kwargs=kwargs)
return resp
def update_health_monitor_and_wait_for_active(
self, health_monitor_id, delay=None,
timeout=None, max_retries=None, http_method=None,
url_path=None, expected_codes=None,
admin_state_up=None):
"""
@summary: Updates a health monitor and waits for it to become active
@param health_monitor_id: ID of the health monitor to update.
@type health_monitor_id: str
@param delay: Time in seconds between probes.
@type delay: int
@param timeout: Time in seconds to timeout each probe.
@type timeout: int
@param max_retries: Maximum consecutive health probe tries.
@type max_retries: int
@param http_method: HTTP method monitor uses to make request.
Default: "GET"
@type http_method: str
@param url_path: Path portion of URI that will be probed if
type is HTTP(S).
Default: "/"
@type url_path: str
@param expected_codes: Expected HTTP codes for a passing HTTP(S).
Default: "200"
@type expected_codes: str
@param admin_state_up: Enabled or Disabled
Default: "true"
@type admin_state_up: bool
@return: Response object containing response and the health monitor
domain object
@rtype: requests.Response
"""
kwargs = {'health_monitor_id': health_monitor_id,
'delay': delay, 'timeout': timeout,
'max_retries': max_retries, 'http_method': http_method,
'url_path': url_path, 'expected_codes': expected_codes,
'admin_state_up': admin_state_up}
resp = self.update_lbaas_object_and_wait_for_active(
lbaas_model_type=self.OBJECT_MODEL,
kwargs=kwargs)
return resp
def wait_for_health_monitor_status(self, health_monitor_id, desired_status,
interval_time=None, timeout=None):
"""
@summary: Waits for a health monitor to reach a desired status
@param health_monitor_id: The id of the health monitor
@type health_monitor_id: String
@param desired_status: The desired final status of the health monitor
@type desired_status: String
@param interval_time: The amount of time in seconds to wait
between polling
@type interval_time: Integer
@param interval_time: The amount of time in seconds to wait
before aborting
@type interval_time: Integer
@return: Response object containing response and the health monitor
domain object
@rtype: requests.Response
"""
kwargs = {'health_monitor_id': health_monitor_id,
'desired_status': desired_status,
'interval_time': interval_time,
'timeout': timeout}
resp = self.wait_for_lbaas_object_status(
lbaas_model_type=self.OBJECT_MODEL, **kwargs)
return resp

View File

@ -14,5 +14,117 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from cafe.engine.behaviors import BaseBehavior
import mock
import unittest
from cloudcafe.networking.lbaas.lbaas_api.health_monitor.behaviors \
import HealthMonitorBehaviors
from cloudcafe.networking.lbaas.lbaas_api.health_monitor.client \
import HealthMonitorsClient
class HealthMonitorBehaviorsFixture(unittest.TestCase):
"""
@summary: Health Monitor Behaviors Tests
"""
@classmethod
def setUpClass(cls):
super(HealthMonitorBehaviorsFixture, cls).setUpClass()
cls.auth_token = "fake_auth_token"
cls.url = "http://fake.url.endpoint"
cls.health_monitor_id = "12345"
cls.type_ = "HTTP"
cls.tenant_id = "453105b9-1754-413f-aab1-55f1af620750"
cls.delay = 20
cls.timeout = 10
cls.max_retries = 5
cls.http_method = "GET"
cls.url_path = "/check"
cls.expected_codes = "200-299"
cls.admin_state_up = False
cls.desired_status = "ACTIVE"
cls.interval_time = 20
cls.timeout = 120
cls.health_monitors_client = HealthMonitorsClient(
url=cls.url,
auth_token=cls.auth_token,
serialize_format=cls.SERIALIZE,
deserialize_format=cls.DESERIALIZE)
cls.health_monitor_behaviors = HealthMonitorBehaviors(
health_monitors_client=cls.health_monitors_client, config=None)
class HealthMonitorBehaviorsTests(object):
@mock.patch.object(HealthMonitorBehaviors, 'create_active_health_monitor',
autospec=True)
def test_create_active_health_monitor(self, mock_request):
create_active_health_monitor_kwargs = (
{'type_': self.type_,
'tenant_id': self.tenant_id,
'delay': self.delay,
'timeout': self.timeout,
'max_retries': self.max_retries,
'http_method': self.http_method,
'url_path': self.url_path,
'expected_codes': self.expected_codes,
'admin_state_up': self.admin_state_up})
self.health_monitor_behaviors.create_active_health_monitor(
**create_active_health_monitor_kwargs)
mock_request.assert_called_once_with(
self.health_monitor_behaviors,
**create_active_health_monitor_kwargs)
@mock.patch.object(HealthMonitorBehaviors,
'update_health_monitor_and_wait_for_active',
autospec=True)
def test_update_health_monitor_and_wait_for_active(self, mock_request):
update_health_monitor_and_wait_for_active_kwargs = (
{'health_monitor_id': self.health_monitor_id,
'delay': self.delay,
'timeout': self.timeout,
'max_retries': self.max_retries,
'http_method': self.http_method,
'url_path': self.url_path,
'expected_codes': self.expected_codes,
'admin_state_up': self.admin_state_up})
self.health_monitor_behaviors.\
update_health_monitor_and_wait_for_active(
**update_health_monitor_and_wait_for_active_kwargs)
mock_request.assert_called_once_with(
self.health_monitor_behaviors,
**update_health_monitor_and_wait_for_active_kwargs)
@mock.patch.object(HealthMonitorBehaviors,
'wait_for_health_monitor_status',
autospec=True)
def test_wait_for_health_monitor_status(self, mock_request):
wait_for_health_monitor_status_kwargs = (
{'health_monitor_id': self.health_monitor_id,
'desired_status': self.desired_status,
'interval_time': self.interval_time,
'timeout': self.timeout})
self.health_monitor_behaviors.wait_for_health_monitor_status(
**wait_for_health_monitor_status_kwargs)
mock_request.assert_called_once_with(
self.health_monitor_behaviors,
**wait_for_health_monitor_status_kwargs)
class HealthMonitorsClientTestsXML(HealthMonitorBehaviorsFixture,
HealthMonitorBehaviorsTests):
SERIALIZE = 'xml'
DESERIALIZE = 'xml'
class HealthMonitorsClientTestsJSON(HealthMonitorBehaviorsFixture,
HealthMonitorBehaviorsTests):
SERIALIZE = 'json'
DESERIALIZE = 'json'