From 8997def2b570d0df6cb0d2331ff8d294444cb572 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Fri, 26 Oct 2018 17:05:22 -0700 Subject: [PATCH] Updates Octavia to support octavia-lib This is the base patch that updates octavia to use the new octavia-lib. It is backwards compatible by using debtcollector moves. It adds a new controller process called the "driver-agent". This patch also adds unit test coverage for a few additional modules. Depends-On: https://review.openstack.org/#/c/641180/ Change-Id: I438e1548ec0fb6111d1ab85b05015007d9d0a006 --- devstack/README.md | 2 +- devstack/contrib/new-octavia-devstack.sh | 2 +- devstack/plugin.sh | 18 + devstack/samples/multinode/local.conf | 1 + devstack/samples/singlenode/local.conf | 1 + devstack/settings | 9 + doc/source/contributor/guides/providers.rst | 27 +- doc/source/reference/introduction.rst | 14 +- .../reference/octavia-component-overview.svg | 339 +++++-------- etc/octavia.conf | 20 + lower-constraints.txt | 1 + octavia/api/drivers/amphora_driver/driver.py | 10 +- octavia/api/drivers/data_models.py | 280 +---------- octavia/api/drivers/driver_agent/__init__.py | 11 + .../drivers/driver_agent/driver_listener.py | 144 ++++++ .../drivers/driver_agent/driver_updater.py | 174 +++++++ octavia/api/drivers/driver_lib.py | 147 +----- octavia/api/drivers/exceptions.py | 147 +----- octavia/api/drivers/noop_driver/driver.py | 4 +- octavia/api/drivers/provider_base.py | 471 +----------------- octavia/cmd/driver_agent.py | 84 ++++ octavia/common/config.py | 32 ++ octavia/common/constants.py | 320 +++++++----- octavia/opts.py | 1 + .../amphora_driver/test_amphora_driver.py | 21 +- .../unit/api/drivers/driver_agent/__init__.py | 11 + .../driver_agent/test_driver_listener.py | 171 +++++++ .../driver_agent/test_driver_updater.py | 295 +++++++++++ .../unit/api/drivers/test_data_models.py | 217 -------- .../tests/unit/api/drivers/test_driver_lib.py | 251 +--------- .../tests/unit/api/drivers/test_exceptions.py | 88 ---- .../unit/api/drivers/test_provider_base.py | 157 +----- octavia/tests/unit/cmd/test_driver_agent.py | 70 +++ octavia/tests/unit/test_opts.py | 26 + octavia/tests/unit/test_version.py | 34 ++ .../legacy/grenade-devstack-octavia/run.yaml | 1 + .../octavia-v1-dsvm-py3x-scenario/run.yaml | 1 + .../legacy/octavia-v1-dsvm-scenario/run.yaml | 1 + ...nsition-driver-agent-aeefef114898b8f5.yaml | 18 + requirements.txt | 2 + setup.cfg | 1 + zuul.d/jobs.yaml | 2 + 42 files changed, 1594 insertions(+), 2032 deletions(-) create mode 100644 octavia/api/drivers/driver_agent/__init__.py create mode 100644 octavia/api/drivers/driver_agent/driver_listener.py create mode 100644 octavia/api/drivers/driver_agent/driver_updater.py create mode 100644 octavia/cmd/driver_agent.py create mode 100644 octavia/tests/unit/api/drivers/driver_agent/__init__.py create mode 100644 octavia/tests/unit/api/drivers/driver_agent/test_driver_listener.py create mode 100644 octavia/tests/unit/api/drivers/driver_agent/test_driver_updater.py delete mode 100644 octavia/tests/unit/api/drivers/test_data_models.py delete mode 100644 octavia/tests/unit/api/drivers/test_exceptions.py create mode 100644 octavia/tests/unit/cmd/test_driver_agent.py create mode 100644 octavia/tests/unit/test_opts.py create mode 100644 octavia/tests/unit/test_version.py create mode 100644 releasenotes/notes/Octavia-lib-transition-driver-agent-aeefef114898b8f5.yaml diff --git a/devstack/README.md b/devstack/README.md index 4902f89bb5..300cc2946b 100644 --- a/devstack/README.md +++ b/devstack/README.md @@ -23,7 +23,7 @@ For example For example - ENABLED_SERVICES+=octavia,o-api,o-cw,o-hk,o-hm + ENABLED_SERVICES+=octavia,o-api,o-cw,o-hk,o-hm,o-da For more information, see the "Externally Hosted Plugins" section of https://docs.openstack.org/devstack/latest/plugins.html diff --git a/devstack/contrib/new-octavia-devstack.sh b/devstack/contrib/new-octavia-devstack.sh index 843ae5d00e..847e0314e8 100755 --- a/devstack/contrib/new-octavia-devstack.sh +++ b/devstack/contrib/new-octavia-devstack.sh @@ -45,7 +45,7 @@ ENABLED_SERVICES+=,neutron-metadata-agent,neutron-qos # Tempest (optional) #ENABLED_SERVICES+=,tempest # Octavia -ENABLED_SERVICES+=,octavia,o-api,o-cw,o-hm,o-hk +ENABLED_SERVICES+=,octavia,o-api,o-cw,o-hm,o-hk,o-da EOF # Create the stack user diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 51599893a7..14793fff8d 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -29,6 +29,15 @@ function octaviaclient_install { fi } +function octavia_lib_install { + if use_library_from_git "octavia-lib"; then + git_clone_by_name "octavia-lib" + setup_dev_lib "octavia-lib" + else + pip_install_gr octavia-lib + fi +} + function install_diskimage_builder { if use_library_from_git "diskimage-builder"; then GITREPO["diskimage-builder"]=$DISKIMAGE_BUILDER_REPO_URL @@ -209,6 +218,9 @@ function octavia_configure { sudo mkdir -m 755 -p $OCTAVIA_CONF_DIR safe_chown $STACK_USER $OCTAVIA_CONF_DIR + sudo mkdir -m 700 -p $OCTAVIA_RUN_DIR + safe_chown $STACK_USER $OCTAVIA_RUN_DIR + if ! [ -e $OCTAVIA_CONF ] ; then cp $OCTAVIA_DIR/etc/octavia.conf $OCTAVIA_CONF fi @@ -477,6 +489,7 @@ function octavia_start { run_process $OCTAVIA_API "$OCTAVIA_API_BINARY $OCTAVIA_API_ARGS" fi + run_process $OCTAVIA_DRIVER_AGENT "$OCTAVIA_DRIVER_AGENT_BINARY $OCTAVIA_DRIVER_AGENT_ARGS" run_process $OCTAVIA_CONSUMER "$OCTAVIA_CONSUMER_BINARY $OCTAVIA_CONSUMER_ARGS" run_process $OCTAVIA_HOUSEKEEPER "$OCTAVIA_HOUSEKEEPER_BINARY $OCTAVIA_HOUSEKEEPER_ARGS" run_process $OCTAVIA_HEALTHMANAGER "$OCTAVIA_HEALTHMANAGER_BINARY $OCTAVIA_HEALTHMANAGER_ARGS" @@ -489,6 +502,7 @@ function octavia_stop { else stop_process $OCTAVIA_API fi + stop_process $OCTAVIA_DRIVER_AGENT stop_process $OCTAVIA_CONSUMER stop_process $OCTAVIA_HOUSEKEEPER stop_process $OCTAVIA_HEALTHMANAGER @@ -523,6 +537,9 @@ function octavia_cleanup { if [ ${OCTAVIA_CONF_DIR}x != x ] ; then sudo rm -rf ${OCTAVIA_CONF_DIR} fi + if [ ${OCTAVIA_RUN_DIR}x != x ] ; then + sudo rm -rf ${OCTAVIA_RUN_DIR} + fi if [ ${OCTAVIA_AMP_SSH_KEY_PATH}x != x ] ; then rm -f ${OCTAVIA_AMP_SSH_KEY_PATH} ${OCTAVIA_AMP_SSH_KEY_PATH}.pub fi @@ -635,6 +652,7 @@ if is_service_enabled $OCTAVIA; then if [[ "$1" == "stack" && "$2" == "install" ]]; then # Perform installation of service source echo_summary "Installing octavia" + octavia_lib_install octavia_install octaviaclient_install diff --git a/devstack/samples/multinode/local.conf b/devstack/samples/multinode/local.conf index 693aa52af8..da8fdac86f 100644 --- a/devstack/samples/multinode/local.conf +++ b/devstack/samples/multinode/local.conf @@ -61,6 +61,7 @@ enable_service o-hm enable_service o-hk enable_service o-api enable_service o-api-ha +enable_service o-da OCTAVIA_USE_PREGENERATED_CERTS=True OCTAVIA_USE_PREGENERATED_SSH_KEY=True diff --git a/devstack/samples/singlenode/local.conf b/devstack/samples/singlenode/local.conf index 7a9d985e1c..ba244810a4 100644 --- a/devstack/samples/singlenode/local.conf +++ b/devstack/samples/singlenode/local.conf @@ -73,6 +73,7 @@ enable_service o-cw enable_service o-hm enable_service o-hk enable_service o-api +enable_service o-da # enable DVR diff --git a/devstack/settings b/devstack/settings index f02e87107a..8fc86dd7bb 100644 --- a/devstack/settings +++ b/devstack/settings @@ -16,6 +16,7 @@ OCTAVIA_DHCLIENT_CONF=${OCTAVIA_DHCLIENT_CONF:-${OCTAVIA_DHCLIENT_DIR}/dhclient. OCTAVIA_CONF=${OCTAVIA_CONF:-${OCTAVIA_CONF_DIR}/octavia.conf} OCTAVIA_AUDIT_MAP=${OCTAVIA_AUDIT_MAP:-${OCTAVIA_CONF_DIR}/octavia_api_audit_map.conf} OCTAVIA_TEMPEST_DIR=${OCTAVIA_TEMPEST_DIR:-${OCTAVIA_DIR}/octavia/tests/tempest} +OCTAVIA_RUN_DIR=${OCTAVIA_RUN_DIR:-"/var/run/octavia"} OCTAVIA_AMPHORA_DRIVER=${OCTAVIA_AMPHORA_DRIVER:-"amphora_haproxy_rest_driver"} OCTAVIA_NETWORK_DRIVER=${OCTAVIA_NETWORK_DRIVER:-"allowed_address_pairs_driver"} @@ -61,11 +62,13 @@ OCTAVIA_API_BINARY=${OCTAVIA_API_BINARY:-${OCTAVIA_BIN_DIR}/octavia-api} OCTAVIA_CONSUMER_BINARY=${OCTAVIA_CONSUMER_BINARY:-${OCTAVIA_BIN_DIR}/octavia-worker} OCTAVIA_HOUSEKEEPER_BINARY=${OCTAVIA_HOUSEKEEPER_BINARY:-${OCTAVIA_BIN_DIR}/octavia-housekeeping} OCTAVIA_HEALTHMANAGER_BINARY=${OCTAVIA_HEALTHMANAGER_BINARY:-${OCTAVIA_BIN_DIR}/octavia-health-manager} +OCTAVIA_DRIVER_AGENT_BINARY=${OCTAVIA_DRIVER_AGENT_BINARY:-${OCTAVIA_BIN_DIR}/octavia-driver-agent} OCTAVIA_API_ARGS=${OCTAVIA_API_ARGS:-" --config-file $OCTAVIA_CONF"} OCTAVIA_CONSUMER_ARGS=${OCTAVIA_CONSUMER_ARGS:-" --config-file $OCTAVIA_CONF"} OCTAVIA_HOUSEKEEPER_ARGS=${OCTAVIA_HOUSEKEEPER_ARGS:-" --config-file $OCTAVIA_CONF"} OCTAVIA_HEALTHMANAGER_ARGS=${OCTAVIA_HEALTHMANAGER_ARGS:-" --config-file $OCTAVIA_CONF"} +OCTAVIA_DRIVER_AGENT_ARGS=${OCTAVIA_DRIVER_AGENT_ARGS:-" --config-file $OCTAVIA_CONF"} OCTAVIA_TEMPEST=${OCTAVIA_TEMPEST:-"disabled"} @@ -75,12 +78,18 @@ OCTAVIA_HOUSEKEEPER="o-hk" OCTAVIA_HEALTHMANAGER="o-hm" OCTAVIA_SERVICE="octavia" OCTAVIA_API_HAPROXY="o-api-ha" +OCTAVIA_DRIVER_AGENT="o-da" # Client settings GITREPO["python-octaviaclient"]=${OCTAVIACLIENT_REPO:-${GIT_BASE}/openstack/python-octaviaclient.git} GITBRANCH["python-octaviaclient"]=${OCTAVIACLIENT_BRANCH:-master} GITDIR["python-octaviaclient"]=$DEST/python-octaviaclient +# Library settings +GITREPO["octavia-lib"]=${OCTAVIA_LIB_REPO:-${GIT_BASE}/openstack/octavia-lib.git} +GITBRANCH["octavia-lib"]=${OCTAVIA_LIB_BRANCH:-master} +GITDIR["octavia-lib"]=$DEST/octavia-lib + NEUTRON_LBAAS_DIR=$DEST/neutron-lbaas NEUTRON_LBAAS_CONF=$NEUTRON_CONF_DIR/neutron_lbaas.conf OCTAVIA_SERVICE_PROVIDER=${OCTAVIA_SERVICE_PROVIDER:-"LOADBALANCERV2:Octavia:neutron_lbaas.drivers.octavia.driver.OctaviaDriver:default"} diff --git a/doc/source/contributor/guides/providers.rst b/doc/source/contributor/guides/providers.rst index f0f439ed93..b74495af1b 100644 --- a/doc/source/contributor/guides/providers.rst +++ b/doc/source/contributor/guides/providers.rst @@ -55,10 +55,11 @@ Provider drivers should only access the following Octavia APIs. All other Octavia APIs are not considered stable or safe for provider driver use and may change at any time. -* octavia.api.drivers.data_models -* octavia.api.drivers.driver_lib -* octavia.api.drivers.exceptions -* octavia.api.drivers.provider_base +* octavia_lib.api.drivers.data_models +* octavia_lib.api.drivers.driver_lib +* octavia_lib.api.drivers.exceptions +* octavia_lib.api.drivers.provider_base +* octavia_lib.common.constants Octavia Provider Driver API =========================== @@ -1695,7 +1696,7 @@ Driver Support Library Provider drivers need support for updating provisioning status, operating status, and statistics. Drivers will not directly use database operations, -and instead will callback to Octavia using a new API. +and instead will callback to octavia-lib using a new API. .. warning:: @@ -1708,7 +1709,7 @@ and instead will callback to Octavia using a new API. This library is interim and will be removed when the driver support endpoint is made available. At which point drivers will not import any code from - Octavia. + octavia-lib. Update Provisioning and Operating Status API -------------------------------------------- @@ -1723,6 +1724,13 @@ and operating status parameters are as defined by Octavia status codes. If an existing object is not included in the input parameter, the status remains unchanged. +.. note:: + + If the driver-agent exceeds its configured `status_max_processes` this call + may block while it waits for a status process slot to become available. + The operator will be notified if the driver-agent approaches or reaches + the configured limit. + provisioning_status: status associated with lifecycle of the resource. See `Octavia Provisioning Status Codes `_. @@ -1765,6 +1773,13 @@ with multiple listener statistics is used to update statistics in a single call. If an existing listener is not included, the statistics that object remain unchanged. +.. note:: + + If the driver-agent exceeds its configured `stats_max_processes` this call + may block while it waits for a stats process slot to become available. + The operator will be notified if the driver-agent approaches or reaches + the configured limit. + The general form of the input dictionary is a list of listener statistics: .. code-block:: python diff --git a/doc/source/reference/introduction.rst b/doc/source/reference/introduction.rst index 3901266ac8..cc545b0d16 100644 --- a/doc/source/reference/introduction.rst +++ b/doc/source/reference/introduction.rst @@ -83,10 +83,9 @@ It is also possible to use Octavia as a Neutron LBaaS plugin, in the same way as any other vendor. You can think of Octavia as an "open source vendor" for Neutron LBaaS. -Soon, Octavia will support third-party vendor drivers just like Neutron LBaaS, -and will then fully replace Neutron LBaaS as the load balancing solution for -OpenStack. At that time, third-party vendor drivers that presently "plug in" to -Neutron LBaaS will plug in to Octavia instead. +Octavia supports third-party vendor drivers just like Neutron LBaaS, +and fully replaces Neutron LBaaS as the load balancing solution for +OpenStack. For further information on OpenStack Neutron LBaaS deprecation, please refer to https://wiki.openstack.org/wiki/Neutron/LBaaS/Deprecation. @@ -119,7 +118,7 @@ A 10,000-foot overview of Octavia components :width: 660px :alt: Octavia Component Overview -Octavia version 0.9 consists of the following major components: +Octavia version 4.0 consists of the following major components: * **amphorae** - Amphorae are the individual virtual machines, containers, or bare metal servers that accomplish the delivery of load balancing services to @@ -128,7 +127,7 @@ Octavia version 0.9 consists of the following major components: HAProxy. * **controller** - The Controller is the "brains" of Octavia. It consists of - four sub-components, which are individual daemons. They can be run on + five sub-components, which are individual daemons. They can be run on separate back-end infrastructure if desired: * **API Controller** - As the name implies, this subcomponent runs Octavia's @@ -147,6 +146,9 @@ Octavia version 0.9 consists of the following major components: database records, manages the spares pool, and manages amphora certificate rotation. + * **Driver Agent** - The driver agent receives status and statistics updates + from provider drivers. + * **network** - Octavia cannot accomplish what it does without manipulating the network environment. Amphorae are spun up with a network interface on the "load balancer network," and they may also plug directly into tenant networks diff --git a/doc/source/reference/octavia-component-overview.svg b/doc/source/reference/octavia-component-overview.svg index 711499278b..696ae67863 100644 --- a/doc/source/reference/octavia-component-overview.svg +++ b/doc/source/reference/octavia-component-overview.svg @@ -1,16 +1,9 @@ - + - - - - - - - + width="11.6929in" height="8.26772in" viewBox="0 0 841.89 595.276" xml:space="preserve" color-interpolation-filters="sRGB" + class="st17"> @@ -35,162 +30,97 @@ - + - + + + + - - - - + Page-1 - - - - + Parallelogram.1023 - - - - + Rectangle Controller Worker Driver - - - - - - Controller Worker Driver - + Controller Worker Driver + Square Certificate Driver - - - - - - CertificateDriver - + Certificate Driver + Square.3 Compute Driver - - - - - - ComputeDriver - + Compute Driver + Square.4 Network Driver - - - - - - NetworkDriver - + Network Driver + Square.5 Amphora Driver - - - - - - AmphoraDriver - + Amphora Driver + Ellipse.6 Neutron - - - - - - Neutron - + Neutron + Ellipse.5 Nova - - - - - - Nova - + Nova + Ellipse.69 - Barbican - - - - - + Barbican / Castellan - Barbican - + Barbican / Castellan + Ellipse.3 Oslo Messaging - - - - - - OsloMessaging - + Oslo Messaging + Dynamic connector.1006 - + Dynamic connector.1007 - + - + Dynamic connector.1008 - + Dynamic connector.1009 - + Dynamic connector.1010 - + - + Sheet.1011 - + Square.9 Octavia API - - - - - - OctaviaAPI - + Octavia API + Sheet.15 - + - + - + Sheet.1012 - + Square.6 Octavia Worker - - - - - - OctaviaWorker - + Octavia Worker + Sheet.16 - + - + - + Sheet.1013 - + Square.7 Health Manager - - - - - - HealthManager - + Health Manager + Sheet.17 - + - + - + Sheet.1014 - + Square.8 Housekeeping Manager - - - - - - HousekeepingManager - + Housekeeping Manager + Sheet.18 - + - + - + Dynamic connector.1016 - + - + Dynamic connector.1017 - + Dynamic connector.1018 - - - - + Can.1019 Database - + Sheet.1020 - - - - - - - - - + - Database + Database - + Parallelogram.1021 - - - - + Parallelogram.1022 Amphora - - - - - - Amphora - + Amphora + Sheet.1054 - + Dynamic connector.1050 - + Dynamic connector.1051 - - Sheet.1064 - - Rectangle.1056 - Neutron - - - - - - - Neutron - - Rectangle.1057 - LBaaS v2 User API Handler - - - - - - - LBaaS v2User API Handler - - Rectangle.1058 - Octavia Driver - - - - - - - Octavia Driver + + Sheet.1056 + Amphora Driver + + Amphora Driver + + Sheet.1057 + + Square.7 + Driver Agent + + Driver Agent + + Sheet.19 + + + + - - Dynamic connector.1065 - + + Dynamic connector.1059 + + + + Dynamic connector + diff --git a/etc/octavia.conf b/etc/octavia.conf index 261507eeb0..1d140e88b0 100644 --- a/etc/octavia.conf +++ b/etc/octavia.conf @@ -460,3 +460,23 @@ # A URL representing messaging driver to use for notification. If not # specified, we fall back to the same configuration used for RPC. # transport_url = + +[driver_agent] +# status_socket_path = /var/run/octavia/status.sock +# stats_socket_path = /var/run/octavia/stats.sock + +# Maximum time to wait for a status message before checking for shutdown +# status_request_timeout = 5 + +# Maximum number of status processes per driver-agent +# status_max_processes = 50 + +# Maximum time to wait for a stats message before checking for shutdown +# stats_request_timeout = 5 + +# Maximum number of stats processes per driver-agent +# stats_max_processes = 50 + +# Percentage of max_processes (both status and stats) in use to start +# logging warning messages about an overloaded driver-agent. +# max_process_warning_percent = .75 diff --git a/lower-constraints.txt b/lower-constraints.txt index 60c5d4282d..d51901979e 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -72,6 +72,7 @@ munch==2.2.0 netaddr==0.7.19 netifaces==0.10.4 networkx==1.11 +octavia-lib==1.1.1 openstacksdk==0.12.0 os-client-config==1.29.0 os-service-types==1.2.0 diff --git a/octavia/api/drivers/amphora_driver/driver.py b/octavia/api/drivers/amphora_driver/driver.py index fe6e1796c8..320380baf3 100644 --- a/octavia/api/drivers/amphora_driver/driver.py +++ b/octavia/api/drivers/amphora_driver/driver.py @@ -14,15 +14,17 @@ from jsonschema import exceptions as js_exceptions from jsonschema import validate + from oslo_config import cfg from oslo_log import log as logging import oslo_messaging as messaging from stevedore import driver as stevedore_driver +from octavia_lib.api.drivers import data_models as driver_dm +from octavia_lib.api.drivers import exceptions +from octavia_lib.api.drivers import provider_base as driver_base + from octavia.api.drivers.amphora_driver import flavor_schema -from octavia.api.drivers import data_models as driver_dm -from octavia.api.drivers import exceptions -from octavia.api.drivers import provider_base as driver_base from octavia.api.drivers import utils as driver_utils from octavia.common import constants as consts from octavia.common import data_models @@ -93,7 +95,7 @@ class AmphoraProviderDriver(driver_base.ProviderDriver): # expects vip_qos_policy_id = lb_dict.pop('vip_qos_policy_id', None) if vip_qos_policy_id: - vip_dict = {"qos_policy_id": vip_qos_policy_id} + vip_dict = {"vip_qos_policy_id": vip_qos_policy_id} lb_dict["vip"] = vip_dict payload = {consts.LOAD_BALANCER_ID: lb_id, diff --git a/octavia/api/drivers/data_models.py b/octavia/api/drivers/data_models.py index ce88b851aa..0db407f8ed 100644 --- a/octavia/api/drivers/data_models.py +++ b/octavia/api/drivers/data_models.py @@ -15,270 +15,44 @@ # License for the specific language governing permissions and limitations # under the License. -import six +import warnings -from oslo_log import log as logging +from debtcollector import moves -LOG = logging.getLogger(__name__) +from octavia_lib.api.drivers import data_models as lib_data_models -class BaseDataModel(object): - def to_dict(self, calling_classes=None, recurse=False, - render_unsets=False, **kwargs): - """Converts a data model to a dictionary.""" - calling_classes = calling_classes or [] - ret = {} - for attr in self.__dict__: - if attr.startswith('_') or not kwargs.get(attr, True): - continue - value = self.__dict__[attr] +warnings.simplefilter('default', DeprecationWarning) - if recurse: - if isinstance(getattr(self, attr), list): - ret[attr] = [] - for item in value: - if isinstance(item, BaseDataModel): - if type(self) not in calling_classes: - ret[attr].append( - item.to_dict(calling_classes=( - calling_classes + [type(self)]), - recurse=True, - render_unsets=render_unsets)) - else: - ret[attr].append(None) - else: - ret[attr].append(item) - elif isinstance(getattr(self, attr), BaseDataModel): - if type(self) not in calling_classes: - ret[attr] = value.to_dict( - render_unsets=render_unsets, - calling_classes=calling_classes + [type(self)]) - else: - ret[attr] = None - elif six.PY2 and isinstance(value, six.text_type): - ret[attr.encode('utf8')] = value.encode('utf8') - elif isinstance(value, UnsetType): - if render_unsets: - ret[attr] = None - else: - continue - else: - ret[attr] = value - else: - if (isinstance(getattr(self, attr), (BaseDataModel, list)) or - isinstance(value, UnsetType)): - if render_unsets: - ret[attr] = None - else: - continue - else: - ret[attr] = value +BaseDataModel = moves.moved_class(lib_data_models.BaseDataModel, + 'BaseDataModel', __name__, + version='Stein', removal_version='U') - return ret +UnsetType = moves.moved_class(lib_data_models.UnsetType, 'UnsetType', __name__, + version='Stein', removal_version='U') - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.to_dict() == other.to_dict() - return False +LoadBalancer = moves.moved_class(lib_data_models.LoadBalancer, 'LoadBalancer', + __name__, version='Stein', + removal_version='U') - def __ne__(self, other): - return not self.__eq__(other) +Listener = moves.moved_class(lib_data_models.Listener, 'Listener', __name__, + version='Stein', removal_version='U') - @classmethod - def from_dict(cls, dict): - return cls(**dict) +Pool = moves.moved_class(lib_data_models.Pool, 'Pool', __name__, + version='Stein', removal_version='U') +Member = moves.moved_class(lib_data_models.Member, 'Member', __name__, + version='Stein', removal_version='U') -class UnsetType(object): - def __bool__(self): - return False - __nonzero__ = __bool__ +HealthMonitor = moves.moved_class(lib_data_models.HealthMonitor, + 'HealthMonitor', __name__, + version='Stein', removal_version='U') - def __repr__(self): - return 'Unset' +L7Policy = moves.moved_class(lib_data_models.L7Policy, 'L7Policy', __name__, + version='Stein', removal_version='U') +L7Rule = moves.moved_class(lib_data_models.L7Rule, 'L7Rule', __name__, + version='Stein', removal_version='U') -Unset = UnsetType() - - -class LoadBalancer(BaseDataModel): - def __init__(self, admin_state_up=Unset, description=Unset, flavor=Unset, - listeners=Unset, loadbalancer_id=Unset, name=Unset, - pools=Unset, project_id=Unset, vip_address=Unset, - vip_network_id=Unset, vip_port_id=Unset, vip_subnet_id=Unset, - vip_qos_policy_id=Unset): - - self.admin_state_up = admin_state_up - self.description = description - self.flavor = flavor - self.listeners = listeners - self.loadbalancer_id = loadbalancer_id - self.name = name - self.pools = pools - self.project_id = project_id - self.vip_address = vip_address - self.vip_network_id = vip_network_id - self.vip_port_id = vip_port_id - self.vip_subnet_id = vip_subnet_id - self.vip_qos_policy_id = vip_qos_policy_id - - -class Listener(BaseDataModel): - def __init__(self, admin_state_up=Unset, connection_limit=Unset, - default_pool=Unset, default_pool_id=Unset, - default_tls_container_ref=Unset, - default_tls_container_data=Unset, description=Unset, - insert_headers=Unset, l7policies=Unset, listener_id=Unset, - loadbalancer_id=Unset, name=Unset, protocol=Unset, - protocol_port=Unset, sni_container_refs=Unset, - sni_container_data=Unset, timeout_client_data=Unset, - timeout_member_connect=Unset, timeout_member_data=Unset, - timeout_tcp_inspect=Unset, client_ca_tls_container_ref=Unset, - client_ca_tls_container_data=Unset, - client_authentication=Unset, client_crl_container_ref=Unset, - client_crl_container_data=Unset): - - self.admin_state_up = admin_state_up - self.connection_limit = connection_limit - self.default_pool = default_pool - self.default_pool_id = default_pool_id - self.default_tls_container_data = default_tls_container_data - self.default_tls_container_ref = default_tls_container_ref - self.description = description - self.insert_headers = insert_headers - self.l7policies = l7policies - self.listener_id = listener_id - self.loadbalancer_id = loadbalancer_id - self.name = name - self.protocol = protocol - self.protocol_port = protocol_port - self.sni_container_data = sni_container_data - self.sni_container_refs = sni_container_refs - self.timeout_client_data = timeout_client_data - self.timeout_member_connect = timeout_member_connect - self.timeout_member_data = timeout_member_data - self.timeout_tcp_inspect = timeout_tcp_inspect - self.client_ca_tls_container_ref = client_ca_tls_container_ref - self.client_ca_tls_container_data = client_ca_tls_container_data - self.client_authentication = client_authentication - self.client_crl_container_ref = client_crl_container_ref - self.client_crl_container_data = client_crl_container_data - - -class Pool(BaseDataModel): - def __init__(self, admin_state_up=Unset, description=Unset, - healthmonitor=Unset, lb_algorithm=Unset, - loadbalancer_id=Unset, members=Unset, name=Unset, - pool_id=Unset, listener_id=Unset, protocol=Unset, - session_persistence=Unset, tls_container_ref=Unset, - tls_container_data=Unset, ca_tls_container_ref=Unset, - ca_tls_container_data=Unset, crl_container_ref=Unset, - crl_container_data=Unset, tls_enabled=Unset): - - self.admin_state_up = admin_state_up - self.description = description - self.healthmonitor = healthmonitor - self.lb_algorithm = lb_algorithm - self.loadbalancer_id = loadbalancer_id - self.members = members - self.name = name - self.pool_id = pool_id - self.listener_id = listener_id - self.protocol = protocol - self.session_persistence = session_persistence - self.tls_container_ref = tls_container_ref - self.tls_container_data = tls_container_data - self.ca_tls_container_ref = ca_tls_container_ref - self.ca_tls_container_data = ca_tls_container_data - self.crl_container_ref = crl_container_ref - self.crl_container_data = crl_container_data - self.tls_enabled = tls_enabled - - -class Member(BaseDataModel): - def __init__(self, address=Unset, admin_state_up=Unset, member_id=Unset, - monitor_address=Unset, monitor_port=Unset, name=Unset, - pool_id=Unset, protocol_port=Unset, subnet_id=Unset, - weight=Unset, backup=Unset): - - self.address = address - self.admin_state_up = admin_state_up - self.member_id = member_id - self.monitor_address = monitor_address - self.monitor_port = monitor_port - self.name = name - self.pool_id = pool_id - self.protocol_port = protocol_port - self.subnet_id = subnet_id - self.weight = weight - self.backup = backup - - -class HealthMonitor(BaseDataModel): - def __init__(self, admin_state_up=Unset, delay=Unset, expected_codes=Unset, - healthmonitor_id=Unset, http_method=Unset, max_retries=Unset, - max_retries_down=Unset, name=Unset, pool_id=Unset, - timeout=Unset, type=Unset, url_path=Unset, http_version=Unset, - domain_name=Unset): - - self.admin_state_up = admin_state_up - self.delay = delay - self.expected_codes = expected_codes - self.healthmonitor_id = healthmonitor_id - self.http_method = http_method - self.max_retries = max_retries - self.max_retries_down = max_retries_down - self.name = name - self.pool_id = pool_id - self.timeout = timeout - self.type = type - self.url_path = url_path - self.http_version = http_version - self.domain_name = domain_name - - -class L7Policy(BaseDataModel): - def __init__(self, action=Unset, admin_state_up=Unset, description=Unset, - l7policy_id=Unset, listener_id=Unset, name=Unset, - position=Unset, redirect_pool_id=Unset, redirect_url=Unset, - rules=Unset, redirect_prefix=Unset, redirect_http_code=Unset): - - self.action = action - self.admin_state_up = admin_state_up - self.description = description - self.l7policy_id = l7policy_id - self.listener_id = listener_id - self.name = name - self.position = position - self.redirect_pool_id = redirect_pool_id - self.redirect_url = redirect_url - self.rules = rules - self.redirect_prefix = redirect_prefix - self.redirect_http_code = redirect_http_code - - -class L7Rule(BaseDataModel): - def __init__(self, admin_state_up=Unset, compare_type=Unset, invert=Unset, - key=Unset, l7policy_id=Unset, l7rule_id=Unset, type=Unset, - value=Unset): - - self.admin_state_up = admin_state_up - self.compare_type = compare_type - self.invert = invert - self.key = key - self.l7policy_id = l7policy_id - self.l7rule_id = l7rule_id - self.type = type - self.value = value - - -class VIP(BaseDataModel): - def __init__(self, vip_address=Unset, vip_network_id=Unset, - vip_port_id=Unset, vip_subnet_id=Unset, - vip_qos_policy_id=Unset): - - self.vip_address = vip_address - self.vip_network_id = vip_network_id - self.vip_port_id = vip_port_id - self.vip_subnet_id = vip_subnet_id - self.vip_qos_policy_id = vip_qos_policy_id +VIP = moves.moved_class(lib_data_models.VIP, 'VIP', __name__, + version='Stein', removal_version='U') diff --git a/octavia/api/drivers/driver_agent/__init__.py b/octavia/api/drivers/driver_agent/__init__.py new file mode 100644 index 0000000000..94e731d201 --- /dev/null +++ b/octavia/api/drivers/driver_agent/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/octavia/api/drivers/driver_agent/driver_listener.py b/octavia/api/drivers/driver_agent/driver_listener.py new file mode 100644 index 0000000000..a1172a77bb --- /dev/null +++ b/octavia/api/drivers/driver_agent/driver_listener.py @@ -0,0 +1,144 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import errno +import os +import signal +import threading + +import six.moves.socketserver as socketserver + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils + +from octavia.api.drivers.driver_agent import driver_updater + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +def _recv(recv_socket): + size_str = b'' + char = recv_socket.recv(1) + while char != b'\n': + size_str += char + char = recv_socket.recv(1) + payload_size = int(size_str) + mv_buffer = memoryview(bytearray(payload_size)) + next_offset = 0 + while payload_size - next_offset > 0: + recv_size = recv_socket.recv_into(mv_buffer[next_offset:], + payload_size - next_offset) + next_offset += recv_size + return jsonutils.loads(mv_buffer.tobytes()) + + +class StatusRequestHandler(socketserver.BaseRequestHandler): + + def handle(self): + # Get the update data + status = _recv(self.request) + + # Process the update + updater = driver_updater.DriverUpdater() + response = updater.update_loadbalancer_status(status) + + # Send the response + json_data = jsonutils.dump_as_bytes(response) + len_str = '{}\n'.format(len(json_data)).encode('utf-8') + self.request.send(len_str) + self.request.sendall(json_data) + + +class StatsRequestHandler(socketserver.BaseRequestHandler): + + def handle(self): + # Get the update data + stats = _recv(self.request) + + # Process the update + updater = driver_updater.DriverUpdater() + response = updater.update_listener_statistics(stats) + + # Send the response + json_data = jsonutils.dump_as_bytes(response) + len_str = '{}\n'.format(len(json_data)).encode('utf-8') + self.request.send(len_str) + self.request.sendall(json_data) + + +class ForkingUDSServer(socketserver.ForkingMixIn, + socketserver.UnixStreamServer): + pass + + +def _mutate_config(*args, **kwargs): + CONF.mutate_config_files() + + +def _cleanup_socket_file(filename): + # Remove the socket file if it already exists + try: + os.remove(filename) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + +def status_listener(exit_event): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGHUP, _mutate_config) + + _cleanup_socket_file(CONF.driver_agent.status_socket_path) + + server = ForkingUDSServer(CONF.driver_agent.status_socket_path, + StatusRequestHandler) + + server.timeout = CONF.driver_agent.status_request_timeout + server.max_children = CONF.driver_agent.status_max_processes + + while not exit_event.is_set(): + server.handle_request() + + LOG.info('Waiting for driver status listener to shutdown...') + # Can't shut ourselves down as we would deadlock, spawn a thread + threading.Thread(target=server.shutdown).start() + LOG.info('Driver status listener shutdown finished.') + server.server_close() + _cleanup_socket_file(CONF.driver_agent.status_socket_path) + + +def stats_listener(exit_event): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGHUP, _mutate_config) + + _cleanup_socket_file(CONF.driver_agent.stats_socket_path) + + server = ForkingUDSServer(CONF.driver_agent.stats_socket_path, + StatsRequestHandler) + + server.timeout = CONF.driver_agent.stats_request_timeout + server.max_children = CONF.driver_agent.stats_max_processes + + while not exit_event.is_set(): + server.handle_request() + + LOG.info('Waiting for driver statistics listener to shutdown...') + # Can't shut ourselves down as we would deadlock, spawn a thread + threading.Thread(target=server.shutdown).start() + LOG.info('Driver statistics listener shutdown finished.') + server.server_close() + _cleanup_socket_file(CONF.driver_agent.stats_socket_path) diff --git a/octavia/api/drivers/driver_agent/driver_updater.py b/octavia/api/drivers/driver_agent/driver_updater.py new file mode 100644 index 0000000000..c672a2a618 --- /dev/null +++ b/octavia/api/drivers/driver_agent/driver_updater.py @@ -0,0 +1,174 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from octavia_lib.api.drivers import exceptions as driver_exceptions +from octavia_lib.common import constants as lib_consts + +from octavia.common import constants as consts +from octavia.common import utils +from octavia.db import api as db_apis +from octavia.db import repositories as repo + + +class DriverUpdater(object): + + def __init__(self, **kwargs): + self.loadbalancer_repo = repo.LoadBalancerRepository() + self.listener_repo = repo.ListenerRepository() + self.pool_repo = repo.PoolRepository() + self.health_mon_repo = repo.HealthMonitorRepository() + self.member_repo = repo.MemberRepository() + self.l7policy_repo = repo.L7PolicyRepository() + self.l7rule_repo = repo.L7RuleRepository() + self.listener_stats_repo = repo.ListenerStatisticsRepository() + + self.db_session = db_apis.get_session() + super(DriverUpdater, self).__init__(**kwargs) + + def _check_for_lb_vip_deallocate(self, repo, lb_id): + lb = repo.get(self.db_session, id=lb_id) + if lb.vip.octavia_owned: + vip = lb.vip + # We need a backreference + vip.load_balancer = lb + # Only lookup the network driver if we have a VIP to deallocate + network_driver = utils.get_network_driver() + network_driver.deallocate_vip(vip) + + def _process_status_update(self, repo, object_name, record, + delete_record=False): + # Zero it out so that if the ID is missing from a record we do not + # report the last LB as the failed record in the exception + record_id = None + try: + record_id = record['id'] + record_kwargs = {} + prov_status = record.get(consts.PROVISIONING_STATUS, None) + if prov_status: + if (prov_status == consts.DELETED and + object_name == consts.LOADBALANCERS): + self._check_for_lb_vip_deallocate(repo, record_id) + elif prov_status == consts.DELETED and delete_record: + repo.delete(self.db_session, id=record_id) + return + record_kwargs[consts.PROVISIONING_STATUS] = prov_status + op_status = record.get(consts.OPERATING_STATUS, None) + if op_status: + record_kwargs[consts.OPERATING_STATUS] = op_status + if prov_status or op_status: + repo.update(self.db_session, record_id, **record_kwargs) + except Exception as e: + # We need to raise a failure here to notify the driver it is + # sending bad status data. + raise driver_exceptions.UpdateStatusError( + fault_string=str(e), status_object_id=record_id, + status_object=object_name) + + def update_loadbalancer_status(self, status): + """Update load balancer status. + + :param status: dictionary defining the provisioning status and + operating status for load balancer objects, including pools, + members, listeners, L7 policies, and L7 rules. + iod (string): ID for the object. + provisioning_status (string): Provisioning status for the object. + operating_status (string): Operating status for the object. + :type status: dict + :raises: UpdateStatusError + :returns: None + """ + try: + members = status.pop(consts.MEMBERS, []) + for member in members: + self._process_status_update(self.member_repo, consts.MEMBERS, + member, delete_record=True) + + health_mons = status.pop(consts.HEALTHMONITORS, []) + for health_mon in health_mons: + self._process_status_update( + self.health_mon_repo, consts.HEALTHMONITORS, health_mon, + delete_record=True) + + pools = status.pop(consts.POOLS, []) + for pool in pools: + self._process_status_update(self.pool_repo, consts.POOLS, + pool, delete_record=True) + + l7rules = status.pop(consts.L7RULES, []) + for l7rule in l7rules: + self._process_status_update(self.l7rule_repo, consts.L7RULES, + l7rule, delete_record=True) + + l7policies = status.pop(consts.L7POLICIES, []) + for l7policy in l7policies: + self._process_status_update( + self.l7policy_repo, consts.L7POLICIES, l7policy, + delete_record=True) + + listeners = status.pop(lib_consts.LISTENERS, []) + for listener in listeners: + self._process_status_update( + self.listener_repo, lib_consts.LISTENERS, listener, + delete_record=True) + + lbs = status.pop(consts.LOADBALANCERS, []) + for lb in lbs: + self._process_status_update(self.loadbalancer_repo, + consts.LOADBALANCERS, lb) + except driver_exceptions.UpdateStatusError as e: + return {lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.FAULT_STRING: e.fault_string, + lib_consts.STATUS_OBJECT: e.status_object, + lib_consts.STATUS_OBJECT_ID: e.status_object_id} + except Exception as e: + return {lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.FAULT_STRING: str(e)} + return {lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_OK} + + def update_listener_statistics(self, statistics): + """Update listener statistics. + + :param statistics: Statistics for listeners: + id (string): ID for listener. + active_connections (int): Number of currently active connections. + bytes_in (int): Total bytes received. + bytes_out (int): Total bytes sent. + request_errors (int): Total requests not fulfilled. + total_connections (int): The total connections handled. + :type statistics: dict + :raises: UpdateStatisticsError + :returns: None + """ + listener_stats = statistics.get(lib_consts.LISTENERS, []) + for stat in listener_stats: + try: + listener_id = stat.pop('id') + except Exception as e: + return { + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.FAULT_STRING: str(e), + lib_consts.STATS_OBJECT: lib_consts.LISTENERS} + # Provider drivers other than the amphora driver do not have + # an amphora ID, use the listener ID again here to meet the + # constraint requirement. + try: + self.listener_stats_repo.replace(self.db_session, listener_id, + listener_id, **stat) + except Exception as e: + return { + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.FAULT_STRING: str(e), + lib_consts.STATS_OBJECT: lib_consts.LISTENERS, + lib_consts.STATS_OBJECT_ID: listener_id} + return {lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_OK} diff --git a/octavia/api/drivers/driver_lib.py b/octavia/api/drivers/driver_lib.py index 2f605cfa3b..3bc4bddbe8 100644 --- a/octavia/api/drivers/driver_lib.py +++ b/octavia/api/drivers/driver_lib.py @@ -12,144 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. -from octavia.api.drivers import exceptions as driver_exceptions -from octavia.common import constants as consts -from octavia.common import utils -from octavia.db import api as db_apis -from octavia.db import repositories as repo +import warnings + +from debtcollector import moves + +from octavia_lib.api.drivers import driver_lib as lib_driver_lib -class DriverLibrary(object): +warnings.simplefilter('default', DeprecationWarning) - def __init__(self, **kwargs): - self.loadbalancer_repo = repo.LoadBalancerRepository() - self.listener_repo = repo.ListenerRepository() - self.pool_repo = repo.PoolRepository() - self.health_mon_repo = repo.HealthMonitorRepository() - self.member_repo = repo.MemberRepository() - self.l7policy_repo = repo.L7PolicyRepository() - self.l7rule_repo = repo.L7RuleRepository() - self.listener_stats_repo = repo.ListenerStatisticsRepository() - - self.db_session = db_apis.get_session() - super(DriverLibrary, self).__init__(**kwargs) - - def _check_for_lb_vip_deallocate(self, repo, lb_id): - lb = repo.get(self.db_session, id=lb_id) - if lb.vip.octavia_owned: - vip = lb.vip - # We need a backreference - vip.load_balancer = lb - # Only lookup the network driver if we have a VIP to deallocate - network_driver = utils.get_network_driver() - network_driver.deallocate_vip(vip) - - def _process_status_update(self, repo, object_name, record, - delete_record=False): - # Zero it out so that if the ID is missing from a record we do not - # report the last LB as the failed record in the exception - record_id = None - try: - record_id = record['id'] - record_kwargs = {} - prov_status = record.get(consts.PROVISIONING_STATUS, None) - if prov_status: - if (prov_status == consts.DELETED and - object_name == consts.LOADBALANCERS): - self._check_for_lb_vip_deallocate(repo, record_id) - elif prov_status == consts.DELETED and delete_record: - repo.delete(self.db_session, id=record_id) - return - record_kwargs[consts.PROVISIONING_STATUS] = prov_status - op_status = record.get(consts.OPERATING_STATUS, None) - if op_status: - record_kwargs[consts.OPERATING_STATUS] = op_status - if prov_status or op_status: - repo.update(self.db_session, record_id, **record_kwargs) - except Exception as e: - # We need to raise a failure here to notify the driver it is - # sending bad status data. - raise driver_exceptions.UpdateStatusError( - fault_string=str(e), status_object_id=record_id, - status_object=object_name) - - def update_loadbalancer_status(self, status): - """Update load balancer status. - - :param status: dictionary defining the provisioning status and - operating status for load balancer objects, including pools, - members, listeners, L7 policies, and L7 rules. - iod (string): ID for the object. - provisioning_status (string): Provisioning status for the object. - operating_status (string): Operating status for the object. - :type status: dict - :raises: UpdateStatusError - :returns: None - """ - members = status.pop(consts.MEMBERS, []) - for member in members: - self._process_status_update(self.member_repo, consts.MEMBERS, - member, delete_record=True) - - health_mons = status.pop(consts.HEALTHMONITORS, []) - for health_mon in health_mons: - self._process_status_update( - self.health_mon_repo, consts.HEALTHMONITORS, health_mon, - delete_record=True) - - pools = status.pop(consts.POOLS, []) - for pool in pools: - self._process_status_update(self.pool_repo, consts.POOLS, - pool, delete_record=True) - - l7rules = status.pop(consts.L7RULES, []) - for l7rule in l7rules: - self._process_status_update(self.l7rule_repo, consts.L7RULES, - l7rule, delete_record=True) - - l7policies = status.pop(consts.L7POLICIES, []) - for l7policy in l7policies: - self._process_status_update(self.l7policy_repo, consts.L7POLICIES, - l7policy, delete_record=True) - - listeners = status.pop(consts.LISTENERS, []) - for listener in listeners: - self._process_status_update(self.listener_repo, consts.LISTENERS, - listener, delete_record=True) - - lbs = status.pop(consts.LOADBALANCERS, []) - for lb in lbs: - self._process_status_update(self.loadbalancer_repo, - consts.LOADBALANCERS, lb) - - def update_listener_statistics(self, statistics): - """Update listener statistics. - - :param statistics: Statistics for listeners: - id (string): ID for listener. - active_connections (int): Number of currently active connections. - bytes_in (int): Total bytes received. - bytes_out (int): Total bytes sent. - request_errors (int): Total requests not fulfilled. - total_connections (int): The total connections handled. - :type statistics: dict - :raises: UpdateStatisticsError - :returns: None - """ - listener_stats = statistics.get('listeners', []) - for stat in listener_stats: - try: - listener_id = stat.pop('id') - except Exception as e: - raise driver_exceptions.UpdateStatisticsError( - fault_string=str(e), stats_object='listeners') - # Provider drivers other than the amphora driver do not have - # an amphora ID, use the listener ID again here to meet the - # constraint requirement. - try: - self.listener_stats_repo.replace(self.db_session, listener_id, - listener_id, **stat) - except Exception as e: - raise driver_exceptions.UpdateStatisticsError( - fault_string=str(e), stats_object='listeners', - stats_object_id=listener_id) +DriverLibrary = moves.moved_class( + lib_driver_lib.DriverLibrary, 'DriverLibrary', __name__, + version='Stein', removal_version='U') diff --git a/octavia/api/drivers/exceptions.py b/octavia/api/drivers/exceptions.py index d2a6c45912..21830a84ac 100644 --- a/octavia/api/drivers/exceptions.py +++ b/octavia/api/drivers/exceptions.py @@ -12,137 +12,30 @@ # License for the specific language governing permissions and limitations # under the License. -from octavia.i18n import _ +import warnings + +from debtcollector import moves + +from octavia_lib.api.drivers import exceptions as lib_exceptions -class DriverError(Exception): - """Catch all exception that drivers can raise. +warnings.simplefilter('default', DeprecationWarning) - This exception includes two strings: The user fault string and the - optional operator fault string. The user fault string, - "user_fault_string", will be provided to the API requester. The operator - fault string, "operator_fault_string", will be logged in the Octavia API - log file for the operator to use when debugging. +DriverError = moves.moved_class(lib_exceptions.DriverError, 'DriverError', + __name__, version='Stein', removal_version='U') - :param user_fault_string: String provided to the API requester. - :type user_fault_string: string - :param operator_fault_string: Optional string logged by the Octavia API - for the operator to use when debugging. - :type operator_fault_string: string - """ - user_fault_string = _("An unknown driver error occurred.") - operator_fault_string = _("An unknown driver error occurred.") +NotImplementedError = moves.moved_class( + lib_exceptions.NotImplementedError, 'NotImplementedError', __name__, + version='Stein', removal_version='U') - def __init__(self, *args, **kwargs): - self.user_fault_string = kwargs.pop('user_fault_string', - self.user_fault_string) - self.operator_fault_string = kwargs.pop('operator_fault_string', - self.operator_fault_string) - super(DriverError, self).__init__(*args, **kwargs) +UnsupportedOptionError = moves.moved_class( + lib_exceptions.UnsupportedOptionError, 'UnsupportedOptionError', __name__, + version='Stein', removal_version='U') +UpdateStatusError = moves.moved_class( + lib_exceptions.UpdateStatusError, 'UpdateStatusError', __name__, + version='Stein', removal_version='U') -class NotImplementedError(Exception): - """Exception raised when a driver does not implement an API function. - - :param user_fault_string: String provided to the API requester. - :type user_fault_string: string - :param operator_fault_string: Optional string logged by the Octavia API - for the operator to use when debugging. - :type operator_fault_string: string - """ - user_fault_string = _("This feature is not implemented by the provider.") - operator_fault_string = _("This feature is not implemented by this " - "provider.") - - def __init__(self, *args, **kwargs): - self.user_fault_string = kwargs.pop('user_fault_string', - self.user_fault_string) - self.operator_fault_string = kwargs.pop('operator_fault_string', - self.operator_fault_string) - super(NotImplementedError, self).__init__(*args, **kwargs) - - -class UnsupportedOptionError(Exception): - """Exception raised when a driver does not support an option. - - Provider drivers will validate that they can complete the request -- that - all options are supported by the driver. If the request fails validation, - drivers will raise an UnsupportedOptionError exception. For example, if a - driver does not support a flavor passed as an option to load balancer - create(), the driver will raise an UnsupportedOptionError and include a - message parameter providing an explanation of the failure. - - :param user_fault_string: String provided to the API requester. - :type user_fault_string: string - :param operator_fault_string: Optional string logged by the Octavia API - for the operator to use when debugging. - :type operator_fault_string: string - """ - user_fault_string = _("A specified option is not supported by this " - "provider.") - operator_fault_string = _("A specified option is not supported by this " - "provider.") - - def __init__(self, *args, **kwargs): - self.user_fault_string = kwargs.pop('user_fault_string', - self.user_fault_string) - self.operator_fault_string = kwargs.pop('operator_fault_string', - self.operator_fault_string) - super(UnsupportedOptionError, self).__init__(*args, **kwargs) - - -class UpdateStatusError(Exception): - """Exception raised when a status update fails. - - Each exception will include a message field that describes the - error and references to the failed record if available. - :param fault_string: String describing the fault. - :type fault_string: string - :param status_object: The object the fault occurred on. - :type status_object: string - :param status_object_id: The ID of the object that failed status update. - :type status_object_id: string - :param status_record: The status update record that caused the fault. - :type status_record: string - """ - fault_string = _("The status update had an unknown error.") - status_object = None - status_object_id = None - status_record = None - - def __init__(self, *args, **kwargs): - self.fault_string = kwargs.pop('fault_string', self.fault_string) - self.status_object = kwargs.pop('status_object', None) - self.status_object_id = kwargs.pop('status_object_id', None) - self.status_record = kwargs.pop('status_record', None) - - super(UpdateStatusError, self).__init__(*args, **kwargs) - - -class UpdateStatisticsError(Exception): - """Exception raised when a statistics update fails. - - Each exception will include a message field that describes the - error and references to the failed record if available. - :param fault_string: String describing the fault. - :type fault_string: string - :param status_object: The object the fault occurred on. - :type status_object: string - :param status_object_id: The ID of the object that failed stats update. - :type status_object_id: string - :param status_record: The stats update record that caused the fault. - :type status_record: string - """ - fault_string = _("The statistics update had an unknown error.") - stats_object = None - stats_object_id = None - stats_record = None - - def __init__(self, *args, **kwargs): - self.fault_string = kwargs.pop('fault_string', - self.fault_string) - self.stats_object = kwargs.pop('stats_object', None) - self.stats_object_id = kwargs.pop('stats_object_id', None) - self.stats_record = kwargs.pop('stats_record', None) - - super(UpdateStatisticsError, self).__init__(*args, **kwargs) +UpdateStatisticsError = moves.moved_class( + lib_exceptions.UpdateStatisticsError, 'UpdateStatisticsError', __name__, + version='Stein', removal_version='U') diff --git a/octavia/api/drivers/noop_driver/driver.py b/octavia/api/drivers/noop_driver/driver.py index d336b79fec..95a4b3bd1e 100644 --- a/octavia/api/drivers/noop_driver/driver.py +++ b/octavia/api/drivers/noop_driver/driver.py @@ -15,8 +15,8 @@ from oslo_log import log as logging from oslo_utils import uuidutils -from octavia.api.drivers import data_models -from octavia.api.drivers import provider_base as driver_base +from octavia_lib.api.drivers import data_models +from octavia_lib.api.drivers import provider_base as driver_base LOG = logging.getLogger(__name__) diff --git a/octavia/api/drivers/provider_base.py b/octavia/api/drivers/provider_base.py index b18e1cd644..4cef3387d6 100644 --- a/octavia/api/drivers/provider_base.py +++ b/octavia/api/drivers/provider_base.py @@ -12,470 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. -from octavia.api.drivers import exceptions +import warnings -# This class describes the abstraction of a provider driver interface. -# Load balancing provider drivers will implement this interface. +from debtcollector import moves + +from octavia_lib.api.drivers import provider_base as lib_provider_base -class ProviderDriver(object): - # name is for internal Octavia use and should not be used by drivers - name = None +warnings.simplefilter('default', DeprecationWarning) - # Load Balancer - def create_vip_port(self, loadbalancer_id, project_id, vip_dictionary): - """Creates a port for a load balancer VIP. - - If the driver supports creating VIP ports, the driver will create a - VIP port and return the vip_dictionary populated with the vip_port_id. - If the driver does not support port creation, the driver will raise - a NotImplementedError. - - :param loadbalancer_id: ID of loadbalancer. - :type loadbalancer_id: string - :param project_id: The project ID to create the VIP under. - :type project_id: string - :param: vip_dictionary: The VIP dictionary. - :type vip_dictionary: dict - :returns: VIP dictionary with vip_port_id. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: The driver does not support creating - VIP ports. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating VIP ' - 'ports.', - operator_fault_string='This provider does not support creating ' - 'VIP ports. Octavia will create it.') - - def loadbalancer_create(self, loadbalancer): - """Creates a new load balancer. - - :param loadbalancer: The load balancer object. - :type loadbalancer: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: The driver does not support create. - :raises UnsupportedOptionError: The driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'load balancers.', - operator_fault_string='This provider does not support creating ' - 'load balancers. What?') - - def loadbalancer_delete(self, loadbalancer, cascade=False): - """Deletes a load balancer. - - :param loadbalancer: The load balancer to delete. - :type loadbalancer: object - :param cascade: If True, deletes all child objects (listeners, - pools, etc.) in addition to the load balancer. - :type cascade: bool - :return: Nothing if the delete request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'load balancers.', - operator_fault_string='This provider does not support deleting ' - 'load balancers.') - - def loadbalancer_failover(self, loadbalancer_id): - """Performs a fail over of a load balancer. - - :param loadbalancer_id: ID of the load balancer to failover. - :type loadbalancer_id: string - :return: Nothing if the failover request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises: NotImplementedError if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support failing over ' - 'load balancers.', - operator_fault_string='This provider does not support failing ' - 'over load balancers.') - - def loadbalancer_update(self, old_loadbalancer, new_loadbalncer): - """Updates a load balancer. - - :param old_loadbalancer: The baseline load balancer object. - :type old_loadbalancer: object - :param new_loadbalancer: The updated load balancer object. - :type new_loadbalancer: object - :return: Nothing if the update request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: The driver does not support request. - :raises UnsupportedOptionError: The driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'load balancers.', - operator_fault_string='This provider does not support updating ' - 'load balancers.') - - # Listener - def listener_create(self, listener): - """Creates a new listener. - - :param listener: The listener object. - :type listener: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'listeners.', - operator_fault_string='This provider does not support creating ' - 'listeners.') - - def listener_delete(self, listener): - """Deletes a listener. - - :param listener: The listener to delete. - :type listener: object - :return: Nothing if the delete request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'listeners.', - operator_fault_string='This provider does not support deleting ' - 'listeners.') - - def listener_update(self, old_listener, new_listener): - """Updates a listener. - - :param old_listener: The baseline listener object. - :type old_listener: object - :param new_listener: The updated listener object. - :type new_listener: object - :return: Nothing if the update request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'listeners.', - operator_fault_string='This provider does not support updating ' - 'listeners.') - - # Pool - def pool_create(self, pool): - """Creates a new pool. - - :param pool: The pool object. - :type pool: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'pools.', - operator_fault_string='This provider does not support creating ' - 'pools.') - - def pool_delete(self, pool): - """Deletes a pool and its members. - - :param pool: The pool to delete. - :type pool: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'pools.', - operator_fault_string='This provider does not support deleting ' - 'pools.') - - def pool_update(self, old_pool, new_pool): - """Updates a pool. - - :param pool: The baseline pool object. - :type pool: object - :param pool: The updated pool object. - :type pool: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'pools.', - operator_fault_string='This provider does not support updating ' - 'pools.') - - # Member - def member_create(self, member): - """Creates a new member for a pool. - - :param member: The member object. - :type member: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'members.', - operator_fault_string='This provider does not support creating ' - 'members.') - - def member_delete(self, member): - """Deletes a pool member. - - :param member: The member to delete. - :type member: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'members.', - operator_fault_string='This provider does not support deleting ' - 'members.') - - def member_update(self, old_member, new_member): - """Updates a pool member. - - :param old_member: The baseline member object. - :type old_member: object - :param new_member: The updated member object. - :type new_member: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'members.', - operator_fault_string='This provider does not support updating ' - 'members.') - - def member_batch_update(self, members): - """Creates, updates, or deletes a set of pool members. - - :param members: List of member objects. - :type members: list - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support batch ' - 'updating members.', - operator_fault_string='This provider does not support batch ' - 'updating members.') - - # Health Monitor - def health_monitor_create(self, healthmonitor): - """Creates a new health monitor. - - :param healthmonitor: The health monitor object. - :type healthmonitor: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'health monitors.', - operator_fault_string='This provider does not support creating ' - 'health monitors.') - - def health_monitor_delete(self, healthmonitor): - """Deletes a healthmonitor_id. - - :param healthmonitor: The monitor to delete. - :type healthmonitor: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'health monitors.', - operator_fault_string='This provider does not support deleting ' - 'health monitors.') - - def health_monitor_update(self, old_healthmonitor, new_healthmonitor): - """Updates a health monitor. - - :param old_healthmonitor: The baseline health monitor object. - :type old_healthmonitor: object - :param new_healthmonitor: The updated health monitor object. - :type new_healthmonitor: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'health monitors.', - operator_fault_string='This provider does not support updating ' - 'health monitors.') - - # L7 Policy - def l7policy_create(self, l7policy): - """Creates a new L7 policy. - - :param l7policy: The L7 policy object. - :type l7policy: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'l7policies.', - operator_fault_string='This provider does not support creating ' - 'l7policies.') - - def l7policy_delete(self, l7policy): - """Deletes an L7 policy. - - :param l7policy: The L7 policy to delete. - :type l7policy: object - :return: Nothing if the delete request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'l7policies.', - operator_fault_string='This provider does not support deleting ' - 'l7policies.') - - def l7policy_update(self, old_l7policy, new_l7policy): - """Updates an L7 policy. - - :param old_l7policy: The baseline L7 policy object. - :type old_l7policy: object - :param new_l7policy: The updated L7 policy object. - :type new_l7policy: object - :return: Nothing if the update request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'l7policies.', - operator_fault_string='This provider does not support updating ' - 'l7policies.') - - # L7 Rule - def l7rule_create(self, l7rule): - """Creates a new L7 rule. - - :param l7rule: The L7 rule object. - :type l7rule: object - :return: Nothing if the create request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support creating ' - 'l7rules.', - operator_fault_string='This provider does not support creating ' - 'l7rules.') - - def l7rule_delete(self, l7rule): - """Deletes an L7 rule. - - :param l7rule: The L7 rule to delete. - :type l7rule: object - :return: Nothing if the delete request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support deleting ' - 'l7rules.', - operator_fault_string='This provider does not support deleting ' - 'l7rules.') - - def l7rule_update(self, old_l7rule, new_l7rule): - """Updates an L7 rule. - - :param old_l7rule: The baseline L7 rule object. - :type old_l7rule: object - :param new_l7rule: The updated L7 rule object. - :type new_l7rule: object - :return: Nothing if the update request was accepted. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: if driver does not support request. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support updating ' - 'l7rules.', - operator_fault_string='This provider does not support updating ' - 'l7rules.') - - # Flavor - def get_supported_flavor_metadata(self): - """Returns a dict of flavor metadata keys supported by this driver. - - The returned dictionary will include key/value pairs, 'name' and - 'description.' - - :returns: The flavor metadata dictionary - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: The driver does not support flavors. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support getting the ' - 'supported flavor metadata.', - operator_fault_string='This provider does not support getting ' - 'the supported flavor metadata.') - - def validate_flavor(self, flavor_metadata): - """Validates if driver can support the flavor. - - :param flavor_metadata: Dictionary with flavor metadata. - :type flavor_metadata: dict - :return: Nothing if the flavor is valid and supported. - :raises DriverError: An unexpected error occurred in the driver. - :raises NotImplementedError: The driver does not support flavors. - :raises UnsupportedOptionError: if driver does not - support one of the configuration options. - """ - raise exceptions.NotImplementedError( - user_fault_string='This provider does not support validating ' - 'flavors.', - operator_fault_string='This provider does not support validating ' - 'the supported flavor metadata.') +ProviderDriver = moves.moved_class( + lib_provider_base.ProviderDriver, 'ProviderDriver', __name__, + version='Stein', removal_version='U') diff --git a/octavia/cmd/driver_agent.py b/octavia/cmd/driver_agent.py new file mode 100644 index 0000000000..2a8da690fa --- /dev/null +++ b/octavia/cmd/driver_agent.py @@ -0,0 +1,84 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from functools import partial +import multiprocessing +import os +import signal +import sys + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_reports import guru_meditation_report as gmr + +from octavia.api.drivers.driver_agent import driver_listener +from octavia.common import service +from octavia import version + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +def _mutate_config(*args, **kwargs): + CONF.mutate_config_files() + + +def _handle_mutate_config(status_proc_pid, stats_proc_pid, *args, **kwargs): + LOG.info("Driver agent received HUP signal, mutating config.") + _mutate_config() + os.kill(status_proc_pid, signal.SIGHUP) + os.kill(stats_proc_pid, signal.SIGHUP) + + +def main(): + service.prepare_service(sys.argv) + + gmr.TextGuruMeditation.setup_autorun(version) + + processes = [] + exit_event = multiprocessing.Event() + + status_listener_proc = multiprocessing.Process( + name='status_listener', target=driver_listener.status_listener, + args=(exit_event,)) + processes.append(status_listener_proc) + + LOG.info("Driver agent status listener process starts:") + status_listener_proc.start() + + stats_listener_proc = multiprocessing.Process( + name='stats_listener', target=driver_listener.stats_listener, + args=(exit_event,)) + processes.append(stats_listener_proc) + + LOG.info("Driver agent statistics listener process starts:") + stats_listener_proc.start() + + def process_cleanup(*args, **kwargs): + LOG.info("Driver agent exiting due to signal") + exit_event.set() + status_listener_proc.join() + stats_listener_proc.join() + + signal.signal(signal.SIGTERM, process_cleanup) + signal.signal(signal.SIGHUP, partial( + _handle_mutate_config, status_listener_proc.pid, + stats_listener_proc.pid)) + + try: + for process in processes: + process.join() + except KeyboardInterrupt: + process_cleanup() diff --git a/octavia/common/config.py b/octavia/common/config.py index 4cb926312c..40a44249ee 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -601,6 +601,37 @@ audit_opts = [ 'enabled.')), ] +driver_agent_opts = [ + cfg.StrOpt('status_socket_path', + default='/var/run/octavia/status.sock', + help=_('Path to the driver status unix domain socket file.')), + cfg.StrOpt('stats_socket_path', + default='/var/run/octavia/stats.sock', + help=_('Path to the driver statistics unix domain socket ' + 'file.')), + cfg.IntOpt('status_request_timeout', + default=5, + help=_('Time, in seconds, to wait for a status update ' + 'request.')), + cfg.IntOpt('status_max_processes', + default=50, + help=_('Maximum number of concurrent processes to use ' + 'servicing status updates.')), + cfg.IntOpt('stats_request_timeout', + default=5, + help=_('Time, in seconds, to wait for a statistics update ' + 'request.')), + cfg.IntOpt('stats_max_processes', + default=50, + help=_('Maximum number of concurrent processes to use ' + 'servicing statistics updates.')), + cfg.FloatOpt('max_process_warning_percent', + default=0.75, min=0.01, max=0.99, + help=_('Percentage of max_processes (both status and stats) ' + 'in use to start logging warning messages about an ' + 'overloaded driver-agent.')), +] + # Register the configuration options cfg.CONF.register_opts(core_opts) cfg.CONF.register_opts(api_opts, group='api_settings') @@ -621,6 +652,7 @@ cfg.CONF.register_opts(glance_opts, group='glance') cfg.CONF.register_opts(neutron_opts, group='neutron') cfg.CONF.register_opts(quota_opts, group='quotas') cfg.CONF.register_opts(audit_opts, group='audit') +cfg.CONF.register_opts(driver_agent_opts, group='driver_agent') cfg.CONF.register_opts(local.certgen_opts, group='certificates') cfg.CONF.register_opts(local.certmgr_opts, group='certificates') diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 1b48721364..1979ba7664 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -12,48 +12,183 @@ # License for the specific language governing permissions and limitations # under the License. -LB_ALGORITHM_ROUND_ROBIN = 'ROUND_ROBIN' -LB_ALGORITHM_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS' -LB_ALGORITHM_SOURCE_IP = 'SOURCE_IP' -SUPPORTED_LB_ALGORITHMS = (LB_ALGORITHM_LEAST_CONNECTIONS, - LB_ALGORITHM_ROUND_ROBIN, - LB_ALGORITHM_SOURCE_IP) +from octavia_lib.common import constants as lib_consts -SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP' -SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE' -SESSION_PERSISTENCE_APP_COOKIE = 'APP_COOKIE' -SUPPORTED_SP_TYPES = (SESSION_PERSISTENCE_SOURCE_IP, - SESSION_PERSISTENCE_HTTP_COOKIE, - SESSION_PERSISTENCE_APP_COOKIE) +############################################################################## +# Constants common to the provider drivers moved to +# octavia_lib.common.constants +# These are deprecated, to be removed in the 'U' release +############################################################################## +# 'loadbalancers' +LOADBALANCERS = lib_consts.LOADBALANCERS +# 'listeners' +LISTENERS = lib_consts.LISTENERS +# 'pools' +POOLS = lib_consts.POOLS +# HEALTHMONITORS = 'healthmonitors' +HEALTHMONITORS = lib_consts.HEALTHMONITORS +# 'members' +MEMBERS = lib_consts.MEMBERS +# 'l7policies' +L7POLICIES = lib_consts.L7POLICIES +# 'l7rules' +L7RULES = lib_consts.L7RULES -HEALTH_MONITOR_PING = 'PING' -HEALTH_MONITOR_TCP = 'TCP' -HEALTH_MONITOR_HTTP = 'HTTP' -HEALTH_MONITOR_HTTPS = 'HTTPS' -HEALTH_MONITOR_TLS_HELLO = 'TLS-HELLO' -HEALTH_MONITOR_UDP_CONNECT = 'UDP-CONNECT' -SUPPORTED_HEALTH_MONITOR_TYPES = (HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS, - HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP, - HEALTH_MONITOR_TLS_HELLO, - HEALTH_MONITOR_UDP_CONNECT) -HEALTH_MONITOR_HTTP_METHOD_GET = 'GET' -HEALTH_MONITOR_HTTP_METHOD_HEAD = 'HEAD' -HEALTH_MONITOR_HTTP_METHOD_POST = 'POST' -HEALTH_MONITOR_HTTP_METHOD_PUT = 'PUT' -HEALTH_MONITOR_HTTP_METHOD_DELETE = 'DELETE' -HEALTH_MONITOR_HTTP_METHOD_TRACE = 'TRACE' -HEALTH_MONITOR_HTTP_METHOD_OPTIONS = 'OPTIONS' -HEALTH_MONITOR_HTTP_METHOD_CONNECT = 'CONNECT' -HEALTH_MONITOR_HTTP_METHOD_PATCH = 'PATCH' -HEALTH_MONITOR_HTTP_DEFAULT_METHOD = HEALTH_MONITOR_HTTP_METHOD_GET +# 'PING' +HEALTH_MONITOR_PING = lib_consts.HEALTH_MONITOR_PING +# 'TCP' +HEALTH_MONITOR_TCP = lib_consts.HEALTH_MONITOR_TCP +# 'HTTP' +HEALTH_MONITOR_HTTP = lib_consts.HEALTH_MONITOR_HTTP +# 'HTTPS' +HEALTH_MONITOR_HTTPS = lib_consts.HEALTH_MONITOR_HTTPS +# 'TLS-HELLO' +HEALTH_MONITOR_TLS_HELLO = lib_consts.HEALTH_MONITOR_TLS_HELLO +# 'UDP-CONNECT' +HEALTH_MONITOR_UDP_CONNECT = lib_consts.HEALTH_MONITOR_UDP_CONNECT +SUPPORTED_HEALTH_MONITOR_TYPES = lib_consts.SUPPORTED_HEALTH_MONITOR_TYPES + +# 'GET' +HEALTH_MONITOR_HTTP_METHOD_GET = lib_consts.HEALTH_MONITOR_HTTP_METHOD_GET +# 'HEAD' +HEALTH_MONITOR_HTTP_METHOD_HEAD = lib_consts.HEALTH_MONITOR_HTTP_METHOD_HEAD +# 'POST' +HEALTH_MONITOR_HTTP_METHOD_POST = lib_consts.HEALTH_MONITOR_HTTP_METHOD_POST +# 'PUT' +HEALTH_MONITOR_HTTP_METHOD_PUT = lib_consts.HEALTH_MONITOR_HTTP_METHOD_PUT +# 'DELETE' +HEALTH_MONITOR_HTTP_METHOD_DELETE = ( + lib_consts.HEALTH_MONITOR_HTTP_METHOD_DELETE) +# 'TRACE' +HEALTH_MONITOR_HTTP_METHOD_TRACE = lib_consts.HEALTH_MONITOR_HTTP_METHOD_TRACE +# 'OPTIONS' +HEALTH_MONITOR_HTTP_METHOD_OPTIONS = ( + lib_consts.HEALTH_MONITOR_HTTP_METHOD_OPTIONS) +# 'CONNECT' +HEALTH_MONITOR_HTTP_METHOD_CONNECT = ( + lib_consts.HEALTH_MONITOR_HTTP_METHOD_CONNECT) +# 'PATCH' +HEALTH_MONITOR_HTTP_METHOD_PATCH = lib_consts.HEALTH_MONITOR_HTTP_METHOD_PATCH SUPPORTED_HEALTH_MONITOR_HTTP_METHODS = ( - HEALTH_MONITOR_HTTP_METHOD_GET, HEALTH_MONITOR_HTTP_METHOD_HEAD, - HEALTH_MONITOR_HTTP_METHOD_POST, HEALTH_MONITOR_HTTP_METHOD_PUT, - HEALTH_MONITOR_HTTP_METHOD_DELETE, HEALTH_MONITOR_HTTP_METHOD_TRACE, - HEALTH_MONITOR_HTTP_METHOD_OPTIONS, HEALTH_MONITOR_HTTP_METHOD_CONNECT, - HEALTH_MONITOR_HTTP_METHOD_PATCH) -SUPPORTED_HTTP_VERSIONS = [1.0, 1.1] + lib_consts.SUPPORTED_HEALTH_MONITOR_HTTP_METHODS) + +# 'REJECT' +L7POLICY_ACTION_REJECT = lib_consts.L7POLICY_ACTION_REJECT +# 'REDIRECT_TO_URL' +L7POLICY_ACTION_REDIRECT_TO_URL = lib_consts.L7POLICY_ACTION_REDIRECT_TO_URL +# 'REDIRECT_TO_POOL' +L7POLICY_ACTION_REDIRECT_TO_POOL = lib_consts.L7POLICY_ACTION_REDIRECT_TO_POOL +# 'REDIRECT_PREFIX' +L7POLICY_ACTION_REDIRECT_PREFIX = lib_consts.L7POLICY_ACTION_REDIRECT_PREFIX +SUPPORTED_L7POLICY_ACTIONS = lib_consts.SUPPORTED_L7POLICY_ACTIONS + +# 'REGEX' +L7RULE_COMPARE_TYPE_REGEX = lib_consts.L7RULE_COMPARE_TYPE_REGEX +# 'STARTS_WITH' +L7RULE_COMPARE_TYPE_STARTS_WITH = lib_consts.L7RULE_COMPARE_TYPE_STARTS_WITH +# 'ENDS_WITH' +L7RULE_COMPARE_TYPE_ENDS_WITH = lib_consts.L7RULE_COMPARE_TYPE_ENDS_WITH +# 'CONTAINS' +L7RULE_COMPARE_TYPE_CONTAINS = lib_consts.L7RULE_COMPARE_TYPE_CONTAINS +# 'EQUAL_TO' +L7RULE_COMPARE_TYPE_EQUAL_TO = lib_consts.L7RULE_COMPARE_TYPE_EQUAL_TO +SUPPORTED_L7RULE_COMPARE_TYPES = lib_consts.SUPPORTED_L7RULE_COMPARE_TYPES + +# 'HOST_NAME' +L7RULE_TYPE_HOST_NAME = lib_consts.L7RULE_TYPE_HOST_NAME +# 'PATH' +L7RULE_TYPE_PATH = lib_consts.L7RULE_TYPE_PATH +# 'FILE_TYPE' +L7RULE_TYPE_FILE_TYPE = lib_consts.L7RULE_TYPE_FILE_TYPE +# 'HEADER' +L7RULE_TYPE_HEADER = lib_consts.L7RULE_TYPE_HEADER +# 'COOKIE' +L7RULE_TYPE_COOKIE = lib_consts.L7RULE_TYPE_COOKIE +# 'SSL_CONN_HAS_CERT' +L7RULE_TYPE_SSL_CONN_HAS_CERT = lib_consts.L7RULE_TYPE_SSL_CONN_HAS_CERT +# 'SSL_VERIFY_RESULT' +L7RULE_TYPE_SSL_VERIFY_RESULT = lib_consts.L7RULE_TYPE_SSL_VERIFY_RESULT +# 'SSL_DN_FIELD' +L7RULE_TYPE_SSL_DN_FIELD = lib_consts.L7RULE_TYPE_SSL_DN_FIELD +SUPPORTED_L7RULE_TYPES = lib_consts.SUPPORTED_L7RULE_TYPES + +# 'ROUND_ROBIN' +LB_ALGORITHM_ROUND_ROBIN = lib_consts.LB_ALGORITHM_ROUND_ROBIN +# 'LEAST_CONNECTIONS' +LB_ALGORITHM_LEAST_CONNECTIONS = lib_consts.LB_ALGORITHM_LEAST_CONNECTIONS +# 'SOURCE_IP' +LB_ALGORITHM_SOURCE_IP = lib_consts.LB_ALGORITHM_SOURCE_IP +SUPPORTED_LB_ALGORITHMS = lib_consts.SUPPORTED_LB_ALGORITHMS + +# 'operating_status' +OPERATING_STATUS = lib_consts.OPERATING_STATUS +# 'ONLINE' +ONLINE = lib_consts.ONLINE +# 'OFFLINE' +OFFLINE = lib_consts.OFFLINE +# 'DEGRADED' +DEGRADED = lib_consts.DEGRADED +# 'ERROR' +ERROR = lib_consts.ERROR +# 'DRAINING' +DRAINING = lib_consts.DRAINING +# 'NO_MONITOR' +NO_MONITOR = lib_consts.NO_MONITOR +# 'operating_status' +SUPPORTED_OPERATING_STATUSES = lib_consts.SUPPORTED_OPERATING_STATUSES + +# 'TCP' +PROTOCOL_TCP = lib_consts.PROTOCOL_TCP +# 'UDP' +PROTOCOL_UDP = lib_consts.PROTOCOL_UDP +# 'HTTP' +PROTOCOL_HTTP = lib_consts.PROTOCOL_HTTP +# 'HTTPS' +PROTOCOL_HTTPS = lib_consts.PROTOCOL_HTTPS +# 'TERMINATED_HTTPS' +PROTOCOL_TERMINATED_HTTPS = lib_consts.PROTOCOL_TERMINATED_HTTPS +# 'PROXY' +PROTOCOL_PROXY = lib_consts.PROTOCOL_PROXY +SUPPORTED_PROTOCOLS = lib_consts.SUPPORTED_PROTOCOLS + +# 'provisioning_status' +PROVISIONING_STATUS = lib_consts.PROVISIONING_STATUS +# Amphora has been allocated to a load balancer 'ALLOCATED' +AMPHORA_ALLOCATED = lib_consts.AMPHORA_ALLOCATED +# Amphora is being built 'BOOTING' +AMPHORA_BOOTING = lib_consts.AMPHORA_BOOTING +# Amphora is ready to be allocated to a load balancer 'READY' +AMPHORA_READY = lib_consts.AMPHORA_READY +# 'ACTIVE' +ACTIVE = lib_consts.ACTIVE +# 'PENDING_DELETE' +PENDING_DELETE = lib_consts.PENDING_DELETE +# 'PENDING_UPDATE' +PENDING_UPDATE = lib_consts.PENDING_UPDATE +# 'PENDING_CREATE' +PENDING_CREATE = lib_consts.PENDING_CREATE +# 'DELETED' +DELETED = lib_consts.DELETED +SUPPORTED_PROVISIONING_STATUSES = lib_consts.SUPPORTED_PROVISIONING_STATUSES + +# 'SOURCE_IP' +SESSION_PERSISTENCE_SOURCE_IP = lib_consts.SESSION_PERSISTENCE_SOURCE_IP +# 'HTTP_COOKIE' +SESSION_PERSISTENCE_HTTP_COOKIE = lib_consts.SESSION_PERSISTENCE_HTTP_COOKIE +# 'APP_COOKIE' +SESSION_PERSISTENCE_APP_COOKIE = lib_consts.SESSION_PERSISTENCE_APP_COOKIE +SUPPORTED_SP_TYPES = lib_consts.SUPPORTED_SP_TYPES + +# List of HTTP headers which are supported for insertion +SUPPORTED_HTTP_HEADERS = lib_consts.SUPPORTED_HTTP_HEADERS + +# List of SSL headers for client certificate +SUPPORTED_SSL_HEADERS = lib_consts.SUPPORTED_SSL_HEADERS + +############################################################################### + HEALTH_MONITOR_DEFAULT_EXPECTED_CODES = '200' +HEALTH_MONITOR_HTTP_DEFAULT_METHOD = lib_consts.HEALTH_MONITOR_HTTP_METHOD_GET HEALTH_MONITOR_DEFAULT_URL_PATH = '/' TYPE = 'type' URL_PATH = 'url_path' @@ -67,15 +202,6 @@ RISE_THRESHOLD = 'rise_threshold' UPDATE_STATS = 'UPDATE_STATS' UPDATE_HEALTH = 'UPDATE_HEALTH' -PROTOCOL_TCP = 'TCP' -PROTOCOL_UDP = 'UDP' -PROTOCOL_HTTP = 'HTTP' -PROTOCOL_HTTPS = 'HTTPS' -PROTOCOL_TERMINATED_HTTPS = 'TERMINATED_HTTPS' -PROTOCOL_PROXY = 'PROXY' -SUPPORTED_PROTOCOLS = (PROTOCOL_TCP, PROTOCOL_HTTPS, PROTOCOL_HTTP, - PROTOCOL_TERMINATED_HTTPS, PROTOCOL_PROXY, PROTOCOL_UDP) - # API Integer Ranges MIN_PORT_NUMBER = 1 MAX_PORT_NUMBER = 65535 @@ -97,86 +223,27 @@ DEFAULT_TIMEOUT_MEMBER_CONNECT = 5000 DEFAULT_TIMEOUT_MEMBER_DATA = 50000 DEFAULT_TIMEOUT_TCP_INSPECT = 0 +MUTABLE_STATUSES = (lib_consts.ACTIVE,) +DELETABLE_STATUSES = (lib_consts.ACTIVE, lib_consts.ERROR) +FAILOVERABLE_STATUSES = (lib_consts.ACTIVE, lib_consts.ERROR) + # Note: The database Amphora table has a foreign key constraint against # the provisioning_status table -# Amphora has been allocated to a load balancer -AMPHORA_ALLOCATED = 'ALLOCATED' -# Amphora is being built -AMPHORA_BOOTING = 'BOOTING' -# Amphora is ready to be allocated to a load balancer -AMPHORA_READY = 'READY' - -ACTIVE = 'ACTIVE' -PENDING_DELETE = 'PENDING_DELETE' -PENDING_UPDATE = 'PENDING_UPDATE' -PENDING_CREATE = 'PENDING_CREATE' -DELETED = 'DELETED' -ERROR = 'ERROR' -SUPPORTED_PROVISIONING_STATUSES = (ACTIVE, AMPHORA_ALLOCATED, - AMPHORA_BOOTING, AMPHORA_READY, - PENDING_DELETE, PENDING_CREATE, - PENDING_UPDATE, DELETED, ERROR) -MUTABLE_STATUSES = (ACTIVE,) -DELETABLE_STATUSES = (ACTIVE, ERROR) -FAILOVERABLE_STATUSES = (ACTIVE, ERROR) - -SUPPORTED_AMPHORA_STATUSES = (AMPHORA_ALLOCATED, AMPHORA_BOOTING, ERROR, - AMPHORA_READY, DELETED, - PENDING_CREATE, PENDING_DELETE) - -ONLINE = 'ONLINE' -OFFLINE = 'OFFLINE' -DEGRADED = 'DEGRADED' -ERROR = 'ERROR' -DRAINING = 'DRAINING' -NO_MONITOR = 'NO_MONITOR' -OPERATING_STATUS = 'operating_status' -PROVISIONING_STATUS = 'provisioning_status' -SUPPORTED_OPERATING_STATUSES = (ONLINE, OFFLINE, DEGRADED, ERROR, DRAINING, - NO_MONITOR) +SUPPORTED_AMPHORA_STATUSES = ( + lib_consts.AMPHORA_ALLOCATED, lib_consts.AMPHORA_BOOTING, lib_consts.ERROR, + lib_consts.AMPHORA_READY, lib_consts.DELETED, lib_consts.PENDING_CREATE, + lib_consts.PENDING_DELETE) AMPHORA_VM = 'VM' SUPPORTED_AMPHORA_TYPES = (AMPHORA_VM,) -# L7 constants -L7RULE_TYPE_HOST_NAME = 'HOST_NAME' -L7RULE_TYPE_PATH = 'PATH' -L7RULE_TYPE_FILE_TYPE = 'FILE_TYPE' -L7RULE_TYPE_HEADER = 'HEADER' -L7RULE_TYPE_COOKIE = 'COOKIE' -L7RULE_TYPE_SSL_CONN_HAS_CERT = 'SSL_CONN_HAS_CERT' -L7RULE_TYPE_SSL_VERIFY_RESULT = 'SSL_VERIFY_RESULT' -L7RULE_TYPE_SSL_DN_FIELD = 'SSL_DN_FIELD' -SUPPORTED_L7RULE_TYPES = (L7RULE_TYPE_HOST_NAME, L7RULE_TYPE_PATH, - L7RULE_TYPE_FILE_TYPE, L7RULE_TYPE_HEADER, - L7RULE_TYPE_COOKIE, L7RULE_TYPE_SSL_CONN_HAS_CERT, - L7RULE_TYPE_SSL_VERIFY_RESULT, - L7RULE_TYPE_SSL_DN_FIELD) -DISTINGUISHED_NAME_FIELD_REGEX = '^([a-zA-Z][A-Za-z0-9-]*)$' - -L7RULE_COMPARE_TYPE_REGEX = 'REGEX' -L7RULE_COMPARE_TYPE_STARTS_WITH = 'STARTS_WITH' -L7RULE_COMPARE_TYPE_ENDS_WITH = 'ENDS_WITH' -L7RULE_COMPARE_TYPE_CONTAINS = 'CONTAINS' -L7RULE_COMPARE_TYPE_EQUAL_TO = 'EQUAL_TO' -SUPPORTED_L7RULE_COMPARE_TYPES = (L7RULE_COMPARE_TYPE_REGEX, - L7RULE_COMPARE_TYPE_STARTS_WITH, - L7RULE_COMPARE_TYPE_ENDS_WITH, - L7RULE_COMPARE_TYPE_CONTAINS, - L7RULE_COMPARE_TYPE_EQUAL_TO) - -L7POLICY_ACTION_REJECT = 'REJECT' -L7POLICY_ACTION_REDIRECT_TO_URL = 'REDIRECT_TO_URL' -L7POLICY_ACTION_REDIRECT_TO_POOL = 'REDIRECT_TO_POOL' -L7POLICY_ACTION_REDIRECT_PREFIX = 'REDIRECT_PREFIX' -SUPPORTED_L7POLICY_ACTIONS = (L7POLICY_ACTION_REJECT, - L7POLICY_ACTION_REDIRECT_TO_URL, - L7POLICY_ACTION_REDIRECT_TO_POOL, - L7POLICY_ACTION_REDIRECT_PREFIX) +DISTINGUISHED_NAME_FIELD_REGEX = lib_consts.DISTINGUISHED_NAME_FIELD_REGEX # For redirect, only codes 301, 302, 303, 307 and 308 are # supported. SUPPORTED_L7POLICY_REDIRECT_HTTP_CODES = [301, 302, 303, 307, 308] +SUPPORTED_HTTP_VERSIONS = [1.0, 1.1] + MIN_POLICY_POSITION = 1 # Largest a 32-bit integer can be, which is a limitation # here if you're using MySQL, as most probably are. This just needs @@ -220,17 +287,14 @@ DELTAS = 'deltas' HEALTH_MON = 'health_mon' HEALTH_MONITOR = 'health_monitor' LISTENER = 'listener' -LISTENERS = 'listeners' LISTENER_ID = 'listener_id' LOADBALANCER = 'loadbalancer' -LOADBALANCERS = 'loadbalancers' LOADBALANCER_ID = 'loadbalancer_id' LOAD_BALANCER_ID = 'load_balancer_id' SERVER_GROUP_ID = 'server_group_id' ANTI_AFFINITY = 'anti-affinity' SOFT_ANTI_AFFINITY = 'soft-anti-affinity' MEMBER = 'member' -MEMBERS = 'members' MEMBER_ID = 'member_id' COMPUTE_ID = 'compute_id' COMPUTE_OBJ = 'compute_obj' @@ -239,7 +303,6 @@ AMPS_DATA = 'amps_data' NICS = 'nics' VIP = 'vip' POOL = 'pool' -POOLS = 'pools' POOL_CHILD_COUNT = 'pool_child_count' POOL_ID = 'pool_id' L7POLICY = 'l7policy' @@ -253,11 +316,8 @@ ADDED_PORTS = 'added_ports' PORTS = 'ports' MEMBER_PORTS = 'member_ports' TOPOLOGY = 'topology' -HEALTHMONITORS = 'healthmonitors' HEALTH_MONITOR_ID = 'health_monitor_id' -L7POLICIES = 'l7policies' L7POLICY_ID = 'l7policy_id' -L7RULES = 'l7rules' L7RULE_ID = 'l7rule_id' LOAD_BALANCER_UPDATES = 'load_balancer_updates' LISTENER_UPDATES = 'listener_updates' @@ -418,7 +478,6 @@ DEFAULT_VRRP_ID = 1 VRRP_PROTOCOL_NUM = 112 AUTH_HEADER_PROTOCOL_NUMBER = 51 - TEMPLATES = '/templates' AGENT_API_TEMPLATES = '/templates' @@ -474,17 +533,6 @@ PLUGGED_INTERFACES = '/var/lib/octavia/plugged_interfaces' HAPROXY_USER_GROUP_CFG = '/var/lib/octavia/haproxy-default-user-group.conf' AMPHORA_NAMESPACE = 'amphora-haproxy' -# List of HTTP headers which are supported for insertion -SUPPORTED_HTTP_HEADERS = ['X-Forwarded-For', - 'X-Forwarded-Port', - 'X-Forwarded-Proto'] - -# List of SSL headers for client certificate -SUPPORTED_SSL_HEADERS = ['X-SSL-Client-Verify', 'X-SSL-Client-Has-Cert', - 'X-SSL-Client-DN', 'X-SSL-Client-CN', - 'X-SSL-Issuer', 'X-SSL-Client-SHA1', - 'X-SSL-Client-Not-Before', 'X-SSL-Client-Not-After'] - FLOW_DOC_TITLES = {'AmphoraFlows': 'Amphora Flows', 'LoadBalancerFlows': 'Load Balancer Flows', 'ListenerFlows': 'Listener Flows', diff --git a/octavia/opts.py b/octavia/opts.py index c93bc62432..7b71cd1895 100644 --- a/octavia/opts.py +++ b/octavia/opts.py @@ -45,6 +45,7 @@ def list_opts(): ('glance', octavia.common.config.glance_opts), ('quotas', octavia.common.config.quota_opts), ('audit', octavia.common.config.audit_opts), + ('driver_agent', octavia.common.config.driver_agent_opts), add_auth_opts(), ] diff --git a/octavia/tests/unit/api/drivers/amphora_driver/test_amphora_driver.py b/octavia/tests/unit/api/drivers/amphora_driver/test_amphora_driver.py index 427542b8e6..1804e9d1a7 100644 --- a/octavia/tests/unit/api/drivers/amphora_driver/test_amphora_driver.py +++ b/octavia/tests/unit/api/drivers/amphora_driver/test_amphora_driver.py @@ -13,9 +13,12 @@ # under the License. import mock +from oslo_utils import uuidutils + +from octavia_lib.api.drivers import data_models as driver_dm +from octavia_lib.api.drivers import exceptions + from octavia.api.drivers.amphora_driver import driver -from octavia.api.drivers import data_models as driver_dm -from octavia.api.drivers import exceptions from octavia.common import constants as consts from octavia.network import base as network_base from octavia.tests.unit.api.drivers import sample_data_models @@ -101,6 +104,20 @@ class TestAmphoraDriver(base.TestRpc): consts.LOAD_BALANCER_UPDATES: lb_dict} mock_cast.assert_called_with({}, 'update_load_balancer', **payload) + @mock.patch('oslo_messaging.RPCClient.cast') + def test_loadbalancer_update_qos(self, mock_cast): + qos_policy_id = uuidutils.generate_uuid() + old_provider_lb = driver_dm.LoadBalancer( + loadbalancer_id=self.sample_data.lb_id) + provider_lb = driver_dm.LoadBalancer( + loadbalancer_id=self.sample_data.lb_id, + vip_qos_policy_id=qos_policy_id) + lb_dict = {'vip': {'vip_qos_policy_id': qos_policy_id}} + self.amp_driver.loadbalancer_update(old_provider_lb, provider_lb) + payload = {consts.LOAD_BALANCER_ID: self.sample_data.lb_id, + consts.LOAD_BALANCER_UPDATES: lb_dict} + mock_cast.assert_called_with({}, 'update_load_balancer', **payload) + # Listener @mock.patch('oslo_messaging.RPCClient.cast') def test_listener_create(self, mock_cast): diff --git a/octavia/tests/unit/api/drivers/driver_agent/__init__.py b/octavia/tests/unit/api/drivers/driver_agent/__init__.py new file mode 100644 index 0000000000..94e731d201 --- /dev/null +++ b/octavia/tests/unit/api/drivers/driver_agent/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/octavia/tests/unit/api/drivers/driver_agent/test_driver_listener.py b/octavia/tests/unit/api/drivers/driver_agent/test_driver_listener.py new file mode 100644 index 0000000000..bffd50d379 --- /dev/null +++ b/octavia/tests/unit/api/drivers/driver_agent/test_driver_listener.py @@ -0,0 +1,171 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import errno + +import mock + +from oslo_config import cfg +from oslo_serialization import jsonutils + +from octavia.api.drivers.driver_agent import driver_listener +import octavia.tests.unit.base as base + +CONF = cfg.CONF + + +class TestDriverListener(base.TestCase): + + def setUp(self): + super(TestDriverListener, self).setUp() + + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.memoryview') + def test_recv(self, mock_memoryview): + # TEST_STRING len() is 15 + TEST_STRING = '{"test": "msg"}' + ref_object = jsonutils.loads(TEST_STRING) + + mock_recv_socket = mock.MagicMock() + mock_recv = mock.MagicMock() + mock_recv.side_effect = [b'1', b'5', b'\n'] + mock_recv_socket.recv = mock_recv + mock_recv_socket.recv_into.return_value = 15 + mock_mv_buffer = mock.MagicMock() + mock_tobytes = mock.MagicMock() + mock_tobytes.return_value = TEST_STRING + mock_mv_buffer.tobytes = mock_tobytes + mock_memoryview.return_value = mock_mv_buffer + + result = driver_listener._recv(mock_recv_socket) + + self.assertEqual(ref_object, result) + calls = [mock.call(1), mock.call(1), mock.call(1)] + mock_recv.assert_has_calls(calls) + mock_memoryview.assert_called_once_with(bytearray(15)) + mock_recv_socket.recv_into.assert_called_once_with(mock_mv_buffer[0:], + 15) + + @mock.patch('octavia.api.drivers.driver_agent.driver_updater.' + 'DriverUpdater') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener._recv') + def test_StatusRequestHandler_handle(self, mock_recv, mock_driverupdater): + TEST_OBJECT = {"test": "msg"} + mock_recv.return_value = 'bogus' + mock_updater = mock.MagicMock() + mock_update_loadbalancer_status = mock.MagicMock() + mock_update_loadbalancer_status.return_value = TEST_OBJECT + mock_updater.update_loadbalancer_status = ( + mock_update_loadbalancer_status) + mock_driverupdater.return_value = mock_updater + mock_request = mock.MagicMock() + mock_send = mock.MagicMock() + mock_sendall = mock.MagicMock() + mock_request.send = mock_send + mock_request.sendall = mock_sendall + + StatusRequestHandler = driver_listener.StatusRequestHandler( + mock_request, 'bogus', 'bogus') + StatusRequestHandler.handle() + + mock_recv.assert_called_with(mock_request) + mock_update_loadbalancer_status.assert_called_with('bogus') + mock_send.assert_called_with(b'15\n') + mock_sendall.assert_called_with( + jsonutils.dumps(TEST_OBJECT).encode('utf-8')) + + @mock.patch('octavia.api.drivers.driver_agent.driver_updater.' + 'DriverUpdater') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener._recv') + def test_StatsRequestHandler_handle(self, mock_recv, mock_driverupdater): + TEST_OBJECT = {"test": "msg"} + mock_recv.return_value = 'bogus' + mock_updater = mock.MagicMock() + mock_update_listener_stats = mock.MagicMock() + mock_update_listener_stats.return_value = TEST_OBJECT + mock_updater.update_listener_statistics = (mock_update_listener_stats) + mock_driverupdater.return_value = mock_updater + mock_request = mock.MagicMock() + mock_send = mock.MagicMock() + mock_sendall = mock.MagicMock() + mock_request.send = mock_send + mock_request.sendall = mock_sendall + + StatsRequestHandler = driver_listener.StatsRequestHandler( + mock_request, 'bogus', 'bogus') + StatsRequestHandler.handle() + + mock_recv.assert_called_with(mock_request) + mock_update_listener_stats.assert_called_with('bogus') + mock_send.assert_called_with(b'15\n') + mock_sendall.assert_called_with(jsonutils.dump_as_bytes(TEST_OBJECT)) + + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.CONF') + def test_mutate_config(self, mock_conf): + driver_listener._mutate_config() + mock_conf.mutate_config_files.assert_called_once() + + @mock.patch('os.remove') + def test_cleanup_socket_file(self, mock_remove): + mock_remove.side_effect = [mock.DEFAULT, OSError, + OSError(errno.ENOENT, 'no_file')] + driver_listener._cleanup_socket_file('fake_filename') + mock_remove.assert_called_once_with('fake_filename') + + self.assertRaises(OSError, driver_listener._cleanup_socket_file, + 'fake_filename') + # Make sure we just pass if the file was not found + driver_listener._cleanup_socket_file('fake_filename') + + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.' + '_cleanup_socket_file') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.signal') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.' + 'ForkingUDSServer') + def test_status_listener(self, mock_forking_server, + mock_signal, mock_cleanup): + mock_server = mock.MagicMock() + mock_active_children = mock.PropertyMock( + side_effect=['a', 'a', 'a', + 'a' * CONF.driver_agent.status_max_processes, 'a', + 'a' * 1000, '']) + type(mock_server).active_children = mock_active_children + mock_forking_server.return_value = mock_server + mock_exit_event = mock.MagicMock() + mock_exit_event.is_set.side_effect = [False, False, False, False, True] + + driver_listener.status_listener(mock_exit_event) + mock_server.handle_request.assert_called() + mock_server.server_close.assert_called_once() + self.assertEqual(2, mock_cleanup.call_count) + + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.' + '_cleanup_socket_file') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.signal') + @mock.patch('octavia.api.drivers.driver_agent.driver_listener.' + 'ForkingUDSServer') + def test_stats_listener(self, mock_forking_server, + mock_signal, mock_cleanup): + mock_server = mock.MagicMock() + mock_active_children = mock.PropertyMock( + side_effect=['a', 'a', 'a', + 'a' * CONF.driver_agent.status_max_processes, 'a', + 'a' * 1000, '']) + type(mock_server).active_children = mock_active_children + mock_forking_server.return_value = mock_server + mock_exit_event = mock.MagicMock() + mock_exit_event.is_set.side_effect = [False, False, False, False, True] + + driver_listener.stats_listener(mock_exit_event) + mock_server.handle_request.assert_called() + mock_server.server_close.assert_called_once() diff --git a/octavia/tests/unit/api/drivers/driver_agent/test_driver_updater.py b/octavia/tests/unit/api/drivers/driver_agent/test_driver_updater.py new file mode 100644 index 0000000000..40e050ec3a --- /dev/null +++ b/octavia/tests/unit/api/drivers/driver_agent/test_driver_updater.py @@ -0,0 +1,295 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +import mock +from mock import call + +from octavia.api.drivers.driver_agent import driver_updater +import octavia.tests.unit.base as base +from octavia_lib.api.drivers import exceptions as driver_exceptions +from octavia_lib.common import constants as lib_consts + + +class TestDriverUpdater(base.TestCase): + + @mock.patch('octavia.db.repositories.LoadBalancerRepository') + @mock.patch('octavia.db.repositories.ListenerRepository') + @mock.patch('octavia.db.repositories.L7PolicyRepository') + @mock.patch('octavia.db.repositories.L7RuleRepository') + @mock.patch('octavia.db.repositories.PoolRepository') + @mock.patch('octavia.db.repositories.HealthMonitorRepository') + @mock.patch('octavia.db.repositories.MemberRepository') + @mock.patch('octavia.db.api.get_session') + def setUp(self, mock_get_session, mock_member_repo, mock_health_repo, + mock_pool_repo, mock_l7r_repo, mock_l7p_repo, mock_list_repo, + mock_lb_repo): + super(TestDriverUpdater, self).setUp() + self.mock_session = "FAKE_DB_SESSION" + mock_get_session.return_value = self.mock_session + + member_mock = mock.MagicMock() + mock_member_repo.return_value = member_mock + self.mock_member_repo = member_mock + health_mock = mock.MagicMock() + mock_health_repo.return_value = health_mock + self.mock_health_repo = health_mock + pool_mock = mock.MagicMock() + mock_pool_repo.return_value = pool_mock + self.mock_pool_repo = pool_mock + l7r_mock = mock.MagicMock() + mock_l7r_repo.return_value = l7r_mock + self.mock_l7r_repo = l7r_mock + l7p_mock = mock.MagicMock() + mock_l7p_repo.return_value = l7p_mock + self.mock_l7p_repo = l7p_mock + list_mock = mock.MagicMock() + mock_list_repo.return_value = list_mock + self.mock_list_repo = list_mock + lb_mock = mock.MagicMock() + mock_lb_repo.return_value = lb_mock + self.mock_lb_repo = lb_mock + self.driver_updater = driver_updater.DriverUpdater() + self.ref_ok_response = {lib_consts.STATUS_CODE: + lib_consts.DRVR_STATUS_CODE_OK} + + @mock.patch('octavia.common.utils.get_network_driver') + def test_check_for_lb_vip_deallocate(self, mock_get_net_drvr): + mock_repo = mock.MagicMock() + mock_lb = mock.MagicMock() + mock_vip = mock.MagicMock() + mock_octavia_owned = mock.PropertyMock(side_effect=[True, False]) + type(mock_vip).octavia_owned = mock_octavia_owned + mock_lb.vip = mock_vip + mock_repo.get.return_value = mock_lb + mock_net_drvr = mock.MagicMock() + mock_get_net_drvr.return_value = mock_net_drvr + + self.driver_updater._check_for_lb_vip_deallocate(mock_repo, 'bogus_id') + mock_net_drvr.deallocate_vip.assert_called_once_with(mock_vip) + + mock_net_drvr.reset_mock() + self.driver_updater._check_for_lb_vip_deallocate(mock_repo, 'bogus_id') + mock_net_drvr.deallocate_vip.assert_not_called() + + @mock.patch('octavia.api.drivers.driver_agent.driver_updater.' + 'DriverUpdater._check_for_lb_vip_deallocate') + def test_process_status_update(self, mock_deallocate): + mock_repo = mock.MagicMock() + list_dict = {"id": 2, + lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + list_prov_dict = {"id": 2, + lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE} + list_oper_dict = {"id": 2, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + list_deleted_dict = { + "id": 2, lib_consts.PROVISIONING_STATUS: lib_consts.DELETED, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + + # Test with full record + self.driver_updater._process_status_update(mock_repo, 'FakeName', + list_dict) + mock_repo.update.assert_called_once_with( + self.mock_session, 2, provisioning_status=lib_consts.ACTIVE, + operating_status=lib_consts.ONLINE) + mock_repo.delete.assert_not_called() + + # Test with only provisioning status record + mock_repo.reset_mock() + self.driver_updater._process_status_update(mock_repo, 'FakeName', + list_prov_dict) + mock_repo.update.assert_called_once_with( + self.mock_session, 2, provisioning_status=lib_consts.ACTIVE) + mock_repo.delete.assert_not_called() + + # Test with only operating status record + mock_repo.reset_mock() + self.driver_updater._process_status_update(mock_repo, 'FakeName', + list_oper_dict) + mock_repo.update.assert_called_once_with( + self.mock_session, 2, operating_status=lib_consts.ONLINE) + mock_repo.delete.assert_not_called() + + # Test with deleted but delete_record False + mock_repo.reset_mock() + self.driver_updater._process_status_update(mock_repo, 'FakeName', + list_deleted_dict) + mock_repo.update.assert_called_once_with( + self.mock_session, 2, provisioning_status=lib_consts.DELETED, + operating_status=lib_consts.ONLINE) + mock_repo.delete.assert_not_called() + + # Test with an empty update + mock_repo.reset_mock() + self.driver_updater._process_status_update(mock_repo, 'FakeName', + {"id": 2}) + mock_repo.update.assert_not_called() + mock_repo.delete.assert_not_called() + + # Test with deleted and delete_record True + mock_repo.reset_mock() + self.driver_updater._process_status_update( + mock_repo, 'FakeName', list_deleted_dict, delete_record=True) + mock_repo.delete.assert_called_once_with(self.mock_session, id=2) + mock_repo.update.assert_not_called() + + # Test with LB Delete + mock_repo.reset_mock() + self.driver_updater._process_status_update( + mock_repo, lib_consts.LOADBALANCERS, list_deleted_dict) + mock_deallocate.assert_called_once_with(mock_repo, 2) + + # Test with an exception + mock_repo.reset_mock() + mock_repo.update.side_effect = Exception('boom') + self.assertRaises(driver_exceptions.UpdateStatusError, + self.driver_updater._process_status_update, + mock_repo, 'FakeName', list_dict) + + # Test with no ID record + mock_repo.reset_mock() + self.assertRaises(driver_exceptions.UpdateStatusError, + self.driver_updater._process_status_update, + mock_repo, 'FakeName', {"fake": "data"}) + + @mock.patch('octavia.api.drivers.driver_agent.driver_updater.' + 'DriverUpdater._process_status_update') + def test_update_loadbalancer_status(self, mock_status_update): + mock_status_update.side_effect = [ + mock.DEFAULT, mock.DEFAULT, mock.DEFAULT, mock.DEFAULT, + mock.DEFAULT, mock.DEFAULT, mock.DEFAULT, + driver_exceptions.UpdateStatusError( + fault_string='boom', status_object='fruit', + status_object_id='1', status_record='grape'), + Exception('boom')] + lb_dict = {"id": 1, lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + list_dict = {"id": 2, + lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + pool_dict = {"id": 3, + lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + member_dict = {"id": 4, + lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + hm_dict = {"id": 5, lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + l7p_dict = {"id": 6, lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + l7r_dict = {"id": 7, lib_consts.PROVISIONING_STATUS: lib_consts.ACTIVE, + lib_consts.OPERATING_STATUS: lib_consts.ONLINE} + status_dict = {lib_consts.LOADBALANCERS: [lb_dict], + lib_consts.LISTENERS: [list_dict], + lib_consts.POOLS: [pool_dict], + lib_consts.MEMBERS: [member_dict], + lib_consts.HEALTHMONITORS: [hm_dict], + lib_consts.L7POLICIES: [l7p_dict], + lib_consts.L7RULES: [l7r_dict]} + + result = self.driver_updater.update_loadbalancer_status( + copy.deepcopy(status_dict)) + + calls = [call(self.mock_member_repo, lib_consts.MEMBERS, member_dict, + delete_record=True), + call(self.mock_health_repo, lib_consts.HEALTHMONITORS, + hm_dict, delete_record=True), + call(self.mock_pool_repo, lib_consts.POOLS, pool_dict, + delete_record=True), + call(self.mock_l7r_repo, lib_consts.L7RULES, l7r_dict, + delete_record=True), + call(self.mock_l7p_repo, lib_consts.L7POLICIES, l7p_dict, + delete_record=True), + call(self.mock_list_repo, lib_consts.LISTENERS, list_dict, + delete_record=True), + call(self.mock_lb_repo, lib_consts.LOADBALANCERS, + lb_dict)] + mock_status_update.assert_has_calls(calls) + self.assertEqual(self.ref_ok_response, result) + + # Test empty status updates + mock_status_update.reset_mock() + result = self.driver_updater.update_loadbalancer_status({}) + mock_status_update.assert_not_called() + self.assertEqual(self.ref_ok_response, result) + + # Test UpdateStatusError case + ref_update_status_error = { + lib_consts.FAULT_STRING: 'boom', + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.STATUS_OBJECT: 'fruit', + lib_consts.STATUS_OBJECT_ID: '1'} + result = self.driver_updater.update_loadbalancer_status( + copy.deepcopy(status_dict)) + self.assertEqual(ref_update_status_error, result) + + # Test general exceptions + result = self.driver_updater.update_loadbalancer_status( + copy.deepcopy(status_dict)) + self.assertEqual({ + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.FAULT_STRING: 'boom'}, result) + + @mock.patch('octavia.db.repositories.ListenerStatisticsRepository.replace') + def test_update_listener_statistics(self, mock_replace): + listener_stats_list = [{"id": 1, "active_connections": 10, + "bytes_in": 20, + "bytes_out": 30, + "request_errors": 40, + "total_connections": 50}, + {"id": 2, "active_connections": 60, + "bytes_in": 70, + "bytes_out": 80, + "request_errors": 90, + "total_connections": 100}] + listener_stats_dict = {"listeners": listener_stats_list} + + mock_replace.side_effect = [mock.DEFAULT, mock.DEFAULT, + Exception('boom')] + result = self.driver_updater.update_listener_statistics( + copy.deepcopy(listener_stats_dict)) + calls = [call(self.mock_session, 1, 1, active_connections=10, + bytes_in=20, bytes_out=30, request_errors=40, + total_connections=50), + call(self.mock_session, 2, 2, active_connections=60, + bytes_in=70, bytes_out=80, request_errors=90, + total_connections=100)] + mock_replace.assert_has_calls(calls) + self.assertEqual(self.ref_ok_response, result) + + # Test empty stats updates + mock_replace.reset_mock() + result = self.driver_updater.update_listener_statistics({}) + mock_replace.assert_not_called() + self.assertEqual(self.ref_ok_response, result) + + # Test missing ID + bad_id_dict = {"listeners": [{"notID": "one"}]} + result = self.driver_updater.update_listener_statistics(bad_id_dict) + ref_update_listener_stats_error = { + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.STATS_OBJECT: lib_consts.LISTENERS, + lib_consts.FAULT_STRING: "'id'"} + self.assertEqual(ref_update_listener_stats_error, result) + + # Test for replace exception + result = self.driver_updater.update_listener_statistics( + copy.deepcopy(listener_stats_dict)) + ref_update_listener_stats_error = { + lib_consts.STATUS_CODE: lib_consts.DRVR_STATUS_CODE_FAILED, + lib_consts.STATS_OBJECT: lib_consts.LISTENERS, + lib_consts.FAULT_STRING: 'boom', lib_consts.STATS_OBJECT_ID: 1} + self.assertEqual(ref_update_listener_stats_error, result) diff --git a/octavia/tests/unit/api/drivers/test_data_models.py b/octavia/tests/unit/api/drivers/test_data_models.py deleted file mode 100644 index f23271d649..0000000000 --- a/octavia/tests/unit/api/drivers/test_data_models.py +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright 2018 Rackspace, US Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from copy import deepcopy - -from oslo_utils import uuidutils - -from octavia.api.drivers import data_models -import octavia.tests.unit.base as base - - -class TestProviderDataModels(base.TestCase): - - def setUp(self): - super(TestProviderDataModels, self).setUp() - - self.loadbalancer_id = uuidutils.generate_uuid() - self.project_id = uuidutils.generate_uuid() - self.vip_address = '192.0.2.83' - self.vip_network_id = uuidutils.generate_uuid() - self.vip_port_id = uuidutils.generate_uuid() - self.vip_subnet_id = uuidutils.generate_uuid() - self.listener_id = uuidutils.generate_uuid() - self.vip_qos_policy_id = uuidutils.generate_uuid() - self.default_tls_container_ref = uuidutils.generate_uuid() - self.sni_container_ref_1 = uuidutils.generate_uuid() - self.sni_container_ref_2 = uuidutils.generate_uuid() - - self.ref_listener = data_models.Listener( - admin_state_up=True, - connection_limit=5000, - default_pool_id=None, - default_tls_container_data='default_cert_data', - default_tls_container_ref=self.default_tls_container_ref, - description=data_models.Unset, - insert_headers={'X-Forwarded-For': 'true'}, - l7policies=[], - listener_id=self.listener_id, - loadbalancer_id=self.loadbalancer_id, - name='super_listener', - protocol='avian', - protocol_port=42, - sni_container_data=['sni_cert_data_1', 'sni_cert_data_2'], - sni_container_refs=[self.sni_container_ref_1, - self.sni_container_ref_2]) - - self.ref_lb = data_models.LoadBalancer( - admin_state_up=False, - description='One great load balancer', - flavor={'cake': 'chocolate'}, - listeners=[self.ref_listener], - loadbalancer_id=self.loadbalancer_id, - name='favorite_lb', - project_id=self.project_id, - vip_address=self.vip_address, - vip_network_id=self.vip_network_id, - vip_port_id=self.vip_port_id, - vip_subnet_id=self.vip_subnet_id, - vip_qos_policy_id=self.vip_qos_policy_id) - - self.ref_lb_dict = {'project_id': self.project_id, - 'flavor': {'cake': 'chocolate'}, - 'vip_network_id': self.vip_network_id, - 'admin_state_up': False, - 'loadbalancer_id': self.loadbalancer_id, - 'vip_port_id': self.vip_port_id, - 'vip_address': self.vip_address, - 'description': 'One great load balancer', - 'vip_subnet_id': self.vip_subnet_id, - 'name': 'favorite_lb', - 'vip_qos_policy_id': self.vip_qos_policy_id} - - self.ref_listener = { - 'admin_state_up': True, - 'connection_limit': 5000, - 'default_pool_id': None, - 'default_tls_container_data': 'default_cert_data', - 'default_tls_container_ref': self.default_tls_container_ref, - 'insert_headers': {'X-Forwarded-For': 'true'}, - 'listener_id': self.listener_id, - 'l7policies': [], - 'loadbalancer_id': self.loadbalancer_id, - 'name': 'super_listener', - 'protocol': 'avian', - 'protocol_port': 42, - 'sni_container_data': ['sni_cert_data_1', 'sni_cert_data_2'], - 'sni_container_refs': [self.sni_container_ref_1, - self.sni_container_ref_2]} - - self.ref_lb_dict_with_listener = { - 'admin_state_up': False, - 'description': 'One great load balancer', - 'flavor': {'cake': 'chocolate'}, - 'listeners': [self.ref_listener], - 'loadbalancer_id': self.loadbalancer_id, - 'name': 'favorite_lb', - 'project_id': self.project_id, - 'vip_address': self.vip_address, - 'vip_network_id': self.vip_network_id, - 'vip_port_id': self.vip_port_id, - 'vip_subnet_id': self.vip_subnet_id, - 'vip_qos_policy_id': self.vip_qos_policy_id} - - def test_equality(self): - second_ref_lb = deepcopy(self.ref_lb) - - self.assertTrue(self.ref_lb == second_ref_lb) - - second_ref_lb.admin_state_up = True - - self.assertFalse(self.ref_lb == second_ref_lb) - - self.assertFalse(self.ref_lb == self.loadbalancer_id) - - def test_inequality(self): - second_ref_lb = deepcopy(self.ref_lb) - - self.assertFalse(self.ref_lb != second_ref_lb) - - second_ref_lb.admin_state_up = True - - self.assertTrue(self.ref_lb != second_ref_lb) - - self.assertTrue(self.ref_lb != self.loadbalancer_id) - - def test_to_dict(self): - ref_lb_converted_to_dict = self.ref_lb.to_dict() - - self.assertEqual(self.ref_lb_dict, ref_lb_converted_to_dict) - - def test_to_dict_private_attrs(self): - private_dict = {'_test': 'foo'} - ref_lb_converted_to_dict = self.ref_lb.to_dict(**private_dict) - - self.assertEqual(self.ref_lb_dict, ref_lb_converted_to_dict) - - def test_to_dict_partial(self): - ref_lb = data_models.LoadBalancer(loadbalancer_id=self.loadbalancer_id) - ref_lb_dict = {'loadbalancer_id': self.loadbalancer_id} - ref_lb_converted_to_dict = ref_lb.to_dict() - - self.assertEqual(ref_lb_dict, ref_lb_converted_to_dict) - - def test_to_dict_render_unsets(self): - - ref_lb_converted_to_dict = self.ref_lb.to_dict(render_unsets=True) - - new_ref_lib_dict = deepcopy(self.ref_lb_dict) - new_ref_lib_dict['pools'] = None - new_ref_lib_dict['listeners'] = None - - self.assertEqual(new_ref_lib_dict, ref_lb_converted_to_dict) - - def test_to_dict_recursive(self): - ref_lb_converted_to_dict = self.ref_lb.to_dict(recurse=True) - - self.assertEqual(self.ref_lb_dict_with_listener, - ref_lb_converted_to_dict) - - def test_to_dict_recursive_partial(self): - ref_lb = data_models.LoadBalancer( - loadbalancer_id=self.loadbalancer_id, - listeners=[self.ref_listener]) - - ref_lb_dict_with_listener = { - 'loadbalancer_id': self.loadbalancer_id, - 'listeners': [self.ref_listener]} - - ref_lb_converted_to_dict = ref_lb.to_dict(recurse=True) - - self.assertEqual(ref_lb_dict_with_listener, ref_lb_converted_to_dict) - - def test_to_dict_recursive_render_unset(self): - ref_lb = data_models.LoadBalancer( - admin_state_up=False, - description='One great load balancer', - flavor={'cake': 'chocolate'}, - listeners=[self.ref_listener], - loadbalancer_id=self.loadbalancer_id, - project_id=self.project_id, - vip_address=self.vip_address, - vip_network_id=self.vip_network_id, - vip_port_id=self.vip_port_id, - vip_subnet_id=self.vip_subnet_id, - vip_qos_policy_id=self.vip_qos_policy_id) - - ref_lb_dict_with_listener = deepcopy(self.ref_lb_dict_with_listener) - ref_lb_dict_with_listener['pools'] = None - ref_lb_dict_with_listener['name'] = None - - ref_lb_converted_to_dict = ref_lb.to_dict(recurse=True, - render_unsets=True) - - self.assertEqual(ref_lb_dict_with_listener, - ref_lb_converted_to_dict) - - def test_from_dict(self): - lb_object = data_models.LoadBalancer.from_dict(self.ref_lb_dict) - - self.assertEqual(self.ref_lb, lb_object) - - def test_unset_bool(self): - self.assertFalse(data_models.Unset) - - def test_unset_repr(self): - self.assertEqual('Unset', repr(data_models.Unset)) diff --git a/octavia/tests/unit/api/drivers/test_driver_lib.py b/octavia/tests/unit/api/drivers/test_driver_lib.py index 8b53ec0aa9..c94616e335 100644 --- a/octavia/tests/unit/api/drivers/test_driver_lib.py +++ b/octavia/tests/unit/api/drivers/test_driver_lib.py @@ -12,239 +12,32 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import octavia_lib.api.drivers.driver_lib as lib_driver_lib from octavia.api.drivers import driver_lib -from octavia.api.drivers import exceptions as driver_exceptions -from octavia.common import constants -from octavia.tests.unit import base +import octavia.tests.unit.base as base class TestDriverLib(base.TestCase): - @mock.patch('octavia.db.repositories.L7RuleRepository') - @mock.patch('octavia.db.repositories.L7PolicyRepository') - @mock.patch('octavia.db.repositories.HealthMonitorRepository') - @mock.patch('octavia.db.repositories.MemberRepository') - @mock.patch('octavia.db.repositories.PoolRepository') - @mock.patch('octavia.db.repositories.ListenerRepository') - @mock.patch('octavia.db.repositories.LoadBalancerRepository') - @mock.patch('octavia.db.api.get_session') - def setUp(self, mock_get_session, mock_lb_repo, mock_list_repo, - mock_pool_repo, mock_member_repo, mock_health_repo, - mock_l7p_repo, mock_l7r_repo): + + def setUp(self): super(TestDriverLib, self).setUp() - self.mock_session = "FAKE_DB_SESSION" - mock_get_session.return_value = self.mock_session - lb_mock = mock.MagicMock() - mock_lb_repo.return_value = lb_mock - self.mock_lb_repo = lb_mock - list_mock = mock.MagicMock() - mock_list_repo.return_value = list_mock - self.mock_list_repo = list_mock - pool_mock = mock.MagicMock() - mock_pool_repo.return_value = pool_mock - self.mock_pool_repo = pool_mock - member_mock = mock.MagicMock() - mock_member_repo.return_value = member_mock - self.mock_member_repo = member_mock - health_mock = mock.MagicMock() - mock_health_repo.return_value = health_mock - self.mock_health_repo = health_mock - l7p_mock = mock.MagicMock() - mock_l7p_repo.return_value = l7p_mock - self.mock_l7p_repo = l7p_mock - l7r_mock = mock.MagicMock() - mock_l7r_repo.return_value = l7r_mock - self.mock_l7r_repo = l7r_mock - self.driver_lib = driver_lib.DriverLibrary() - listener_stats_list = [{"id": 1, "active_connections": 10, - "bytes_in": 20, - "bytes_out": 30, - "request_errors": 40, - "total_connections": 50}, - {"id": 2, "active_connections": 60, - "bytes_in": 70, - "bytes_out": 80, - "request_errors": 90, - "total_connections": 100}] - self.listener_stats_dict = {"listeners": listener_stats_list} - @mock.patch('octavia.common.utils.get_network_driver') - def test_check_for_lb_vip_deallocate(self, mock_get_driver): - mock_repo = mock.MagicMock() - mock_lb = mock.MagicMock() - - # Test VIP not owned by Octavia - mock_lb.vip.octavia_owned = False - mock_repo.get.return_value = mock_lb - self.driver_lib._check_for_lb_vip_deallocate(mock_repo, 4) - mock_get_driver.assert_not_called() - - # Test VIP is owned by Octavia - mock_lb.vip.octavia_owned = True - mock_repo.get.return_value = mock_lb - mock_net_driver = mock.MagicMock() - mock_get_driver.return_value = mock_net_driver - self.driver_lib._check_for_lb_vip_deallocate(mock_repo, 4) - mock_net_driver.deallocate_vip.assert_called_once_with(mock_lb.vip) - - @mock.patch('octavia.api.drivers.driver_lib.DriverLibrary.' - '_check_for_lb_vip_deallocate') - def test_process_status_update(self, mock_deallocate): - mock_repo = mock.MagicMock() - list_dict = {"id": 2, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - list_prov_dict = {"id": 2, - constants.PROVISIONING_STATUS: constants.ACTIVE} - list_oper_dict = {"id": 2, - constants.OPERATING_STATUS: constants.ONLINE} - list_deleted_dict = { - "id": 2, constants.PROVISIONING_STATUS: constants.DELETED, - constants.OPERATING_STATUS: constants.ONLINE} - - # Test with full record - self.driver_lib._process_status_update(mock_repo, 'FakeName', - list_dict) - mock_repo.update.assert_called_once_with( - self.mock_session, 2, provisioning_status=constants.ACTIVE, - operating_status=constants.ONLINE) - mock_repo.delete.assert_not_called() - - # Test with only provisioning status record - mock_repo.reset_mock() - self.driver_lib._process_status_update(mock_repo, 'FakeName', - list_prov_dict) - mock_repo.update.assert_called_once_with( - self.mock_session, 2, provisioning_status=constants.ACTIVE) - mock_repo.delete.assert_not_called() - - # Test with only operating status record - mock_repo.reset_mock() - self.driver_lib._process_status_update(mock_repo, 'FakeName', - list_oper_dict) - mock_repo.update.assert_called_once_with( - self.mock_session, 2, operating_status=constants.ONLINE) - mock_repo.delete.assert_not_called() - - # Test with deleted but delete_record False - mock_repo.reset_mock() - self.driver_lib._process_status_update(mock_repo, 'FakeName', - list_deleted_dict) - mock_repo.update.assert_called_once_with( - self.mock_session, 2, provisioning_status=constants.DELETED, - operating_status=constants.ONLINE) - mock_repo.delete.assert_not_called() - - # Test with an empty update - mock_repo.reset_mock() - self.driver_lib._process_status_update(mock_repo, 'FakeName', - {"id": 2}) - mock_repo.update.assert_not_called() - mock_repo.delete.assert_not_called() - - # Test with deleted and delete_record True - mock_repo.reset_mock() - self.driver_lib._process_status_update( - mock_repo, 'FakeName', list_deleted_dict, delete_record=True) - mock_repo.delete.assert_called_once_with(self.mock_session, id=2) - mock_repo.update.assert_not_called() - - # Test with LB Delete - mock_repo.reset_mock() - self.driver_lib._process_status_update( - mock_repo, constants.LOADBALANCERS, list_deleted_dict) - mock_deallocate.assert_called_once_with(mock_repo, 2) - - # Test with an exception - mock_repo.reset_mock() - mock_repo.update.side_effect = Exception('boom') - self.assertRaises(driver_exceptions.UpdateStatusError, - self.driver_lib._process_status_update, - mock_repo, 'FakeName', list_dict) - - # Test with no ID record - mock_repo.reset_mock() - self.assertRaises(driver_exceptions.UpdateStatusError, - self.driver_lib._process_status_update, - mock_repo, 'FakeName', {"fake": "data"}) - - @mock.patch( - 'octavia.api.drivers.driver_lib.DriverLibrary._process_status_update') - def test_update_loadbalancer_status(self, mock_status_update): - lb_dict = {"id": 1, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - list_dict = {"id": 2, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - pool_dict = {"id": 3, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - member_dict = {"id": 4, - constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - hm_dict = {"id": 5, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - l7p_dict = {"id": 6, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - l7r_dict = {"id": 7, constants.PROVISIONING_STATUS: constants.ACTIVE, - constants.OPERATING_STATUS: constants.ONLINE} - status_dict = {constants.LOADBALANCERS: [lb_dict], - constants.LISTENERS: [list_dict], - constants.POOLS: [pool_dict], - constants.MEMBERS: [member_dict], - constants.HEALTHMONITORS: [hm_dict], - constants.L7POLICIES: [l7p_dict], - constants.L7RULES: [l7r_dict]} - - self.driver_lib.update_loadbalancer_status(status_dict) - - calls = [call(self.mock_member_repo, constants.MEMBERS, member_dict, - delete_record=True), - call(self.mock_health_repo, constants.HEALTHMONITORS, - hm_dict, delete_record=True), - call(self.mock_pool_repo, constants.POOLS, pool_dict, - delete_record=True), - call(self.mock_l7r_repo, constants.L7RULES, l7r_dict, - delete_record=True), - call(self.mock_l7p_repo, constants.L7POLICIES, l7p_dict, - delete_record=True), - call(self.mock_list_repo, constants.LISTENERS, list_dict, - delete_record=True), - call(self.mock_lb_repo, constants.LOADBALANCERS, - lb_dict)] - mock_status_update.assert_has_calls(calls) - - mock_status_update.reset_mock() - self.driver_lib.update_loadbalancer_status({}) - mock_status_update.assert_not_called() - - @mock.patch('octavia.db.repositories.ListenerStatisticsRepository.replace') - def test_update_listener_statistics(self, mock_replace): - self.driver_lib.update_listener_statistics(self.listener_stats_dict) - calls = [call(self.mock_session, 1, 1, active_connections=10, - bytes_in=20, bytes_out=30, request_errors=40, - total_connections=50), - call(self.mock_session, 2, 2, active_connections=60, - bytes_in=70, bytes_out=80, request_errors=90, - total_connections=100)] - mock_replace.assert_has_calls(calls) - - mock_replace.reset_mock() - self.driver_lib.update_listener_statistics({}) - mock_replace.assert_not_called() - - # Test missing ID - bad_id_dict = {"listeners": [{"notID": "one"}]} - self.assertRaises(driver_exceptions.UpdateStatisticsError, - self.driver_lib.update_listener_statistics, - bad_id_dict) - - # Coverage doesn't like this test as part of the above test - # So, broke it out in it's own test - @mock.patch('octavia.db.repositories.ListenerStatisticsRepository.replace') - def test_update_listener_statistics_exception(self, mock_replace): - - # Test stats exception - mock_replace.side_effect = Exception('boom') - self.assertRaises(driver_exceptions.UpdateStatisticsError, - self.driver_lib.update_listener_statistics, - self.listener_stats_dict) + # Silly test to check that debtcollector moves is working + def test_driver_lib_exists(self): + driver_lib_class = driver_lib.DriverLibrary() + self.assertIsInstance(driver_lib_class, lib_driver_lib.DriverLibrary) diff --git a/octavia/tests/unit/api/drivers/test_exceptions.py b/octavia/tests/unit/api/drivers/test_exceptions.py deleted file mode 100644 index 75f5878c38..0000000000 --- a/octavia/tests/unit/api/drivers/test_exceptions.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2018 Rackspace, US Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from octavia.api.drivers import exceptions -import octavia.tests.unit.base as base - - -class TestProviderExceptions(base.TestCase): - - def setUp(self): - super(TestProviderExceptions, self).setUp() - self.user_fault_string = 'Bad driver' - self.operator_fault_string = 'Fix bad driver.' - self.fault_object = 'MCP' - self.fault_object_id = '-1' - self.fault_record = 'skip' - - def test_DriverError(self): - driver_error = exceptions.DriverError( - user_fault_string=self.user_fault_string, - operator_fault_string=self.operator_fault_string) - - self.assertEqual(self.user_fault_string, - driver_error.user_fault_string) - self.assertEqual(self.operator_fault_string, - driver_error.operator_fault_string) - self.assertIsInstance(driver_error, Exception) - - def test_NotImplementedError(self): - not_implemented_error = exceptions.NotImplementedError( - user_fault_string=self.user_fault_string, - operator_fault_string=self.operator_fault_string) - - self.assertEqual(self.user_fault_string, - not_implemented_error.user_fault_string) - self.assertEqual(self.operator_fault_string, - not_implemented_error.operator_fault_string) - self.assertIsInstance(not_implemented_error, Exception) - - def test_UnsupportedOptionError(self): - unsupported_option_error = exceptions.UnsupportedOptionError( - user_fault_string=self.user_fault_string, - operator_fault_string=self.operator_fault_string) - - self.assertEqual(self.user_fault_string, - unsupported_option_error.user_fault_string) - self.assertEqual(self.operator_fault_string, - unsupported_option_error.operator_fault_string) - self.assertIsInstance(unsupported_option_error, Exception) - - def test_UpdateStatusError(self): - update_status_error = exceptions.UpdateStatusError( - fault_string=self.user_fault_string, - status_object=self.fault_object, - status_object_id=self.fault_object_id, - status_record=self.fault_record) - - self.assertEqual(self.user_fault_string, - update_status_error.fault_string) - self.assertEqual(self.fault_object, update_status_error.status_object) - self.assertEqual(self.fault_object_id, - update_status_error.status_object_id) - self.assertEqual(self.fault_record, update_status_error.status_record) - - def test_UpdateStatisticsError(self): - update_stats_error = exceptions.UpdateStatisticsError( - fault_string=self.user_fault_string, - stats_object=self.fault_object, - stats_object_id=self.fault_object_id, - stats_record=self.fault_record) - - self.assertEqual(self.user_fault_string, - update_stats_error.fault_string) - self.assertEqual(self.fault_object, update_stats_error.stats_object) - self.assertEqual(self.fault_object_id, - update_stats_error.stats_object_id) - self.assertEqual(self.fault_record, update_stats_error.stats_record) diff --git a/octavia/tests/unit/api/drivers/test_provider_base.py b/octavia/tests/unit/api/drivers/test_provider_base.py index 2d3f8944c2..86e7d63968 100644 --- a/octavia/tests/unit/api/drivers/test_provider_base.py +++ b/octavia/tests/unit/api/drivers/test_provider_base.py @@ -12,146 +12,33 @@ # License for the specific language governing permissions and limitations # under the License. -from octavia.api.drivers import exceptions -from octavia.api.drivers import provider_base as driver_base +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import octavia_lib.api.drivers.provider_base as lib_provider_base + +from octavia.api.drivers import provider_base import octavia.tests.unit.base as base class TestProviderBase(base.TestCase): - """Test base methods. - Tests that methods not implemented by the drivers raise - NotImplementedError. - """ def setUp(self): super(TestProviderBase, self).setUp() - self.driver = driver_base.ProviderDriver() - def test_create_vip_port(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.create_vip_port, - False, False, False) - - def test_loadbalancer_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.loadbalancer_create, - False) - - def test_loadbalancer_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.loadbalancer_delete, - False) - - def test_loadbalancer_failover(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.loadbalancer_failover, - False) - - def test_loadbalancer_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.loadbalancer_update, - False, False) - - def test_listener_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.listener_create, - False) - - def test_listener_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.listener_delete, - False) - - def test_listener_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.listener_update, - False, False) - - def test_pool_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.pool_create, - False) - - def test_pool_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.pool_delete, - False) - - def test_pool_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.pool_update, - False, False) - - def test_member_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.member_create, - False) - - def test_member_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.member_delete, - False) - - def test_member_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.member_update, - False, False) - - def test_member_batch_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.member_batch_update, - False) - - def test_health_monitor_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.health_monitor_create, - False) - - def test_health_monitor_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.health_monitor_delete, - False) - - def test_health_monitor_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.health_monitor_update, - False, False) - - def test_l7policy_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7policy_create, - False) - - def test_l7policy_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7policy_delete, - False) - - def test_l7policy_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7policy_update, - False, False) - - def test_l7rule_create(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7rule_create, - False) - - def test_l7rule_delete(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7rule_delete, - False) - - def test_l7rule_update(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.l7rule_update, - False, False) - - def test_get_supported_flavor_metadata(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.get_supported_flavor_metadata) - - def test_validate_flavor(self): - self.assertRaises(exceptions.NotImplementedError, - self.driver.validate_flavor, - False) + # Silly test to check that debtcollector moves is working + def test_provider_base_exists(self): + provider_base_class = provider_base.ProviderDriver() + self.assertIsInstance(provider_base_class, + lib_provider_base.ProviderDriver) diff --git a/octavia/tests/unit/cmd/test_driver_agent.py b/octavia/tests/unit/cmd/test_driver_agent.py new file mode 100644 index 0000000000..efc4444337 --- /dev/null +++ b/octavia/tests/unit/cmd/test_driver_agent.py @@ -0,0 +1,70 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import signal + +import mock + +import octavia.api.drivers.driver_agent.driver_listener +from octavia.cmd import driver_agent +from octavia.tests.unit import base + + +class TestDriverAgentCMD(base.TestCase): + + def setUp(self): + super(TestDriverAgentCMD, self).setUp() + + @mock.patch('os.kill') + @mock.patch('octavia.cmd.driver_agent.CONF') + def test_handle_mutate_config(self, mock_conf, mock_os_kill): + driver_agent._handle_mutate_config(1, 2) + mock_conf.mutate_config_files.assert_called_once() + os_calls = [mock.call(1, signal.SIGHUP), mock.call(2, signal.SIGHUP)] + mock_os_kill.assert_has_calls(os_calls, any_order=True) + + @mock.patch('signal.signal') + @mock.patch('octavia.cmd.driver_agent.multiprocessing') + @mock.patch('oslo_reports.guru_meditation_report.TextGuruMeditation.' + 'setup_autorun') + @mock.patch('octavia.common.service.prepare_service') + def test_main(self, mock_prep_srvc, mock_gmr, mock_multiprocessing, + mock_signal): + mock_exit_event = mock.MagicMock() + mock_multiprocessing.Event.return_value = mock_exit_event + mock_status_listener_proc = mock.MagicMock() + mock_stats_listener_proc = mock.MagicMock() + mock_multiprocessing.Process.side_effect = [mock_status_listener_proc, + mock_stats_listener_proc, + mock_status_listener_proc, + mock_stats_listener_proc] + driver_agent.main() + mock_prep_srvc.assert_called_once() + mock_gmr.assert_called_once() + mock_status_listener_proc.start.assert_called_once() + mock_stats_listener_proc.start.assert_called_once() + process_calls = [mock.call( + args=mock_exit_event, name='status_listener', + target=(octavia.api.drivers.driver_agent.driver_listener. + status_listener)), + mock.call( + args=mock_exit_event, name='stats_listener', + target=(octavia.api.drivers.driver_agent.driver_listener. + stats_listener))] + mock_multiprocessing.Process.has_calls(process_calls, any_order=True) + + # Test keyboard interrupt path + mock_stats_listener_proc.join.side_effect = [KeyboardInterrupt, None] + driver_agent.main() + mock_exit_event.set.assert_called_once() diff --git a/octavia/tests/unit/test_opts.py b/octavia/tests/unit/test_opts.py new file mode 100644 index 0000000000..07f4e3df58 --- /dev/null +++ b/octavia/tests/unit/test_opts.py @@ -0,0 +1,26 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from octavia import opts +import octavia.tests.unit.base as base + + +class TestOpts(base.TestCase): + + def setUp(self): + super(TestOpts, self).setUp() + + def test_list_opts(self): + opts_list = opts.list_opts()[0] + self.assertIn('DEFAULT', opts_list) diff --git a/octavia/tests/unit/test_version.py b/octavia/tests/unit/test_version.py new file mode 100644 index 0000000000..1ba5a1bd87 --- /dev/null +++ b/octavia/tests/unit/test_version.py @@ -0,0 +1,34 @@ +# Copyright 2018 Rackspace, US Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +import octavia.tests.unit.base as base +from octavia import version + + +class TestVersion(base.TestCase): + + def setUp(self): + super(TestVersion, self).setUp() + + def test_vendor_str(self): + self.assertEqual("OpenStack Foundation", version.vendor_string()) + + def test_product_string(self): + self.assertEqual("OpenStack Octavia", version.product_string()) + + @mock.patch('pbr.version.VersionInfo.version_string', return_value='0.0.0') + def test_version_str(self, mock_pbr): + self.assertEqual('0.0.0', version.version_string_with_package()) diff --git a/playbooks/legacy/grenade-devstack-octavia/run.yaml b/playbooks/legacy/grenade-devstack-octavia/run.yaml index 608d0f9435..327c326470 100644 --- a/playbooks/legacy/grenade-devstack-octavia/run.yaml +++ b/playbooks/legacy/grenade-devstack-octavia/run.yaml @@ -31,6 +31,7 @@ export PROJECTS="openstack-dev/grenade $PROJECTS" export PROJECTS="openstack/octavia $PROJECTS" + export PROJECTS="openstack/octavia-lib $PROJECTS" export PROJECTS="openstack/octavia-tempest-plugin $PROJECTS" export PROJECTS="openstack/python-octaviaclient $PROJECTS" export DEVSTACK_PROJECT_FROM_GIT="python-octaviaclient $DEVSTACK_PROJECT_FROM_GIT" diff --git a/playbooks/legacy/octavia-v1-dsvm-py3x-scenario/run.yaml b/playbooks/legacy/octavia-v1-dsvm-py3x-scenario/run.yaml index 170f3e832f..61002536b4 100644 --- a/playbooks/legacy/octavia-v1-dsvm-py3x-scenario/run.yaml +++ b/playbooks/legacy/octavia-v1-dsvm-py3x-scenario/run.yaml @@ -43,6 +43,7 @@ export PROJECTS="openstack/diskimage-builder $PROJECTS" export PROJECTS="openstack/tripleo-image-elements $PROJECTS" export PROJECTS="openstack/neutron-lbaas $PROJECTS" + export PROJECTS="openstack/octavia-lib $PROJECTS" export PROJECTS="openstack/octavia $PROJECTS" if ! [[ "$ZUUL_BRANCH" =~ "stable/newton"|"stable/ocata" ]]; then diff --git a/playbooks/legacy/octavia-v1-dsvm-scenario/run.yaml b/playbooks/legacy/octavia-v1-dsvm-scenario/run.yaml index 9501fd5d0e..30f7f0fed7 100644 --- a/playbooks/legacy/octavia-v1-dsvm-scenario/run.yaml +++ b/playbooks/legacy/octavia-v1-dsvm-scenario/run.yaml @@ -42,6 +42,7 @@ export PROJECTS="openstack/diskimage-builder $PROJECTS" export PROJECTS="openstack/tripleo-image-elements $PROJECTS" export PROJECTS="openstack/neutron-lbaas $PROJECTS" + export PROJECTS="openstack/octavia-lib $PROJECTS" export PROJECTS="openstack/octavia $PROJECTS" if ! [[ "$ZUUL_BRANCH" =~ "stable/newton"|"stable/ocata" ]]; then diff --git a/releasenotes/notes/Octavia-lib-transition-driver-agent-aeefef114898b8f5.yaml b/releasenotes/notes/Octavia-lib-transition-driver-agent-aeefef114898b8f5.yaml new file mode 100644 index 0000000000..62e66466bc --- /dev/null +++ b/releasenotes/notes/Octavia-lib-transition-driver-agent-aeefef114898b8f5.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + The Stein release of Octavia introduces the octavia-lib python module. + This library enables provider drivers to integrate easier with the Octavia + API by providing a shared set of coding objects and interfaces. +upgrade: + - | + The Stein release of Octavia adds the driver-agent controller process. + This process is deployed along with the Octavia API process and uses + unix domain sockets for communication between the provider drivers using + octavia-lib and the driver-agent. + When upgrading to Stein, operators should make sure that the + /var/run/octavia directry is available for the driver-agent with the + appropriate ownership and permissions for the driver-agent and API + processes to access it. The operator may need to make sure the driver-agent + process starts after installation. For example, a systemd service may need + to be created and enabled for it. diff --git a/requirements.txt b/requirements.txt index 650f75ea9a..fb25bd337d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,6 +44,8 @@ castellan>=0.16.0 # Apache-2.0 tenacity>=4.9.0 # Apache-2.0 distro>=1.2.0 # Apache-2.0 jsonschema>=2.6.0 # MIT +debtcollector>=1.19.0 # Apache-2.0 +octavia-lib>=1.1.1 # Apache-2.0 #for the amphora api Flask!=0.11,>=0.10 # BSD diff --git a/setup.cfg b/setup.cfg index e6bacc68f4..93073fdd7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,6 +49,7 @@ console_scripts = octavia-health-manager = octavia.cmd.health_manager:main octavia-housekeeping = octavia.cmd.house_keeping:main octavia-db-manage = octavia.db.migration.cli:main + octavia-driver-agent = octavia.cmd.driver_agent:main amphora-agent = octavia.cmd.agent:main haproxy-vrrp-check = octavia.cmd.haproxy_vrrp_check:main octavia-status = octavia.cmd.status:main diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index 89fbdabeec..d70c3fa3f2 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -8,6 +8,7 @@ - openstack/diskimage-builder - openstack/neutron-lbaas - openstack/octavia + - openstack/octavia-lib - openstack/python-barbicanclient - openstack/python-octaviaclient - openstack/tripleo-image-elements @@ -78,5 +79,6 @@ - openstack-dev/grenade - openstack-infra/devstack-gate - openstack/octavia + - openstack/octavia-lib - openstack/octavia-tempest-plugin - openstack/python-octaviaclient