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
changes/09/613709/19
Michael Johnson 4 years ago committed by Carlos Goncalves
parent c00faa7edd
commit 8997def2b5
  1. 2
      devstack/README.md
  2. 2
      devstack/contrib/new-octavia-devstack.sh
  3. 18
      devstack/plugin.sh
  4. 1
      devstack/samples/multinode/local.conf
  5. 1
      devstack/samples/singlenode/local.conf
  6. 9
      devstack/settings
  7. 27
      doc/source/contributor/guides/providers.rst
  8. 14
      doc/source/reference/introduction.rst
  9. 339
      doc/source/reference/octavia-component-overview.svg
  10. 20
      etc/octavia.conf
  11. 1
      lower-constraints.txt
  12. 10
      octavia/api/drivers/amphora_driver/driver.py
  13. 280
      octavia/api/drivers/data_models.py
  14. 11
      octavia/api/drivers/driver_agent/__init__.py
  15. 144
      octavia/api/drivers/driver_agent/driver_listener.py
  16. 174
      octavia/api/drivers/driver_agent/driver_updater.py
  17. 143
      octavia/api/drivers/driver_lib.py
  18. 143
      octavia/api/drivers/exceptions.py
  19. 4
      octavia/api/drivers/noop_driver/driver.py
  20. 469
      octavia/api/drivers/provider_base.py
  21. 84
      octavia/cmd/driver_agent.py
  22. 32
      octavia/common/config.py
  23. 324
      octavia/common/constants.py
  24. 1
      octavia/opts.py
  25. 21
      octavia/tests/unit/api/drivers/amphora_driver/test_amphora_driver.py
  26. 11
      octavia/tests/unit/api/drivers/driver_agent/__init__.py
  27. 171
      octavia/tests/unit/api/drivers/driver_agent/test_driver_listener.py
  28. 295
      octavia/tests/unit/api/drivers/driver_agent/test_driver_updater.py
  29. 217
      octavia/tests/unit/api/drivers/test_data_models.py
  30. 251
      octavia/tests/unit/api/drivers/test_driver_lib.py
  31. 88
      octavia/tests/unit/api/drivers/test_exceptions.py
  32. 157
      octavia/tests/unit/api/drivers/test_provider_base.py
  33. 70
      octavia/tests/unit/cmd/test_driver_agent.py
  34. 26
      octavia/tests/unit/test_opts.py
  35. 34
      octavia/tests/unit/test_version.py
  36. 1
      playbooks/legacy/grenade-devstack-octavia/run.yaml
  37. 1
      playbooks/legacy/octavia-v1-dsvm-py3x-scenario/run.yaml
  38. 1
      playbooks/legacy/octavia-v1-dsvm-scenario/run.yaml
  39. 18
      releasenotes/notes/Octavia-lib-transition-driver-agent-aeefef114898b8f5.yaml
  40. 2
      requirements.txt
  41. 1
      setup.cfg
  42. 2
      zuul.d/jobs.yaml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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