6 changed files with 405 additions and 0 deletions
@ -0,0 +1,48 @@
|
||||
A10 Networks LBaaS Driver |
||||
|
||||
Installation info: |
||||
|
||||
To use this driver, you must: |
||||
- Install the a10-neutron-lbaas module. (E.g.: 'pip install a10-neutron-lbaas') |
||||
- Create a driver config file, a sample of which is given below. |
||||
- Enable it in neutron.conf |
||||
- Restart neutron-server |
||||
|
||||
Third-party CI info: |
||||
|
||||
Contact info for any problems is: a10-openstack-ci at a10networks dot com |
||||
Or contact Doug Wiegley directly (IRC: dougwig) |
||||
|
||||
Configuration file: |
||||
|
||||
Create a configuration file with a list of A10 appliances, similar to the |
||||
file below, located at: |
||||
/etc/neutron/services/loadbalancer/a10networks/config.py |
||||
|
||||
Or you can override that directory by setting the environment |
||||
variable A10_CONFIG_DIR. |
||||
|
||||
Example config file: |
||||
|
||||
devices = { |
||||
"ax1": { |
||||
"name": "ax1", |
||||
"host": "10.10.100.20", |
||||
"port": 443, |
||||
"protocol": "https", |
||||
"username": "admin", |
||||
"password": "a10", |
||||
"status": True, |
||||
"autosnat": False, |
||||
"api_version": "2.1", |
||||
"v_method": "LSI", |
||||
"max_instance": 5000, |
||||
"use_float": False, |
||||
"method": "hash" |
||||
}, |
||||
"ax4": { |
||||
"host": "10.10.100.23", |
||||
"username": "admin", |
||||
"password": "a10", |
||||
}, |
||||
} |
@ -0,0 +1,176 @@
|
||||
# Copyright 2014, Doug Wiegley (dougwig), A10 Networks |
||||
# |
||||
# 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 a10_neutron_lbaas |
||||
|
||||
from neutron.db import l3_db |
||||
from neutron.db.loadbalancer import loadbalancer_db as lb_db |
||||
from neutron.openstack.common import log as logging |
||||
from neutron.plugins.common import constants |
||||
from neutron.services.loadbalancer.drivers import abstract_driver |
||||
|
||||
VERSION = "1.0.0" |
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
|
||||
# Most driver calls below are straight passthroughs to the A10 package |
||||
# 'a10_neutron_lbaas'. Any function that has not been fully abstracted |
||||
# into the openstack driver/plugin interface is NOT passed through, to |
||||
# make it obvious which hidden interfaces/db calls that we rely on. |
||||
|
||||
class ThunderDriver(abstract_driver.LoadBalancerAbstractDriver): |
||||
|
||||
def __init__(self, plugin): |
||||
LOG.debug("A10Driver: init version=%s", VERSION) |
||||
self.plugin = plugin |
||||
|
||||
# Map the string types to neutron classes/functions, in order to keep |
||||
# from reaching into the bowels of Neutron from anywhere but this file. |
||||
self.neutron_map = { |
||||
'member': { |
||||
'model': lb_db.Member, |
||||
'delete_func': self.plugin._delete_db_member, |
||||
}, |
||||
'pool': { |
||||
'model': lb_db.Pool, |
||||
'delete_func': self.plugin._delete_db_pool, |
||||
}, |
||||
'vip': { |
||||
'model': lb_db.Vip, |
||||
'delete_func': self.plugin._delete_db_vip, |
||||
}, |
||||
} |
||||
|
||||
LOG.debug("A10Driver: initializing, version=%s, lbaas_manager=%s", |
||||
VERSION, a10_neutron_lbaas.VERSION) |
||||
|
||||
self.a10 = a10_neutron_lbaas.A10OpenstackLBV1(self) |
||||
|
||||
# The following private helper methods are used by a10_neutron_lbaas, |
||||
# and reflect the neutron interfaces required by that package. |
||||
|
||||
def _hm_binding_count(self, context, hm_id): |
||||
return context.session.query(lb_db.PoolMonitorAssociation).filter_by( |
||||
monitor_id=hm_id).join(lb_db.Pool).count() |
||||
|
||||
def _member_count(self, context, member): |
||||
return context.session.query(lb_db.Member).filter_by( |
||||
tenant_id=member['tenant_id'], |
||||
address=member['address']).count() |
||||
|
||||
def _member_get(self, context, member_id): |
||||
return self.plugin.get_member(context, member_id) |
||||
|
||||
def _member_get_ip(self, context, member, use_float=False): |
||||
ip_address = member['address'] |
||||
if use_float: |
||||
fip_qry = context.session.query(l3_db.FloatingIP) |
||||
if (fip_qry.filter_by(fixed_ip_address=ip_address).count() > 0): |
||||
float_address = fip_qry.filter_by( |
||||
fixed_ip_address=ip_address).first() |
||||
ip_address = str(float_address.floating_ip_address) |
||||
return ip_address |
||||
|
||||
def _pool_get_hm(self, context, hm_id): |
||||
return self.plugin.get_health_monitor(context, hm_id) |
||||
|
||||
def _pool_get_tenant_id(self, context, pool_id): |
||||
pool_qry = context.session.query(lb_db.Pool).filter_by(id=pool_id) |
||||
z = pool_qry.first() |
||||
if z: |
||||
return z.tenant_id |
||||
else: |
||||
return '' |
||||
|
||||
def _pool_get_vip_id(self, context, pool_id): |
||||
pool_qry = context.session.query(lb_db.Pool).filter_by(id=pool_id) |
||||
z = pool_qry.first() |
||||
if z: |
||||
return z.vip_id |
||||
else: |
||||
return '' |
||||
|
||||
def _pool_total(self, context, tenant_id): |
||||
return context.session.query(lb_db.Pool).filter_by( |
||||
tenant_id=tenant_id).count() |
||||
|
||||
def _vip_get(self, context, vip_id): |
||||
return self.plugin.get_vip(context, vip_id) |
||||
|
||||
def _active(self, context, model_type, model_id): |
||||
self.plugin.update_status(context, |
||||
self.neutron_map[model_type]['model'], |
||||
model_id, |
||||
constants.ACTIVE) |
||||
|
||||
def _failed(self, context, model_type, model_id): |
||||
self.plugin.update_status(context, |
||||
self.neutron_map[model_type]['model'], |
||||
model_id, |
||||
constants.ERROR) |
||||
|
||||
def _db_delete(self, context, model_type, model_id): |
||||
self.neutron_map[model_type]['delete_func'](context, model_id) |
||||
|
||||
def _hm_active(self, context, hm_id, pool_id): |
||||
self.plugin.update_pool_health_monitor(context, hm_id, pool_id, |
||||
constants.ACTIVE) |
||||
|
||||
def _hm_failed(self, context, hm_id, pool_id): |
||||
self.plugin.update_pool_health_monitor(context, hm_id, pool_id, |
||||
constants.ERROR) |
||||
|
||||
def _hm_db_delete(self, context, hm_id, pool_id): |
||||
self.plugin._delete_db_pool_health_monitor(context, hm_id, pool_id) |
||||
|
||||
# Pass-through driver |
||||
|
||||
def create_vip(self, context, vip): |
||||
self.a10.vip.create(context, vip) |
||||
|
||||
def update_vip(self, context, old_vip, vip): |
||||
self.a10.vip.update(context, old_vip, vip) |
||||
|
||||
def delete_vip(self, context, vip): |
||||
self.a10.vip.delete(context, vip) |
||||
|
||||
def create_pool(self, context, pool): |
||||
self.a10.pool.create(context, pool) |
||||
|
||||
def update_pool(self, context, old_pool, pool): |
||||
self.a10.pool.update(context, old_pool, pool) |
||||
|
||||
def delete_pool(self, context, pool): |
||||
self.a10.pool.delete(context, pool) |
||||
|
||||
def stats(self, context, pool_id): |
||||
return self.a10.pool.stats(context, pool_id) |
||||
|
||||
def create_member(self, context, member): |
||||
self.a10.member.create(context, member) |
||||
|
||||
def update_member(self, context, old_member, member): |
||||
self.a10.member.update(context, old_member, member) |
||||
|
||||
def delete_member(self, context, member): |
||||
self.a10.member.delete(context, member) |
||||
|
||||
def update_pool_health_monitor(self, context, old_hm, hm, pool_id): |
||||
self.a10.hm.update(context, old_hm, hm, pool_id) |
||||
|
||||
def create_pool_health_monitor(self, context, hm, pool_id): |
||||
self.a10.hm.create(context, hm, pool_id) |
||||
|
||||
def delete_pool_health_monitor(self, context, hm, pool_id): |
||||
self.a10.hm.delete(context, hm, pool_id) |
@ -0,0 +1,179 @@
|
||||
# Copyright 2014, Doug Wiegley (dougwig), A10 Networks |
||||
# |
||||
# 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 sys |
||||
|
||||
import mock |
||||
|
||||
from neutron import context |
||||
from neutron.db.loadbalancer import loadbalancer_db as lb_db |
||||
with mock.patch.dict(sys.modules, {'a10_neutron_lbaas': mock.Mock()}): |
||||
from neutron.services.loadbalancer.drivers.a10networks import driver_v1 |
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer |
||||
|
||||
|
||||
def fake_model(id): |
||||
return { |
||||
'id': id, |
||||
'tenant_id': "tennant-was-a-great-doctor" |
||||
} |
||||
|
||||
|
||||
def fake_member(id): |
||||
return { |
||||
'id': id, |
||||
'tenant_id': "vippyvip", |
||||
'address': '1.1.1.1' |
||||
} |
||||
|
||||
|
||||
class TestA10ThunderDriver(test_db_loadbalancer.LoadBalancerPluginDbTestCase): |
||||
|
||||
def setUp(self): |
||||
super(TestA10ThunderDriver, self).setUp() |
||||
self.context = context.get_admin_context() |
||||
self.plugin = mock.Mock() |
||||
self.driver = driver_v1.ThunderDriver(self.plugin) |
||||
self.driver.a10 = mock.Mock() |
||||
self.m = fake_model('p1') |
||||
|
||||
def test__hm_binding_count(self): |
||||
n = self.driver._hm_binding_count(self.context, 'hm01') |
||||
self.assertEqual(n, 0) |
||||
|
||||
def test__member_count(self): |
||||
self.m = fake_member('mem1') |
||||
n = self.driver._member_count(self.context, self.m) |
||||
self.assertEqual(n, 0) |
||||
|
||||
def test__member_get_ip(self): |
||||
self.m = fake_member('mem1') |
||||
z = self.driver._member_get_ip(self.context, self.m, False) |
||||
self.assertEqual(z, '1.1.1.1') |
||||
z = self.driver._member_get_ip(self.context, self.m, True) |
||||
self.assertEqual(z, '1.1.1.1') |
||||
|
||||
def test__pool_get_hm(self): |
||||
self.driver._pool_get_hm(self.context, 'hm01') |
||||
self.plugin.get_health_monitor.assert_called_once_with( |
||||
self.context, 'hm01') |
||||
|
||||
def test__pool_get_tenant_id(self): |
||||
z = self.driver._pool_get_tenant_id(self.context, 'pool1') |
||||
self.assertEqual(z, '') |
||||
|
||||
def test__pool_get_vip_id(self): |
||||
z = self.driver._pool_get_vip_id(self.context, 'pool1') |
||||
self.assertEqual(z, '') |
||||
|
||||
def test__pool_total(self): |
||||
n = self.driver._pool_total(self.context, |
||||
tenant_id='whatareyoudoingdave') |
||||
self.assertEqual(n, 0) |
||||
|
||||
def test__active(self): |
||||
self.driver._active(self.context, 'vip', 'vip1') |
||||
self.plugin.update_status.assert_called_once_with( |
||||
self.context, lb_db.Vip, 'vip1', 'ACTIVE') |
||||
|
||||
def test__failed(self): |
||||
self.driver._failed(self.context, 'vip', 'vip2-1-2') |
||||
self.plugin.update_status.assert_called_once_with( |
||||
self.context, lb_db.Vip, 'vip2-1-2', 'ERROR') |
||||
|
||||
def test__db_delete(self): |
||||
self.driver._db_delete(self.context, 'pool', 'myid0101') |
||||
self.plugin._delete_db_pool.assert_called_once_with( |
||||
self.context, 'myid0101') |
||||
|
||||
def test__hm_active(self): |
||||
self.driver._hm_active(self.context, 'hm01', 'pool1') |
||||
self.plugin.update_pool_health_monitor.assert_called_once_with( |
||||
self.context, 'hm01', 'pool1', 'ACTIVE') |
||||
|
||||
def test__hm_failed(self): |
||||
self.driver._hm_failed(self.context, 'hm01', 'pool1') |
||||
self.plugin.update_pool_health_monitor.assert_called_once_with( |
||||
self.context, 'hm01', 'pool1', 'ERROR') |
||||
|
||||
def test__hm_db_delete(self): |
||||
self.driver._hm_db_delete(self.context, 'hm01', 'pool2') |
||||
self.plugin._delete_db_pool_health_monitor.assert_called_once_with( |
||||
self.context, 'hm01', 'pool2') |
||||
|
||||
def test_create_vip(self): |
||||
self.driver.create_vip(self.context, self.m) |
||||
self.driver.a10.vip.create.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_update_vip(self): |
||||
self.driver.update_vip(self.context, self.m, self.m) |
||||
self.driver.a10.vip.update.assert_called_once_with( |
||||
self.context, self.m, self.m) |
||||
|
||||
def test_delete_vip(self): |
||||
self.driver.delete_vip(self.context, self.m) |
||||
self.driver.a10.vip.delete.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_create_pool(self): |
||||
self.driver.create_pool(self.context, self.m) |
||||
self.driver.a10.pool.create.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_update_pool(self): |
||||
self.driver.update_pool(self.context, self.m, self.m) |
||||
self.driver.a10.pool.update.assert_called_once_with( |
||||
self.context, self.m, self.m) |
||||
|
||||
def test_delete_pool(self): |
||||
self.driver.delete_pool(self.context, self.m) |
||||
self.driver.a10.pool.delete.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_stats(self): |
||||
self.driver.stats(self.context, self.m['id']) |
||||
self.driver.a10.pool.stats.assert_called_once_with( |
||||
self.context, self.m['id']) |
||||
|
||||
def test_create_member(self): |
||||
self.driver.create_member(self.context, self.m) |
||||
self.driver.a10.member.create.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_update_member(self): |
||||
self.driver.update_member(self.context, self.m, self.m) |
||||
self.driver.a10.member.update.assert_called_once_with( |
||||
self.context, self.m, self.m) |
||||
|
||||
def test_delete_member(self): |
||||
self.driver.delete_member(self.context, self.m) |
||||
self.driver.a10.member.delete.assert_called_once_with( |
||||
self.context, self.m) |
||||
|
||||
def test_update_pool_health_monitor(self): |
||||
self.driver.update_pool_health_monitor(self.context, self.m, self.m, |
||||
'pool1') |
||||
self.driver.a10.hm.update.assert_called_once_with( |
||||
self.context, self.m, self.m, 'pool1') |
||||
|
||||
def test_create_pool_health_monitor(self): |
||||
self.driver.create_pool_health_monitor(self.context, self.m, 'pool1') |
||||
self.driver.a10.hm.create.assert_called_once_with( |
||||
self.context, self.m, 'pool1') |
||||
|
||||
def test_delete_pool_health_monitor(self): |
||||
self.driver.delete_pool_health_monitor(self.context, self.m, 'pool1') |
||||
self.driver.a10.hm.delete.assert_called_once_with( |
||||
self.context, self.m, 'pool1') |
Loading…
Reference in new issue