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
This commit is contained in:
Michael Johnson 2018-10-26 17:05:22 -07:00 committed by Carlos Goncalves
parent c00faa7edd
commit 8997def2b5
42 changed files with 1594 additions and 2032 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"}

View File

@ -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 <https://developer.openstack.org/api-ref/load-balancer/v2/index.html#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

View File

@ -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

View File

@ -1,16 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by Microsoft Visio, SVG Export OctaviaComp.svg Page-1 -->
<!-- Generated by Microsoft Visio, SVG Export Octaviav2APIDriverAgent.svg Page-1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="11.6929in" height="8.26772in"
viewBox="0 0 841.89 595.276" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
<v:documentProperties v:langID="1033" v:metric="true" v:viewMarkup="false">
<v:userDefs>
<v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
<v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
</v:userDefs>
</v:documentProperties>
width="11.6929in" height="8.26772in" viewBox="0 0 841.89 595.276" xml:space="preserve" color-interpolation-filters="sRGB"
class="st17">
<style type="text/css">
<![CDATA[
.st1 {fill:#339933;fill-opacity:0.64;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
@ -18,16 +11,18 @@
.st3 {fill:#ffffff;font-family:Segoe UI;font-size:1.16666em}
.st4 {font-size:1em}
.st5 {fill:#d80073;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st6 {marker-end:url(#mrkr4-43);marker-start:url(#mrkr4-41);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st6 {marker-end:url(#mrkr4-44);marker-start:url(#mrkr4-42);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st7 {fill:#1897d5;fill-opacity:1;stroke:#1897d5;stroke-opacity:1;stroke-width:0.44247787610619}
.st8 {marker-end:url(#mrkr4-43);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st8 {marker-end:url(#mrkr4-44);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st9 {fill:#f09609;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st10 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st11 {fill:#339933;fill-opacity:0.79;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st12 {fill:#339933;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st13 {marker-start:url(#mrkr4-41);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st13 {marker-start:url(#mrkr4-42);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}
.st14 {fill:#a200ff;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
.st15 {marker-end:url(#mrkr4-161);stroke:#1897d5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
.st16 {fill:#1897d5;fill-opacity:1;stroke:#1897d5;stroke-opacity:1;stroke-width:0.47169811320755}
.st17 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]>
</style>
@ -35,162 +30,97 @@
<g id="lend4">
<path d="M 2 1 L 0 0 L 2 -1 L 2 1 " style="stroke:none"/>
</g>
<marker id="mrkr4-41" class="st7" v:arrowType="4" v:arrowSize="2" v:setback="4.34" refX="4.34" orient="auto"
markerUnits="strokeWidth" overflow="visible">
<marker id="mrkr4-42" class="st7" refX="4.34" orient="auto" markerUnits="strokeWidth" overflow="visible">
<use xlink:href="#lend4" transform="scale(2.26) "/>
</marker>
<marker id="mrkr4-43" class="st7" v:arrowType="4" v:arrowSize="2" v:setback="4.52" refX="-4.52" orient="auto"
markerUnits="strokeWidth" overflow="visible">
<marker id="mrkr4-44" class="st7" refX="-4.52" orient="auto" markerUnits="strokeWidth" overflow="visible">
<use xlink:href="#lend4" transform="scale(-2.26,-2.26) "/>
</marker>
<marker id="mrkr4-161" class="st16" refX="-4.24" orient="auto" markerUnits="strokeWidth" overflow="visible">
<use xlink:href="#lend4" transform="scale(-2.12,-2.12) "/>
</marker>
</defs>
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
<v:userDefs>
<v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
</v:userDefs>
<g>
<title>Page-1</title>
<v:pageProperties v:drawingScale="0.0393701" v:pageScale="0.0393701" v:drawingUnits="24" v:shadowOffsetX="8.50394"
v:shadowOffsetY="-8.50394"/>
<v:layer v:name="Connector" v:index="0"/>
<v:layer v:name="Flowchart" v:index="1"/>
<g id="shape1023-1" v:mID="1023" v:groupContext="shape" transform="translate(307.343,-28.3465)">
<g id="shape1023-1" transform="translate(305.179,-99.2126)">
<title>Parallelogram.1023</title>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<path d="M0 595.28 L102.05 595.28 L113.39 510.24 L11.34 510.24 L0 595.28 Z" class="st1"/>
</g>
<g id="shape1-3" v:mID="1" v:groupContext="shape" transform="translate(331.214,-253.382)">
<g id="shape1-3" transform="translate(329.049,-324.248)">
<title>Rectangle</title>
<desc>Controller Worker Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="223.228" cy="569.54" width="446.46" height="51.4712"/>
<rect x="0" y="543.804" width="446.457" height="51.4712" class="st2"/>
<text x="147.47" y="573.74" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Controller Worker Driver</text> </g>
<g id="shape2-6" v:mID="2" v:groupContext="shape" transform="translate(451.214,-166.925)">
<text x="147.47" y="573.74" class="st3">Controller Worker Driver</text> </g>
<g id="shape2-6" transform="translate(449.049,-237.791)">
<title>Square</title>
<desc>Certificate Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="43.2283" cy="552.047" width="86.46" height="86.4567"/>
<rect x="0" y="508.819" width="86.4567" height="86.4567" class="st2"/>
<text x="12.02" y="547.85" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Certificate<v:newlineChar/><tspan
x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape3-10" v:mID="3" v:groupContext="shape" transform="translate(571.214,-166.925)">
<text x="12.02" y="547.85" class="st3">Certificate <tspan x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape3-10" transform="translate(569.049,-237.791)">
<title>Square.3</title>
<desc>Compute Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="43.2283" cy="552.047" width="86.46" height="86.4567"/>
<rect x="0" y="508.819" width="86.4567" height="86.4567" class="st2"/>
<text x="14.65" y="547.85" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Compute<v:newlineChar/><tspan
x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape4-14" v:mID="4" v:groupContext="shape" transform="translate(691.214,-166.925)">
<text x="14.65" y="547.85" class="st3">Compute <tspan x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape4-14" transform="translate(689.049,-237.791)">
<title>Square.4</title>
<desc>Network Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="43.2283" cy="552.047" width="86.46" height="86.4567"/>
<rect x="0" y="508.819" width="86.4567" height="86.4567" class="st2"/>
<text x="16.89" y="547.85" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Network<v:newlineChar/><tspan
x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape5-18" v:mID="5" v:groupContext="shape" transform="translate(331.214,-166.925)">
<text x="16.89" y="547.85" class="st3">Network <tspan x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape5-18" transform="translate(329.049,-237.791)">
<title>Square.5</title>
<desc>Amphora Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="43.2283" cy="552.047" width="86.46" height="86.4567"/>
<rect x="0" y="508.819" width="86.4567" height="86.4567" class="st2"/>
<text x="14.51" y="547.85" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Amphora<v:newlineChar/><tspan
x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape1001-22" v:mID="1001" v:groupContext="shape" transform="translate(687.192,-53.5394)">
<text x="14.51" y="547.85" class="st3">Amphora <tspan x="24.74" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape1001-22" transform="translate(685.028,-124.406)">
<title>Ellipse.6</title>
<desc>Neutron</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="47.25" cy="563.776" width="82.69" height="55.125"/>
<path d="M0 563.78 A47.25 31.5 0 0 1 94.5 563.78 A47.25 31.5 0 1 1 0 563.78 Z" class="st5"/>
<text x="21.52" y="567.98" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Neutron</text> </g>
<g id="shape1002-25" v:mID="1002" v:groupContext="shape" transform="translate(567.192,-53.5394)">
<text x="21.52" y="567.98" class="st3">Neutron</text> </g>
<g id="shape1002-25" transform="translate(565.028,-124.406)">
<title>Ellipse.5</title>
<desc>Nova</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="47.25" cy="563.776" width="82.69" height="55.125"/>
<path d="M0 563.78 A47.25 31.5 0 0 1 94.5 563.78 A47.25 31.5 0 1 1 0 563.78 Z" class="st5"/>
<text x="31" y="567.98" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Nova</text> </g>
<g id="shape1003-28" v:mID="1003" v:groupContext="shape" transform="translate(447.192,-53.5394)">
<text x="31" y="567.98" class="st3">Nova</text> </g>
<g id="shape1003-28" transform="translate(445.028,-124.406)">
<title>Ellipse.69</title>
<desc>Barbican</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="47.25" cy="563.776" width="82.69" height="55.125"/>
<desc>Barbican / Castellan</desc>
<path d="M0 563.78 A47.25 31.5 0 0 1 94.5 563.78 A47.25 31.5 0 1 1 0 563.78 Z" class="st5"/>
<text x="20.68" y="567.98" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Barbican</text> </g>
<g id="shape1004-31" v:mID="1004" v:groupContext="shape" transform="translate(210.906,-330.27)">
<text x="16.03" y="559.58" class="st3">Barbican / <tspan x="19.44" dy="1.2em" class="st4">Castellan</tspan></text> </g>
<g id="shape1004-32" transform="translate(208.742,-401.136)">
<title>Ellipse.3</title>
<desc>Oslo Messaging</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="47.25" cy="563.776" width="82.69" height="55.125"/>
<path d="M0 563.78 A47.25 31.5 0 0 1 94.5 563.78 A47.25 31.5 0 1 1 0 563.78 Z" class="st5"/>
<text x="33.21" y="559.58" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Oslo<v:newlineChar/><tspan
x="13.9" dy="1.2em" class="st4">Messaging</tspan></text> </g>
<g id="shape1006-35" v:mID="1006" v:groupContext="shape" v:layerMember="0" transform="translate(331.214,-272.031)">
<text x="33.21" y="559.58" class="st3">Oslo <tspan x="13.9" dy="1.2em" class="st4">Messaging</tspan></text> </g>
<g id="shape1006-36" transform="translate(329.049,-342.897)">
<title>Dynamic connector.1006</title>
<path d="M-8.68 588.19 L-9.04 588.19 L-29.47 588.19" class="st6"/>
</g>
<g id="shape1007-44" v:mID="1007" v:groupContext="shape" v:layerMember="0" transform="translate(727.356,-166.925)">
<g id="shape1007-45" transform="translate(739.364,-237.791)">
<title>Dynamic connector.1007</title>
<path d="M7.09 595.28 L7.09 636.62" class="st8"/>
<path d="M-7.09 595.28 L-7.09 636.62" class="st8"/>
</g>
<g id="shape1008-49" v:mID="1008" v:groupContext="shape" v:layerMember="0" transform="translate(607.356,-166.925)">
<g id="shape1008-50" transform="translate(605.191,-237.791)">
<title>Dynamic connector.1008</title>
<path d="M7.09 595.28 L7.09 636.62" class="st8"/>
</g>
<g id="shape1009-54" v:mID="1009" v:groupContext="shape" v:layerMember="0" transform="translate(487.356,-166.925)">
<g id="shape1009-55" transform="translate(485.191,-237.791)">
<title>Dynamic connector.1009</title>
<path d="M7.09 595.28 L7.09 636.62" class="st8"/>
</g>
<g id="shape1010-59" v:mID="1010" v:groupContext="shape" v:layerMember="0" transform="translate(417.671,-203.067)">
<g id="shape1010-60" transform="translate(415.506,-288.106)">
<title>Dynamic connector.1010</title>
<path d="M0 588.19 L24.5 588.19" class="st8"/>
<path d="M0 602.36 L24.5 602.36" class="st8"/>
</g>
<g id="group1011-64" transform="translate(71.7133,-304.853)" v:mID="1011" v:groupContext="group">
<g id="group1011-65" transform="translate(69.5486,-375.719)">
<title>Sheet.1011</title>
<g id="shape9-65" v:mID="9" v:groupContext="shape" transform="translate(0,-0.447576)">
<g id="shape9-66" transform="translate(0,-0.447576)">
<title>Square.9</title>
<desc>Octavia API</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="538.583" width="113.39" height="113.386"/>
<rect x="0" y="481.89" width="113.386" height="113.386" class="st9"/>
<text x="33.64" y="534.38" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Octavia<v:newlineChar/><tspan
x="46.39" dy="1.2em" class="st4">API</tspan></text> </g>
<g id="shape15-69" v:mID="15" v:groupContext="shape" transform="translate(80.3899,0)">
<text x="21.42" y="504.09" class="st3">Octavia API</text> </g>
<g id="shape15-69" transform="translate(80.3899,-5.68434E-13)">
<title>Sheet.15</title>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<image x="0" y="564.279" width="32.9959" height="30.9961" preserveAspectRatio="none" xlink:href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACEAAAAfCAYAAABplKSyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMA
AA7DAcdvqGQAAAI9SURBVFhHxZYhUxxBEIVXICIQCEQEAolERiAQCEREBAKBQCAQEQj+AzIyMiIiAhGBQCAQCATixAkEAqoQCERE
@ -202,25 +132,19 @@
5tq5s24jF7wTFwesCDKSkw8U7j/fC+RAlyoasOk1sSp+iIm6krGio3aYg7Sx6/w4rOiQ8TS5eg5Tu+wz75vz47BiiWxORM3gWCIY
tYSbxLGFxvcnSXjrfDms6JCxkS0RmxgU41ELuFUk9WIen4YV25CRbJ8Kjf+N9ax1xYp9Y8W+sWK/jKoXFAS4xJXVHUYAAAAASUVO
RK5CYII="/>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
</g>
</g>
<g id="group1012-73" transform="translate(331.214,-304.853)" v:mID="1012" v:groupContext="group">
<g id="group1012-73" transform="translate(329.049,-375.719)">
<title>Sheet.1012</title>
<g id="shape6-74" v:mID="6" v:groupContext="shape" transform="translate(-1.59872E-14,-0.447576)">
<g id="shape6-74" transform="translate(0,-0.447576)">
<title>Square.6</title>
<desc>Octavia Worker</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="538.583" width="113.39" height="113.386"/>
<rect x="0" y="481.89" width="113.386" height="113.386" class="st9"/>
<text x="33.64" y="534.38" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Octavia<v:newlineChar/><tspan
x="34.05" dy="1.2em" class="st4">Worker</tspan></text> </g>
<g id="shape16-78" v:mID="16" v:groupContext="shape" transform="translate(80.3899,0)">
<text x="33.64" y="534.38" class="st3">Octavia <tspan x="34.05" dy="1.2em" class="st4">Worker</tspan></text> </g>
<g id="shape16-78" transform="translate(80.3899,0)">
<title>Sheet.16</title>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<image x="0" y="564.279" width="32.9959" height="30.9961" preserveAspectRatio="none" xlink:href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACEAAAAfCAYAAABplKSyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMA
AA7DAcdvqGQAAAI9SURBVFhHxZYhUxxBEIVXICIQCEQEAolERiAQCEREBAKBQCAQEQj+AzIyMiIiAhGBQCAQCATixAkEAqoQCERE
@ -232,25 +156,19 @@
5tq5s24jF7wTFwesCDKSkw8U7j/fC+RAlyoasOk1sSp+iIm6krGio3aYg7Sx6/w4rOiQ8TS5eg5Tu+wz75vz47BiiWxORM3gWCIY
tYSbxLGFxvcnSXjrfDms6JCxkS0RmxgU41ELuFUk9WIen4YV25CRbJ8Kjf+N9ax1xYp9Y8W+sWK/jKoXFAS4xJXVHUYAAAAASUVO
RK5CYII="/>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
</g>
</g>
<g id="group1013-82" transform="translate(497.749,-305.301)" v:mID="1013" v:groupContext="group">
<g id="group1013-82" transform="translate(495.585,-376.167)">
<title>Sheet.1013</title>
<g id="shape7-83" v:mID="7" v:groupContext="shape">
<g id="shape7-83">
<title>Square.7</title>
<desc>Health Manager</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="538.583" width="113.39" height="113.386"/>
<rect x="0" y="481.89" width="113.386" height="113.386" class="st9"/>
<text x="36.47" y="534.38" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Health<v:newlineChar/><tspan
x="29.11" dy="1.2em" class="st4">Manager</tspan></text> </g>
<g id="shape17-87" v:mID="17" v:groupContext="shape" transform="translate(80.3899,0)">
<text x="36.47" y="534.38" class="st3">Health <tspan x="29.11" dy="1.2em" class="st4">Manager</tspan></text> </g>
<g id="shape17-87" transform="translate(80.3899,0)">
<title>Sheet.17</title>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<image x="0" y="564.279" width="32.9959" height="30.9961" preserveAspectRatio="none" xlink:href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACEAAAAfCAYAAABplKSyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMA
AA7DAcdvqGQAAAI9SURBVFhHxZYhUxxBEIVXICIQCEQEAolERiAQCEREBAKBQCAQEQj+AzIyMiIiAhGBQCAQCATixAkEAqoQCERE
@ -262,25 +180,19 @@
5tq5s24jF7wTFwesCDKSkw8U7j/fC+RAlyoasOk1sSp+iIm6krGio3aYg7Sx6/w4rOiQ8TS5eg5Tu+wz75vz47BiiWxORM3gWCIY
tYSbxLGFxvcnSXjrfDms6JCxkS0RmxgU41ELuFUk9WIen4YV25CRbJ8Kjf+N9ax1xYp9Y8W+sWK/jKoXFAS4xJXVHUYAAAAASUVO
RK5CYII="/>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
</g>
</g>
<g id="group1014-91" transform="translate(664.285,-304.853)" v:mID="1014" v:groupContext="group">
<g id="group1014-91" transform="translate(662.12,-375.719)">
<title>Sheet.1014</title>
<g id="shape8-92" v:mID="8" v:groupContext="shape" transform="translate(3.19744E-14,-0.447576)">
<g id="shape8-92" transform="translate(3.19744E-14,-0.447576)">
<title>Square.8</title>
<desc>Housekeeping Manager</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="538.583" width="113.39" height="113.386"/>
<rect x="0" y="481.89" width="113.386" height="113.386" class="st9"/>
<text x="12.33" y="534.38" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Housekeeping<v:newlineChar/><tspan
x="29.11" dy="1.2em" class="st4">Manager</tspan></text> </g>
<g id="shape18-96" v:mID="18" v:groupContext="shape" transform="translate(80.3899,0)">
<text x="12.33" y="534.38" class="st3">Housekeeping <tspan x="29.11" dy="1.2em" class="st4">Manager</tspan></text> </g>
<g id="shape18-96" transform="translate(80.3899,0)">
<title>Sheet.18</title>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<image x="0" y="564.279" width="32.9959" height="30.9961" preserveAspectRatio="none" xlink:href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACEAAAAfCAYAAABplKSyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMA
AA7DAcdvqGQAAAI9SURBVFhHxZYhUxxBEIVXICIQCEQEAolERiAQCEREBAKBQCAQEQj+AzIyMiIiAhGBQCAQCATixAkEAqoQCERE
@ -292,109 +204,88 @@
5tq5s24jF7wTFwesCDKSkw8U7j/fC+RAlyoasOk1sSp+iIm6krGio3aYg7Sx6/w4rOiQ8TS5eg5Tu+wz75vz47BiiWxORM3gWCIY
tYSbxLGFxvcnSXjrfDms6JCxkS0RmxgU41ELuFUk9WIen4YV25CRbJ8Kjf+N9ax1xYp9Y8W+sWK/jKoXFAS4xJXVHUYAAAAASUVO
RK5CYII="/>
<rect v:rectContext="foreign" x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
</g>
</g>
<g id="shape1016-100" v:mID="1016" v:groupContext="shape" v:layerMember="0" transform="translate(185.099,-368.968)">
<g id="shape1016-100" transform="translate(168.761,-425.464)">
<title>Dynamic connector.1016</title>
<path d="M0 602.25 L16.77 602.4" class="st8"/>
<path d="M0 588.27 L30.94 588.14" class="st8"/>
</g>
<g id="shape1017-105" v:mID="1017" v:groupContext="shape" v:layerMember="0" transform="translate(305.406,-354.795)">
<g id="shape1017-105" transform="translate(303.242,-425.661)">
<title>Dynamic connector.1017</title>
<path d="M0 588.3 L16.77 588.16" class="st8"/>
</g>
<g id="shape1018-110" v:mID="1018" v:groupContext="shape" v:layerMember="0" transform="translate(128.406,-305.301)">
<g id="shape1018-110" transform="translate(126.242,-376.167)">
<title>Dynamic connector.1018</title>
<path d="M0 603.96 L0 604.32 L0 621.46 L86.16 621.46" class="st6"/>
</g>
<g id="group1019-117" transform="translate(223.609,-244.57)" v:mID="1019" v:groupContext="group">
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<g id="group1019-117" transform="translate(221.445,-315.436)">
<title>Can.1019</title>
<desc>Database</desc>
<g id="shape1020-118" v:mID="1020" v:groupContext="shape">
<g id="shape1020-118">
<title>Sheet.1020</title>
<v:userDefs>
<v:ud v:nameU="ControlHalfHeight" v:prompt="" v:val="VT0(0.11811023622047):24"/>
<v:ud v:nameU="FillForegnd" v:prompt="" v:val="VT0(1):26"/>
<v:ud v:nameU="ControlHalfHeight" v:prompt="" v:val="VT0(0.095964566929134):1"/>
<v:ud v:nameU="FillForegnd" v:prompt="" v:val="VT5(#d80073)"/>
</v:userDefs>
<path d="M0 588.37 A34.5472 6.90945 -180 0 0 69.09 588.37 L69.09 533.09 L0 533.09 L0 588.37 Z" class="st5"/>
</g>
<g id="shape1019-120" v:mID="1019" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="34.5472" cy="560.728" width="69.1" height="69.0945"/>
<g id="shape1019-120">
<ellipse cx="34.5472" cy="533.091" rx="34.5472" ry="6.90945" class="st5"/>
<text x="5.84" y="564.93" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Database</text> </g>
<text x="5.84" y="564.93" class="st3">Database</text> </g>
</g>
<g id="shape1021-123" v:mID="1021" v:groupContext="shape" transform="translate(293.17,-39.3661)">
<g id="shape1021-123" transform="translate(291.005,-110.232)">
<title>Parallelogram.1021</title>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<path d="M0 595.28 L102.05 595.28 L113.39 510.24 L11.34 510.24 L0 595.28 Z" class="st11"/>
</g>
<g id="shape1022-125" v:mID="1022" v:groupContext="shape" transform="translate(278.531,-50.3858)">
<g id="shape1022-125" transform="translate(276.366,-121.252)">
<title>Parallelogram.1022</title>
<desc>Amphora</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="552.756" width="113.39" height="85.0394"/>
<path d="M0 595.28 L102.05 595.28 L113.39 510.24 L11.34 510.24 L0 595.28 Z" class="st12"/>
<text x="27.98" y="556.96" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Amphora</text> </g>
<g id="group1054-128" transform="translate(359.038,-138.579)" v:mID="1054" v:groupContext="group">
<text x="27.98" y="556.96" class="st3">Amphora</text> </g>
<g id="group1054-128" transform="translate(356.874,-209.445)">
<title>Sheet.1054</title>
<g id="shape1050-129" v:mID="1050" v:groupContext="shape" v:layerMember="0" transform="translate(30.8081,-28.3465)">
<g id="shape1050-129" transform="translate(30.8081,-28.3465)">
<title>Dynamic connector.1050</title>
<path d="M-6.99 603.95 L-7 604.31 L-7.33 623.62" class="st13"/>
</g>
<g id="shape1051-134" v:mID="1051" v:groupContext="shape" v:layerMember="0" transform="translate(14.1732,-28.3465)">
<g id="shape1051-134" transform="translate(14.1732,-28.3465)">
<title>Dynamic connector.1051</title>
<path d="M-6.84 595.28 L-7.17 614.58" class="st8"/>
</g>
</g>
<g id="group1064-139" transform="translate(60.1975,-453.543)" v:mID="1064" v:groupContext="group">
<title>Sheet.1064</title>
<g id="shape1056-140" v:mID="1056" v:groupContext="shape">
<title>Rectangle.1056</title>
<desc>Neutron</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="68.2087" cy="491.339" width="136.42" height="18.8976"/>
<rect x="0" y="481.89" width="136.417" height="113.386" class="st5"/>
<text x="42.48" y="495.54" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Neutron</text> </g>
<g id="shape1057-143" v:mID="1057" v:groupContext="shape" transform="translate(11.5157,-11.3386)">
<title>Rectangle.1057</title>
<desc>LBaaS v2 User API Handler</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.6929" cy="537.101" width="113.39" height="42.3913"/>
<rect x="0" y="515.906" width="113.386" height="79.3701" class="st14"/>
<text x="29.5" y="532.9" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LBaaS v2<v:newlineChar/><tspan
x="4.28" dy="1.2em" class="st4">User API Handler</tspan></text> </g>
<g id="shape1058-147" v:mID="1058" v:groupContext="shape" transform="translate(20.9587,-17.0079)">
<title>Rectangle.1058</title>
<desc>Octavia Driver</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="47.25" cy="577.01" width="94.5" height="36.5315"/>
<rect x="0" y="558.744" width="94.5" height="36.5315" class="st9"/>
<text x="24.2" y="572.81" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Octavia <tspan
x="28.77" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="shape1056-139" transform="translate(83.7218,-411.024)">
<title>Sheet.1056</title>
<desc>Amphora Driver</desc>
<rect x="0" y="552.756" width="85.0394" height="42.5197" class="st14"/>
<text x="13.8" y="569.82" class="st3">Amphora <tspan x="24.04" dy="1.2em" class="st4">Driver</tspan></text> </g>
<g id="group1057-143" transform="translate(69.5486,-212.598)">
<title>Sheet.1057</title>
<g id="shape1058-144">
<title>Square.7</title>
<desc>Driver Agent</desc>
<rect x="0" y="481.89" width="113.386" height="113.386" class="st9"/>
<text x="17.66" y="542.78" class="st3">Driver Agent</text> </g>
<g id="shape19-147" transform="translate(80.3899,0)">
<title>Sheet.19</title>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
<image x="0" y="564.279" width="32.9959" height="30.9961" preserveAspectRatio="none" xlink:href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACEAAAAfCAYAAABplKSyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMA
AA7DAcdvqGQAAAI9SURBVFhHxZYhUxxBEIVXICIQCEQEAolERiAQCEREBAKBQCAQEQj+AzIyMiIiAhGBQCAQCATixAkEAqoQCERE
BAJxvC9sX/UNb2/3iqpNV311M29mumd3evq2Go1G/x0r9o0V+8aKfWPFLsjmiv587s+CFach+yxOxHmhfxfX4qtYyGNtWNEhOxQ0
4Lb+XanH5sVfETptNtXp7VjRIfstngUd+CMeBYEfRB5Du3Z+HFZ0yDbqAF1gQwfOj8OKgexjam+LCNIGm7gQ/45DtgDhq8SKIFsX
T+KnIB/y6+7KpTgW+DlyccCKIPsl7gXnnR3PAsHJjyuBn4lrPY5lxapaFDjIDh1lMuYxx5aNZ8XJ69gEt+NAcGw7gqeddmSMndp4
VqyqTYFTOlzDcJTZKNZ8ECTjQJRzgbG9vGa81omBjCfNjoJhw/wvaU5mas2wYiDbTY4yEyU7zW+qJbNvQkZi7osbkZ0FlOVxDUnr
juqxcj5Ju1bOD7z4ej1pnNa/JSQlZ7yU1lDMmhIzbo5/g1Z8zfhw0AQJS1AKUlstuRPMtQXrjRDI+Fs+E9nZe2ATyzaWE0EWtYIz
5tq5s24jF7wTFwesCDKSkw8U7j/fC+RAlyoasOk1sSp+iIm6krGio3aYg7Sx6/w4rOiQ8TS5eg5Tu+wz75vz47BiiWxORM3gWCIY
tYSbxLGFxvcnSXjrfDms6JCxkS0RmxgU41ELuFUk9WIen4YV25CRbJ8Kjf+N9ax1xYp9Y8W+sWK/jKoXFAS4xJXVHUYAAAAASUVO
RK5CYII="/>
<rect x="0" y="564.279" width="32.9959" height="30.9961" class="st10"/>
</g>
</g>
<g id="shape1065-151" v:mID="1065" v:groupContext="shape" v:layerMember="0" transform="translate(121.32,-470.551)">
<title>Dynamic connector.1065</title>
<path d="M7.09 595.28 L7.09 638.1" class="st8"/>
<g id="shape1059-151" transform="translate(90.2835,-411.024)">
<title>Dynamic connector.1059</title>
<path d="M7.09 595.28 L7.09 671.27" class="st8"/>
</g>
<g id="shape1061-156" transform="translate(182.934,-269.291)">
<title>Dynamic connector</title>
<path d="M0 595.28 L73.06 595.28 L73.06 558.67" class="st15"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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')

View File

@ -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.

View File

@ -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)

View File

@ -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}

View File

@ -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')

View File

@ -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')

View File

@ -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__)

View File

@ -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')

View File

@ -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()

View File

@ -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')

View File

@ -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',

View File

@ -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(),
]

View File

@ -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):

View File

@ -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.

View File

@ -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()

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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())

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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