Add Octavia (load_balancer) amphora API

This patch adds the Octavia (load_balancer) amphora API support.

Depends-On: https://review.openstack.org/#/c/632842/
Change-Id: Id5a2ab45c2600a52415387b81369d371b6182578
This commit is contained in:
Michael Johnson 2019-02-01 19:12:03 -08:00
parent 85f1e4c7f1
commit 1249d5fd26
9 changed files with 440 additions and 0 deletions

View File

@ -128,3 +128,14 @@ Flavor Operations
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.delete_flavor
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.find_flavor
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.update_flavor
Amphora Operations
^^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.load_balancer.v2._proxy.Proxy
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.amphorae
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.get_amphora
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.find_amphora
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.configure_amphora
.. automethod:: openstack.load_balancer.v2._proxy.Proxy.failover_amphora

View File

@ -14,3 +14,4 @@ Load Balancer Resources
v2/provider
v2/flavor_profile
v2/flavor
v2/amphora

View File

@ -0,0 +1,30 @@
openstack.load_balancer.v2.amphora
==================================
.. automodule:: openstack.load_balancer.v2.amphora
The Amphora Class
-----------------
The ``Amphora`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.load_balancer.v2.amphora.Amphora
:members:
The AmphoraConfig Class
-----------------------
The ``AmphoraConfig`` class inherits from
:class:`~openstack.resource.Resource`.
.. autoclass:: openstack.load_balancer.v2.amphora.AmphoraConfig
:members:
The AmphoraFailover Class
-------------------------
The ``AmphoraFailover`` class inherits from
:class:`~openstack.resource.Resource`.
.. autoclass:: openstack.load_balancer.v2.amphora.AmphoraFailover
:members:

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack.load_balancer.v2 import amphora as _amphora
from openstack.load_balancer.v2 import flavor as _flavor
from openstack.load_balancer.v2 import flavor_profile as _flavor_profile
from openstack.load_balancer.v2 import health_monitor as _hm
@ -953,3 +954,54 @@ class Proxy(proxy.Proxy):
:rtype: :class:`~openstack.load_balancer.v2.flavor.Flavor`
"""
return self._update(_flavor.Flavor, flavor, **attrs)
def amphorae(self, **query):
"""Retrieve a generator of amphorae
:returns: A generator of amphora instances
"""
return self._list(_amphora.Amphora, **query)
def get_amphora(self, *attrs):
"""Get a amphora
:param amphora: The value can be the ID of an amphora
or :class:`~openstack.load_balancer.v2.amphora.Amphora` instance.
:returns: One
:class:`~openstack.load_balancer.v2.amphora.Amphora`
"""
return self._get(_amphora.Amphora, *attrs)
def find_amphora(self, amphora_id, ignore_missing=True):
"""Find a single amphora
:param amphora_id: The ID of a amphora
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the amphora does not exist.
When set to ``True``, no exception will be set when attempting
to find a nonexistent amphora.
:returns: ``None``
"""
return self._find(_amphora.Amphora, amphora_id,
ignore_missing=ignore_missing)
def configure_amphora(self, amphora_id, **attrs):
"""Update the configuration of an amphora agent
:param amphora_id: The ID of an amphora
:returns: ``None``
"""
return self._update(_amphora.AmphoraConfig, amphora_id=amphora_id)
def failover_amphora(self, amphora_id, **attrs):
"""Failover an amphora
:param amphora_id: The ID of an amphora
:returns: ``None``
"""
return self._update(_amphora.AmphoraFailover, amphora_id=amphora_id)

View File

@ -0,0 +1,148 @@
# Copyright 2019 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 openstack import resource
class Amphora(resource.Resource):
resource_key = 'amphora'
resources_key = 'amphorae'
base_path = '/octavia/amphorae'
# capabilities
allow_create = False
allow_fetch = True
allow_commit = False
allow_delete = False
allow_list = True
_query_mapping = resource.QueryParameters(
'id', 'loadbalancer_id', 'compute_id', 'lb_network_ip', 'vrrp_ip',
'ha_ip', 'vrrp_port_id', 'ha_port_id', 'cert_expiration', 'cert_busy',
'role', 'status', 'vrrp_interface', 'vrrp_id', 'vrrp_priority',
'cached_zone', 'created_at', 'updated_at', 'image_id', 'image_id'
)
# Properties
#: The ID of the amphora.
id = resource.Body('id')
#: The ID of the load balancer.
loadbalancer_id = resource.Body('loadbalancer_id')
#: The ID of the amphora resource in the compute system.
compute_id = resource.Body('compute_id')
#: The management IP of the amphora.
lb_network_ip = resource.Body('lb_network_ip')
#: The address of the vrrp port on the amphora.
vrrp_ip = resource.Body('vrrp_ip')
#: The IP address of the Virtual IP (VIP).
ha_ip = resource.Body('ha_ip')
#: The vrrp port's ID in the networking system.
vrrp_port_id = resource.Body('vrrp_port_id')
#: The ID of the Virtual IP (VIP) port.
ha_port_id = resource.Body('ha_port_id')
#: The date the certificate for the amphora expires.
cert_expiration = resource.Body('cert_expiration')
#: Whether the certificate is in the process of being replaced.
cert_busy = resource.Body('cert_busy')
#: The role configured for the amphora. One of STANDALONE, MASTER, BACKUP.
role = resource.Body('role')
#: The status of the amphora. One of: BOOTING, ALLOCATED, READY,
#: PENDING_CREATE, PENDING_DELETE, DELETED, ERROR.
status = resource.Body('status')
#: The bound interface name of the vrrp port on the amphora.
vrrp_interface = resource.Body('vrrp_interface')
#: The vrrp group's ID for the amphora.
vrrp_id = resource.Body('vrrp_id')
#: The priority of the amphora in the vrrp group.
vrrp_priority = resource.Body('vrrp_priority')
#: The availability zone of a compute instance, cached at create time.
cached_zone = resource.Body('cached_zone')
#: The UTC date and timestamp when the resource was created.
created_at = resource.Body('created_at')
#: The UTC date and timestamp when the resource was last updated.
updated_at = resource.Body('updated_at')
#: The ID of the glance image used for the amphora.
image_id = resource.Body('image_id')
#: The ID of the compute flavor used for the amphora.
compute_flavor = resource.Body('compute_flavor')
class AmphoraConfig(resource.Resource):
base_path = '/octavia/amphorae/%(amphora_id)s/config'
# capabilities
allow_create = False
allow_fetch = False
allow_commit = True
allow_delete = False
allow_list = False
requires_id = False
# Properties
#: The ID of the amphora.
amphora_id = resource.URI('amphora_id')
# The parent commit method assumes there is a header or body change,
# which we do not have here. The default _update code path also has no
# way to pass has_body into this function, so overriding the method here.
def commit(self, session, base_path=None):
kwargs = {}
request = self._prepare_request(prepend_key=False,
base_path=base_path,
**kwargs)
session = self._get_session(session)
kwargs = {}
microversion = self._get_microversion_for(session, 'commit')
response = session.put(request.url, json=request.body,
headers=request.headers,
microversion=microversion, **kwargs)
self.microversion = microversion
self._translate_response(response, has_body=False)
return self
class AmphoraFailover(resource.Resource):
base_path = '/octavia/amphorae/%(amphora_id)s/failover'
# capabilities
allow_create = False
allow_fetch = False
allow_commit = True
allow_delete = False
allow_list = False
requires_id = False
# Properties
#: The ID of the amphora.
amphora_id = resource.URI('amphora_id')
# The parent commit method assumes there is a header or body change,
# which we do not have here. The default _update code path also has no
# way to pass has_body into this function, so overriding the method here.
def commit(self, session, base_path=None):
kwargs = {}
request = self._prepare_request(prepend_key=False,
base_path=base_path,
**kwargs)
session = self._get_session(session)
kwargs = {}
microversion = self._get_microversion_for(session, 'commit')
response = session.put(request.url, json=request.body,
headers=request.headers,
microversion=microversion, **kwargs)
self.microversion = microversion
self._translate_response(response, has_body=False)
return self

View File

@ -37,6 +37,7 @@ class TestLoadBalancer(base.BaseFunctionalTest):
PROJECT_ID = None
FLAVOR_PROFILE_ID = None
FLAVOR_ID = None
AMPHORA_ID = None
PROTOCOL = 'HTTP'
PROTOCOL_PORT = 80
LB_ALGORITHM = 'ROUND_ROBIN'
@ -117,6 +118,10 @@ class TestLoadBalancer(base.BaseFunctionalTest):
wait=self._wait_for_timeout)
self.LB_ID = test_lb.id
amphorae = self.conn.load_balancer.amphorae(loadbalancer_id=self.LB_ID)
for amp in amphorae:
self.AMPHORA_ID = amp.id
test_listener = self.conn.load_balancer.create_listener(
name=self.LISTENER_NAME, protocol=self.PROTOCOL,
protocol_port=self.PROTOCOL_PORT, loadbalancer_id=self.LB_ID)
@ -573,3 +578,25 @@ class TestLoadBalancer(base.BaseFunctionalTest):
self.FLAVOR_ID, name=self.FLAVOR_NAME)
test_flavor = self.conn.load_balancer.get_flavor(self.FLAVOR_ID)
self.assertEqual(self.FLAVOR_NAME, test_flavor.name)
def test_amphora_list(self):
amp_ids = [amp.id for amp in self.conn.load_balancer.amphorae()]
self.assertIn(self.AMPHORA_ID, amp_ids)
def test_amphora_find(self):
test_amphora = self.conn.load_balancer.find_amphora(self.AMPHORA_ID)
self.assertEqual(self.AMPHORA_ID, test_amphora.id)
def test_amphora_get(self):
test_amphora = self.conn.load_balancer.get_amphora(self.AMPHORA_ID)
self.assertEqual(self.AMPHORA_ID, test_amphora.id)
def test_amphora_configure(self):
self.conn.load_balancer.configure_amphora(self.AMPHORA_ID)
test_amp = self.conn.load_balancer.get_amphora(self.AMPHORA_ID)
self.assertEqual(self.AMPHORA_ID, test_amp.id)
def test_amphora_failover(self):
self.conn.load_balancer.failover_amphora(self.AMPHORA_ID)
test_amp = self.conn.load_balancer.get_amphora(self.AMPHORA_ID)
self.assertEqual(self.AMPHORA_ID, test_amp.id)

View File

@ -0,0 +1,142 @@
# Copyright 2019 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 openstack.tests.unit import base
import uuid
from openstack.load_balancer.v2 import amphora
IDENTIFIER = uuid.uuid4()
LB_ID = uuid.uuid4()
LISTENER_ID = uuid.uuid4()
COMPUTE_ID = uuid.uuid4()
VRRP_PORT_ID = uuid.uuid4()
HA_PORT_ID = uuid.uuid4()
IMAGE_ID = uuid.uuid4()
COMPUTE_FLAVOR = uuid.uuid4()
AMPHORA_ID = uuid.uuid4()
EXAMPLE = {
'id': IDENTIFIER,
'loadbalancer_id': LB_ID,
'compute_id': COMPUTE_ID,
'lb_network_ip': '192.168.1.2',
'vrrp_ip': '192.168.1.5',
'ha_ip': '192.168.1.10',
'vrrp_port_id': VRRP_PORT_ID,
'ha_port_id': HA_PORT_ID,
'cert_expiration': '2019-09-19 00:34:51',
'cert_busy': 0,
'role': 'MASTER',
'status': 'ALLOCATED',
'vrrp_interface': 'eth1',
'vrrp_id': 1,
'vrrp_priority': 100,
'cached_zone': 'zone1',
'created_at': '2017-05-10T18:14:44',
'updated_at': '2017-05-10T23:08:12',
'image_id': IMAGE_ID,
'compute_flavor': COMPUTE_FLAVOR
}
class TestAmphora(base.TestCase):
def test_basic(self):
test_amphora = amphora.Amphora()
self.assertEqual('amphora', test_amphora.resource_key)
self.assertEqual('amphorae', test_amphora.resources_key)
self.assertEqual('/octavia/amphorae', test_amphora.base_path)
self.assertFalse(test_amphora.allow_create)
self.assertTrue(test_amphora.allow_fetch)
self.assertFalse(test_amphora.allow_commit)
self.assertFalse(test_amphora.allow_delete)
self.assertTrue(test_amphora.allow_list)
def test_make_it(self):
test_amphora = amphora.Amphora(**EXAMPLE)
self.assertEqual(IDENTIFIER, test_amphora.id)
self.assertEqual(LB_ID, test_amphora.loadbalancer_id)
self.assertEqual(COMPUTE_ID, test_amphora.compute_id)
self.assertEqual(EXAMPLE['lb_network_ip'], test_amphora.lb_network_ip)
self.assertEqual(EXAMPLE['vrrp_ip'], test_amphora.vrrp_ip)
self.assertEqual(EXAMPLE['ha_ip'], test_amphora.ha_ip)
self.assertEqual(VRRP_PORT_ID, test_amphora.vrrp_port_id)
self.assertEqual(HA_PORT_ID, test_amphora.ha_port_id)
self.assertEqual(EXAMPLE['cert_expiration'],
test_amphora.cert_expiration)
self.assertEqual(EXAMPLE['cert_busy'], test_amphora.cert_busy)
self.assertEqual(EXAMPLE['role'], test_amphora.role)
self.assertEqual(EXAMPLE['status'], test_amphora.status)
self.assertEqual(EXAMPLE['vrrp_interface'],
test_amphora.vrrp_interface)
self.assertEqual(EXAMPLE['vrrp_id'], test_amphora.vrrp_id)
self.assertEqual(EXAMPLE['vrrp_priority'], test_amphora.vrrp_priority)
self.assertEqual(EXAMPLE['cached_zone'], test_amphora.cached_zone)
self.assertEqual(EXAMPLE['created_at'], test_amphora.created_at)
self.assertEqual(EXAMPLE['updated_at'], test_amphora.updated_at)
self.assertEqual(IMAGE_ID, test_amphora.image_id)
self.assertEqual(COMPUTE_FLAVOR, test_amphora.compute_flavor)
self.assertDictEqual(
{'limit': 'limit',
'marker': 'marker',
'id': 'id',
'loadbalancer_id': 'loadbalancer_id',
'compute_id': 'compute_id',
'lb_network_ip': 'lb_network_ip',
'vrrp_ip': 'vrrp_ip',
'ha_ip': 'ha_ip',
'vrrp_port_id': 'vrrp_port_id',
'ha_port_id': 'ha_port_id',
'cert_expiration': 'cert_expiration',
'cert_busy': 'cert_busy',
'role': 'role',
'status': 'status',
'vrrp_interface': 'vrrp_interface',
'vrrp_id': 'vrrp_id',
'vrrp_priority': 'vrrp_priority',
'cached_zone': 'cached_zone',
'created_at': 'created_at',
'updated_at': 'updated_at',
'image_id': 'image_id',
'image_id': 'image_id'
},
test_amphora._query_mapping._mapping)
class TestAmphoraConfig(base.TestCase):
def test_basic(self):
test_amp_config = amphora.AmphoraConfig()
self.assertEqual('/octavia/amphorae/%(amphora_id)s/config',
test_amp_config.base_path)
self.assertFalse(test_amp_config.allow_create)
self.assertFalse(test_amp_config.allow_fetch)
self.assertTrue(test_amp_config.allow_commit)
self.assertFalse(test_amp_config.allow_delete)
self.assertFalse(test_amp_config.allow_list)
class TestAmphoraFailover(base.TestCase):
def test_basic(self):
test_amp_failover = amphora.AmphoraFailover()
self.assertEqual('/octavia/amphorae/%(amphora_id)s/failover',
test_amp_failover.base_path)
self.assertFalse(test_amp_failover.allow_create)
self.assertFalse(test_amp_failover.allow_fetch)
self.assertTrue(test_amp_failover.allow_commit)
self.assertFalse(test_amp_failover.allow_delete)
self.assertFalse(test_amp_failover.allow_list)

View File

@ -14,6 +14,7 @@ import uuid
import mock
from openstack.load_balancer.v2 import _proxy
from openstack.load_balancer.v2 import amphora
from openstack.load_balancer.v2 import flavor
from openstack.load_balancer.v2 import flavor_profile
from openstack.load_balancer.v2 import health_monitor
@ -36,6 +37,7 @@ class TestLoadBalancerProxy(test_proxy_base.TestProxyBase):
POOL_ID = uuid.uuid4()
L7_POLICY_ID = uuid.uuid4()
AMPHORA = 'amphora'
AMPHORA_ID = uuid.uuid4()
def setUp(self):
super(TestLoadBalancerProxy, self).setUp()
@ -363,3 +365,26 @@ class TestLoadBalancerProxy(test_proxy_base.TestProxyBase):
def test_flavor_update(self):
self.verify_update(self.proxy.update_flavor, flavor.Flavor)
def test_amphorae(self):
self.verify_list(self.proxy.amphorae, amphora.Amphora)
def test_amphora_get(self):
self.verify_get(self.proxy.get_amphora, amphora.Amphora)
def test_amphora_find(self):
self.verify_find(self.proxy.find_amphora, amphora.Amphora)
def test_amphora_configure(self):
self.verify_update(self.proxy.configure_amphora,
amphora.AmphoraConfig,
value=[self.AMPHORA_ID],
expected_args=[],
expected_kwargs={'amphora_id': self.AMPHORA_ID})
def test_amphora_failover(self):
self.verify_update(self.proxy.failover_amphora,
amphora.AmphoraFailover,
value=[self.AMPHORA_ID],
expected_args=[],
expected_kwargs={'amphora_id': self.AMPHORA_ID})

View File

@ -0,0 +1,4 @@
---
features:
- |
Adds Octavia (load_balancer) support for the amphora APIs.