Split services code out of Neutron, pass 1
- After l3_agent is refactored, need to remove services/firewall - After vmware plugin moves services out of monolothic, remove model copies and services/loadbalancer/constants, and re-enable unit tests. - After alembic chain gets split in four, tweak models/head and fix heal/current chain. - Re-factor test_routerserviceinsertion into one of the service repos Partially-Implements: blueprint services-split Change-Id: I5466984a9e57128266f97e9bd5c265f4dc3cba7b
This commit is contained in:
parent
fcb4e82afd
commit
407ee801e3
|
@ -52,7 +52,12 @@ from neutron.openstack.common import periodic_task
|
|||
from neutron.openstack.common import processutils
|
||||
from neutron.openstack.common import service
|
||||
from neutron import service as neutron_service
|
||||
from neutron.services.firewall.agents.l3reference import firewall_l3_agent
|
||||
try:
|
||||
from neutron_fwaas.services.firewall.agents.l3reference \
|
||||
import firewall_l3_agent
|
||||
except Exception:
|
||||
# TODO(dougw) - REMOVE THIS FROM NEUTRON; during l3_agent refactor only
|
||||
from neutron.services.firewall.agents.l3reference import firewall_l3_agent
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
NS_PREFIX = 'qrouter-'
|
||||
|
|
|
@ -28,6 +28,7 @@ from neutron.db import dvr_mac_db # noqa
|
|||
from neutron.db import external_net_db # noqa
|
||||
from neutron.db import extradhcpopt_db # noqa
|
||||
from neutron.db import extraroute_db # noqa
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.db.firewall import firewall_db # noqa
|
||||
from neutron.db import l3_agentschedulers_db # noqa
|
||||
from neutron.db import l3_attrs_db # noqa
|
||||
|
@ -35,6 +36,7 @@ from neutron.db import l3_db # noqa
|
|||
from neutron.db import l3_dvrscheduler_db # noqa
|
||||
from neutron.db import l3_gwmode_db # noqa
|
||||
from neutron.db import l3_hamode_db # noqa
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.db.loadbalancer import loadbalancer_db # noqa
|
||||
from neutron.db.metering import metering_db # noqa
|
||||
from neutron.db import model_base
|
||||
|
@ -46,6 +48,7 @@ from neutron.db import routedserviceinsertion_db # noqa
|
|||
from neutron.db import routerservicetype_db # noqa
|
||||
from neutron.db import securitygroups_db # noqa
|
||||
from neutron.db import servicetype_db # noqa
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.db.vpn import vpn_db # noqa
|
||||
from neutron.plugins.bigswitch.db import consistency_db # noqa
|
||||
from neutron.plugins.bigswitch import routerrule_db # noqa
|
||||
|
@ -79,9 +82,12 @@ from neutron.plugins.vmware.dbexts import models as vmware_models # noqa
|
|||
from neutron.plugins.vmware.dbexts import networkgw_db # noqa
|
||||
from neutron.plugins.vmware.dbexts import qos_db # noqa
|
||||
from neutron.plugins.vmware.dbexts import vcns_models # noqa
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.services.loadbalancer import agent_scheduler # noqa
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.services.loadbalancer.drivers.embrane import ( # noqa
|
||||
models as embrane_models)
|
||||
# TODO(dougw) - services split, need to complete alembic fixes
|
||||
from neutron.services.vpn.service_drivers import cisco_csr_db # noqa
|
||||
|
||||
|
||||
|
|
|
@ -20,11 +20,26 @@ from oslo.utils import excutils
|
|||
|
||||
from neutron.common import constants
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.db.firewall import firewall_db
|
||||
try:
|
||||
from neutron_fwaas.db.firewall import firewall_db
|
||||
except Exception:
|
||||
print("WARNING: missing neutron-fwaas package")
|
||||
# TODO(dougw) - temporary, this is going away
|
||||
from neutron.db.firewall import firewall_db
|
||||
from neutron.db import l3_db
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
try:
|
||||
from neutron_lbaas.db.loadbalancer import loadbalancer_db
|
||||
except Exception:
|
||||
print("WARNING: missing neutron-lbaas package")
|
||||
# TODO(dougw) - temporary, this is going away
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
from neutron.db import routedserviceinsertion_db as rsi_db
|
||||
from neutron.db.vpn import vpn_db
|
||||
try:
|
||||
from neutron_vpnaas.db.vpn import vpn_db
|
||||
except Exception:
|
||||
print("WARNING: missing neutron-vpnaas package")
|
||||
# TODO(dougw) - temporary, this is going away
|
||||
from neutron.db.vpn import vpn_db
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import routedserviceinsertion as rsi
|
||||
|
|
|
@ -21,7 +21,12 @@ from neutron.plugins.vmware.vshield.common import (
|
|||
constants as vcns_const)
|
||||
from neutron.plugins.vmware.vshield.common import (
|
||||
exceptions as vcns_exc)
|
||||
from neutron.services.loadbalancer import constants as lb_constants
|
||||
try:
|
||||
from neutron_lbaas.services.loadbalancer import constants as lb_constants
|
||||
except Exception:
|
||||
print("WARNING: missing neutron-lbaas package")
|
||||
# TODO(dougw) - this is going away
|
||||
from neutron.services.loadbalancer import constants as lb_constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 base64
|
||||
|
||||
import httplib2
|
||||
from oslo.config import cfg
|
||||
from oslo.serialization import jsonutils
|
||||
|
||||
from neutron.i18n import _LE
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('director', default='localhost',
|
||||
help=_("vArmour director ip")),
|
||||
cfg.StrOpt('director_port', default='443',
|
||||
help=_("vArmour director port")),
|
||||
cfg.StrOpt('username', default='varmour',
|
||||
help=_("vArmour director username")),
|
||||
cfg.StrOpt('password', default='varmour', secret=True,
|
||||
help=_("vArmour director password")), ]
|
||||
|
||||
cfg.CONF.register_opts(OPTS, "vArmour")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
REST_URL_PREFIX = '/api/v1.0'
|
||||
|
||||
|
||||
class vArmourAPIException(Exception):
|
||||
message = _("An unknown exception.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
try:
|
||||
self.err = self.message % kwargs
|
||||
|
||||
except Exception:
|
||||
self.err = self.message
|
||||
|
||||
def __str__(self):
|
||||
return self.err
|
||||
|
||||
|
||||
class AuthenticationFailure(vArmourAPIException):
|
||||
message = _("Invalid login credential.")
|
||||
|
||||
|
||||
class vArmourRestAPI(object):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('vArmourRestAPI: started')
|
||||
self.user = cfg.CONF.vArmour.username
|
||||
self.passwd = cfg.CONF.vArmour.password
|
||||
self.server = cfg.CONF.vArmour.director
|
||||
self.port = cfg.CONF.vArmour.director_port
|
||||
self.timeout = 3
|
||||
self.key = ''
|
||||
|
||||
def auth(self):
|
||||
headers = {}
|
||||
enc = base64.b64encode(self.user + ':' + self.passwd)
|
||||
headers['Authorization'] = 'Basic ' + enc
|
||||
resp = self.rest_api('POST', va_utils.REST_URL_AUTH, None, headers)
|
||||
if resp and resp['status'] == 200:
|
||||
self.key = resp['body']['auth']
|
||||
return True
|
||||
else:
|
||||
raise AuthenticationFailure()
|
||||
|
||||
def commit(self):
|
||||
self.rest_api('POST', va_utils.REST_URL_COMMIT)
|
||||
|
||||
def rest_api(self, method, url, body=None, headers=None):
|
||||
url = REST_URL_PREFIX + url
|
||||
if body:
|
||||
body_data = jsonutils.dumps(body)
|
||||
else:
|
||||
body_data = ''
|
||||
if not headers:
|
||||
headers = {}
|
||||
enc = base64.b64encode('%s:%s' % (self.user, self.key))
|
||||
headers['Authorization'] = 'Basic ' + enc
|
||||
|
||||
LOG.debug("vArmourRestAPI: %(server)s %(port)s",
|
||||
{'server': self.server, 'port': self.port})
|
||||
|
||||
try:
|
||||
action = "https://" + self.server + ":" + self.port + url
|
||||
|
||||
LOG.debug("vArmourRestAPI Sending: "
|
||||
"%(method)s %(action)s %(headers)s %(body_data)s",
|
||||
{'method': method, 'action': action,
|
||||
'headers': headers, 'body_data': body_data})
|
||||
|
||||
h = httplib2.Http(timeout=3,
|
||||
disable_ssl_certificate_validation=True)
|
||||
resp, resp_str = h.request(action, method,
|
||||
body=body_data,
|
||||
headers=headers)
|
||||
|
||||
LOG.debug("vArmourRestAPI Response: %(status)s %(resp_str)s",
|
||||
{'status': resp.status, 'resp_str': resp_str})
|
||||
|
||||
if resp.status == 200:
|
||||
return {'status': resp.status,
|
||||
'reason': resp.reason,
|
||||
'body': jsonutils.loads(resp_str)}
|
||||
except Exception:
|
||||
LOG.error(_LE('vArmourRestAPI: Could not establish HTTP '
|
||||
'connection'))
|
||||
|
||||
def del_cfg_objs(self, url, prefix):
|
||||
resp = self.rest_api('GET', url)
|
||||
if resp and resp['status'] == 200:
|
||||
olist = resp['body']['response']
|
||||
if not olist:
|
||||
return
|
||||
|
||||
for o in olist:
|
||||
if o.startswith(prefix):
|
||||
self.rest_api('DELETE', url + '/"name:%s"' % o)
|
||||
self.commit()
|
||||
|
||||
def count_cfg_objs(self, url, prefix):
|
||||
count = 0
|
||||
resp = self.rest_api('GET', url)
|
||||
if resp and resp['status'] == 200:
|
||||
for o in resp['body']['response']:
|
||||
if o.startswith(prefix):
|
||||
count += 1
|
||||
|
||||
return count
|
|
@ -1,348 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent import l3_agent
|
||||
from neutron.agent import l3_ha_agent
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import interface
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import config as common_config
|
||||
from neutron.common import constants as l3_constants
|
||||
from neutron.common import topics
|
||||
from neutron.i18n import _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import service
|
||||
from neutron import service as neutron_service
|
||||
from neutron.services.firewall.agents.l3reference import firewall_l3_agent
|
||||
from neutron.services.firewall.agents.varmour import varmour_api
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class vArmourL3NATAgent(l3_agent.L3NATAgent,
|
||||
firewall_l3_agent.FWaaSL3AgentRpcCallback):
|
||||
def __init__(self, host, conf=None):
|
||||
LOG.debug('vArmourL3NATAgent: __init__')
|
||||
self.rest = varmour_api.vArmourRestAPI()
|
||||
super(vArmourL3NATAgent, self).__init__(host, conf)
|
||||
|
||||
def _destroy_router_namespaces(self, only_router_id=None):
|
||||
return
|
||||
|
||||
def _destroy_router_namespace(self, namespace):
|
||||
return
|
||||
|
||||
def _create_router_namespace(self, ri):
|
||||
return
|
||||
|
||||
def _router_added(self, router_id, router):
|
||||
LOG.debug("_router_added: %s", router_id)
|
||||
ri = l3_agent.RouterInfo(router_id, self.root_helper, router)
|
||||
self.router_info[router_id] = ri
|
||||
super(vArmourL3NATAgent, self).process_router_add(ri)
|
||||
|
||||
def _router_removed(self, router_id):
|
||||
LOG.debug("_router_removed: %s", router_id)
|
||||
|
||||
ri = self.router_info[router_id]
|
||||
if ri:
|
||||
ri.router['gw_port'] = None
|
||||
ri.router[l3_constants.INTERFACE_KEY] = []
|
||||
ri.router[l3_constants.FLOATINGIP_KEY] = []
|
||||
self.process_router(ri)
|
||||
|
||||
name = va_utils.get_snat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
|
||||
|
||||
name = va_utils.get_dnat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
|
||||
|
||||
name = va_utils.get_trusted_zone_name(ri)
|
||||
self._va_unset_zone_interfaces(name, True)
|
||||
|
||||
name = va_utils.get_untrusted_zone_name(ri)
|
||||
self._va_unset_zone_interfaces(name, True)
|
||||
|
||||
del self.router_info[router_id]
|
||||
|
||||
def _spawn_metadata_proxy(self, router_id, ns_name):
|
||||
return
|
||||
|
||||
def _destroy_metadata_proxy(self, router_id, ns_name):
|
||||
return
|
||||
|
||||
def _set_subnet_info(self, port):
|
||||
ips = port['fixed_ips']
|
||||
if not ips:
|
||||
raise Exception(_("Router port %s has no IP address") % port['id'])
|
||||
if len(ips) > 1:
|
||||
LOG.warn(_LW("Ignoring multiple IPs on router port %s"),
|
||||
port['id'])
|
||||
prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen
|
||||
port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
|
||||
|
||||
def _va_unset_zone_interfaces(self, zone_name, remove_zone=False):
|
||||
# return True if zone exists; otherwise, return False
|
||||
LOG.debug("_va_unset_zone_interfaces: %s", zone_name)
|
||||
resp = self.rest.rest_api('GET', va_utils.REST_URL_CONF_ZONE)
|
||||
if resp and resp['status'] == 200:
|
||||
zlist = resp['body']['response']
|
||||
for zn in zlist:
|
||||
if zn == zone_name:
|
||||
commit = False
|
||||
|
||||
if 'interface' in zlist[zn]:
|
||||
for intf in zlist[zn]['interface']:
|
||||
self.rest.rest_api('DELETE',
|
||||
va_utils.REST_URL_CONF +
|
||||
va_utils.REST_ZONE_NAME % zn +
|
||||
va_utils.REST_INTF_NAME % intf)
|
||||
commit = True
|
||||
if remove_zone:
|
||||
self.rest.rest_api('DELETE',
|
||||
va_utils.REST_URL_CONF +
|
||||
va_utils.REST_ZONE_NAME % zn)
|
||||
commit = True
|
||||
|
||||
if commit:
|
||||
self.rest.commit()
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _va_pif_2_lif(self, pif):
|
||||
return pif + '.0'
|
||||
|
||||
def _va_set_interface_ip(self, pif, cidr):
|
||||
LOG.debug("_va_set_interface_ip: %(pif)s %(cidr)s",
|
||||
{'pif': pif, 'cidr': cidr})
|
||||
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
obj = va_utils.REST_INTF_NAME % pif + va_utils.REST_LOGIC_NAME % lif
|
||||
body = {
|
||||
'name': lif,
|
||||
'family': 'ipv4',
|
||||
'address': cidr
|
||||
}
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF + obj, body)
|
||||
|
||||
def _va_get_port_name(self, port_list, name):
|
||||
if name:
|
||||
for p in port_list:
|
||||
if p['VM name'] == name:
|
||||
return p['name']
|
||||
|
||||
def _va_config_trusted_zone(self, ri, plist):
|
||||
zone = va_utils.get_trusted_zone_name(ri)
|
||||
LOG.debug("_va_config_trusted_zone: %s", zone)
|
||||
|
||||
body = {
|
||||
'name': zone,
|
||||
'type': 'L3',
|
||||
'interface': []
|
||||
}
|
||||
|
||||
if not self._va_unset_zone_interfaces(zone):
|
||||
# if zone doesn't exist, create it
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
# add new internal ports to trusted zone
|
||||
for p in ri.internal_ports:
|
||||
if p['admin_state_up']:
|
||||
dev = self.get_internal_device_name(p['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
if lif not in body['interface']:
|
||||
body['interface'].append(lif)
|
||||
|
||||
self._va_set_interface_ip(pif, p['ip_cidr'])
|
||||
|
||||
if body['interface']:
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_untrusted_zone(self, ri, plist):
|
||||
zone = va_utils.get_untrusted_zone_name(ri)
|
||||
LOG.debug("_va_config_untrusted_zone: %s", zone)
|
||||
|
||||
body = {
|
||||
'name': zone,
|
||||
'type': 'L3',
|
||||
'interface': []
|
||||
}
|
||||
|
||||
if not self._va_unset_zone_interfaces(zone):
|
||||
# if zone doesn't exist, create it
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
# add new gateway ports to untrusted zone
|
||||
if ri.ex_gw_port:
|
||||
LOG.debug("_va_config_untrusted_zone: gw=%r", ri.ex_gw_port)
|
||||
dev = self.get_external_device_name(ri.ex_gw_port['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
|
||||
self._va_set_interface_ip(pif, ri.ex_gw_port['ip_cidr'])
|
||||
|
||||
body['interface'].append(lif)
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_router_snat_rules(self, ri, plist):
|
||||
LOG.debug('_va_config_router_snat_rules: %s', ri.router['id'])
|
||||
|
||||
prefix = va_utils.get_snat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
|
||||
|
||||
if not ri.enable_snat:
|
||||
return
|
||||
|
||||
for idx, p in enumerate(ri.internal_ports):
|
||||
if p['admin_state_up']:
|
||||
dev = self.get_internal_device_name(p['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
net = netaddr.IPNetwork(p['ip_cidr'])
|
||||
body = {
|
||||
'name': '%s_%d' % (prefix, idx),
|
||||
'ingress-context-type': 'interface',
|
||||
'ingress-index': self._va_pif_2_lif(pif),
|
||||
'source-address': [
|
||||
[str(netaddr.IPAddress(net.first + 2)),
|
||||
str(netaddr.IPAddress(net.last - 1))]
|
||||
],
|
||||
'flag': 'interface translate-source'
|
||||
}
|
||||
self.rest.rest_api('POST',
|
||||
va_utils.REST_URL_CONF_NAT_RULE,
|
||||
body)
|
||||
|
||||
if ri.internal_ports:
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_floating_ips(self, ri):
|
||||
LOG.debug('_va_config_floating_ips: %s', ri.router['id'])
|
||||
|
||||
prefix = va_utils.get_dnat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
|
||||
|
||||
# add new dnat rules
|
||||
for idx, fip in enumerate(ri.floating_ips):
|
||||
body = {
|
||||
'name': '%s_%d' % (prefix, idx),
|
||||
'ingress-context-type': 'zone',
|
||||
'ingress-index': va_utils.get_untrusted_zone_name(ri),
|
||||
'destination-address': [[fip['floating_ip_address'],
|
||||
fip['floating_ip_address']]],
|
||||
'static': [fip['fixed_ip_address'], fip['fixed_ip_address']],
|
||||
'flag': 'translate-destination'
|
||||
}
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_NAT_RULE, body)
|
||||
|
||||
if ri.floating_ips:
|
||||
self.rest.commit()
|
||||
|
||||
def process_router(self, ri):
|
||||
LOG.debug("process_router: %s", ri.router['id'])
|
||||
super(vArmourL3NATAgent, self).process_router(ri)
|
||||
|
||||
self.rest.auth()
|
||||
|
||||
# read internal port name and configuration port name map
|
||||
resp = self.rest.rest_api('GET', va_utils.REST_URL_INTF_MAP)
|
||||
if resp and resp['status'] == 200:
|
||||
try:
|
||||
plist = resp['body']['response']
|
||||
except ValueError:
|
||||
LOG.warn(_LW("Unable to parse interface mapping."))
|
||||
return
|
||||
else:
|
||||
LOG.warn(_LW("Unable to read interface mapping."))
|
||||
return
|
||||
|
||||
if ri.ex_gw_port:
|
||||
self._set_subnet_info(ri.ex_gw_port)
|
||||
self._va_config_trusted_zone(ri, plist)
|
||||
self._va_config_untrusted_zone(ri, plist)
|
||||
self._va_config_router_snat_rules(ri, plist)
|
||||
self._va_config_floating_ips(ri)
|
||||
|
||||
def _handle_router_snat_rules(self, ri, ex_gw_port,
|
||||
interface_name, action):
|
||||
return
|
||||
|
||||
def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address):
|
||||
return
|
||||
|
||||
def external_gateway_added(self, ri, ex_gw_port,
|
||||
interface_name, internal_cidrs):
|
||||
LOG.debug("external_gateway_added: %s", ri.router['id'])
|
||||
|
||||
if not ip_lib.device_exists(interface_name,
|
||||
root_helper=self.root_helper,
|
||||
namespace=ri.ns_name):
|
||||
self.driver.plug(ex_gw_port['network_id'],
|
||||
ex_gw_port['id'], interface_name,
|
||||
ex_gw_port['mac_address'],
|
||||
bridge=self.conf.external_network_bridge,
|
||||
namespace=ri.ns_name,
|
||||
prefix=l3_agent.EXTERNAL_DEV_PREFIX)
|
||||
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
|
||||
namespace=ri.ns_name)
|
||||
|
||||
def _update_routing_table(self, ri, operation, route):
|
||||
return
|
||||
|
||||
|
||||
class vArmourL3NATAgentWithStateReport(vArmourL3NATAgent,
|
||||
l3_agent.L3NATAgentWithStateReport):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
conf = cfg.CONF
|
||||
conf.register_opts(vArmourL3NATAgent.OPTS)
|
||||
conf.register_opts(l3_ha_agent.OPTS)
|
||||
config.register_interface_driver_opts_helper(conf)
|
||||
config.register_use_namespaces_opts_helper(conf)
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
config.register_root_helper(conf)
|
||||
conf.register_opts(interface.OPTS)
|
||||
conf.register_opts(external_process.OPTS)
|
||||
common_config.init(sys.argv[1:])
|
||||
config.setup_logging()
|
||||
server = neutron_service.Service.create(
|
||||
binary='neutron-l3-agent',
|
||||
topic=topics.L3_AGENT,
|
||||
report_interval=cfg.CONF.AGENT.report_interval,
|
||||
manager='neutron.services.firewall.agents.varmour.varmour_router.'
|
||||
'vArmourL3NATAgentWithStateReport')
|
||||
service.launch(server).wait()
|
|
@ -1,70 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
ROUTER_OBJ_PREFIX = 'r-'
|
||||
OBJ_PREFIX_LEN = 8
|
||||
TRUST_ZONE = '_z_trust'
|
||||
UNTRUST_ZONE = '_z_untrust'
|
||||
SNAT_RULE = '_snat'
|
||||
DNAT_RULE = '_dnat'
|
||||
ROUTER_POLICY = '_p'
|
||||
|
||||
REST_URL_CONF = '/config'
|
||||
REST_URL_AUTH = '/auth'
|
||||
REST_URL_COMMIT = '/commit'
|
||||
REST_URL_INTF_MAP = '/operation/interface/mapping'
|
||||
|
||||
REST_URL_CONF_NAT_RULE = REST_URL_CONF + '/nat/rule'
|
||||
REST_URL_CONF_ZONE = REST_URL_CONF + '/zone'
|
||||
REST_URL_CONF_POLICY = REST_URL_CONF + '/policy'
|
||||
REST_URL_CONF_ADDR = REST_URL_CONF + '/address'
|
||||
REST_URL_CONF_SERVICE = REST_URL_CONF + '/service'
|
||||
|
||||
REST_ZONE_NAME = '/zone/"name:%s"'
|
||||
REST_INTF_NAME = '/interface/"name:%s"'
|
||||
REST_LOGIC_NAME = '/logical/"name:%s"'
|
||||
REST_SERVICE_NAME = '/service/"name:%s"/rule'
|
||||
|
||||
|
||||
def get_router_object_prefix(ri):
|
||||
return ROUTER_OBJ_PREFIX + ri.router['id'][:OBJ_PREFIX_LEN]
|
||||
|
||||
|
||||
def get_firewall_object_prefix(ri, fw):
|
||||
return get_router_object_prefix(ri) + '-' + fw['id'][:OBJ_PREFIX_LEN]
|
||||
|
||||
|
||||
def get_trusted_zone_name(ri):
|
||||
return get_router_object_prefix(ri) + TRUST_ZONE
|
||||
|
||||
|
||||
def get_untrusted_zone_name(ri):
|
||||
return get_router_object_prefix(ri) + UNTRUST_ZONE
|
||||
|
||||
|
||||
def get_snat_rule_name(ri):
|
||||
return get_router_object_prefix(ri) + SNAT_RULE
|
||||
|
||||
|
||||
def get_dnat_rule_name(ri):
|
||||
return get_router_object_prefix(ri) + DNAT_RULE
|
||||
|
||||
|
||||
def get_router_policy_name(ri):
|
||||
return get_router_object_prefix(ri) + ROUTER_POLICY
|
||||
|
||||
|
||||
def get_firewall_policy_name(ri, fw, rule):
|
||||
return get_firewall_object_prefix(ri, fw) + rule['id'][:OBJ_PREFIX_LEN]
|
|
@ -1,96 +0,0 @@
|
|||
# Copyright 2013 Dell Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class FwaasDriverBase(object):
|
||||
"""Firewall as a Service Driver base class.
|
||||
|
||||
Using FwaasDriver Class, an instance of L3 perimeter Firewall
|
||||
can be created. The firewall co-exists with the L3 agent.
|
||||
|
||||
One instance is created for each tenant. One firewall policy
|
||||
is associated with each tenant (in the Havana release).
|
||||
|
||||
The Firewall can be visualized as having two zones (in Havana
|
||||
release), trusted and untrusted.
|
||||
|
||||
All the 'internal' interfaces of Neutron Router is treated as trusted. The
|
||||
interface connected to 'external network' is treated as untrusted.
|
||||
|
||||
The policy is applied on traffic ingressing/egressing interfaces on
|
||||
the trusted zone. This implies that policy will be applied for traffic
|
||||
passing from
|
||||
- trusted to untrusted zones
|
||||
- untrusted to trusted zones
|
||||
- trusted to trusted zones
|
||||
|
||||
Policy WILL NOT be applied for traffic from untrusted to untrusted zones.
|
||||
This is not a problem in Havana release as there is only one interface
|
||||
connected to external network.
|
||||
|
||||
Since the policy is applied on the internal interfaces, the traffic
|
||||
will be not be NATed to floating IP. For incoming traffic, the
|
||||
traffic will get NATed to internal IP address before it hits
|
||||
the firewall rules. So, while writing the rules, care should be
|
||||
taken if using rules based on floating IP.
|
||||
|
||||
The firewall rule addition/deletion/insertion/update are done by the
|
||||
management console. When the policy is sent to the driver, the complete
|
||||
policy is sent and the whole policy has to be applied atomically. The
|
||||
firewall rules will not get updated individually. This is to avoid problems
|
||||
related to out-of-order notifications or inconsistent behaviour by partial
|
||||
application of rules.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_firewall(self, apply_list, firewall):
|
||||
"""Create the Firewall with default (drop all) policy.
|
||||
|
||||
The default policy will be applied on all the interfaces of
|
||||
trusted zone.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_firewall(self, apply_list, firewall):
|
||||
"""Delete firewall.
|
||||
|
||||
Removes all policies created by this instance and frees up
|
||||
all the resources.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_firewall(self, apply_list, firewall):
|
||||
"""Apply the policy on all trusted interfaces.
|
||||
|
||||
Remove previous policy and apply the new policy on all trusted
|
||||
interfaces.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply_default_policy(self, apply_list, firewall):
|
||||
"""Apply the default policy on all trusted interfaces.
|
||||
|
||||
Remove current policy and apply the default policy on all trusted
|
||||
interfaces.
|
||||
"""
|
||||
pass
|
|
@ -1,311 +0,0 @@
|
|||
# Copyright 2013 Dell Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.agent.linux import iptables_manager
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.i18n import _LE
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.firewall.drivers import fwaas_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FWAAS_DRIVER_NAME = 'Fwaas iptables driver'
|
||||
FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy'
|
||||
INGRESS_DIRECTION = 'ingress'
|
||||
EGRESS_DIRECTION = 'egress'
|
||||
CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i',
|
||||
EGRESS_DIRECTION: 'o'}
|
||||
|
||||
""" Firewall rules are applied on internal-interfaces of Neutron router.
|
||||
The packets ingressing tenant's network will be on the output
|
||||
direction on internal-interfaces.
|
||||
"""
|
||||
IPTABLES_DIR = {INGRESS_DIRECTION: '-o',
|
||||
EGRESS_DIRECTION: '-i'}
|
||||
IPV4 = 'ipv4'
|
||||
IPV6 = 'ipv6'
|
||||
IP_VER_TAG = {IPV4: 'v4',
|
||||
IPV6: 'v6'}
|
||||
|
||||
INTERNAL_DEV_PREFIX = 'qr-'
|
||||
SNAT_INT_DEV_PREFIX = 'sg-'
|
||||
ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
|
||||
|
||||
|
||||
class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
|
||||
"""IPTables driver for Firewall As A Service."""
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug("Initializing fwaas iptables driver")
|
||||
|
||||
def create_firewall(self, agent_mode, apply_list, firewall):
|
||||
LOG.debug('Creating firewall %(fw_id)s for tenant %(tid)s)',
|
||||
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
|
||||
try:
|
||||
if firewall['admin_state_up']:
|
||||
self._setup_firewall(agent_mode, apply_list, firewall)
|
||||
else:
|
||||
self.apply_default_policy(agent_mode, apply_list, firewall)
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_LE("Failed to create firewall: %s"), firewall['id'])
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def _get_ipt_mgrs_with_if_prefix(self, agent_mode, router_info):
|
||||
"""Gets the iptables manager along with the if prefix to apply rules.
|
||||
|
||||
With DVR we can have differing namespaces depending on which agent
|
||||
(on Network or Compute node). Also, there is an associated i/f for
|
||||
each namespace. The iptables on the relevant namespace and matching
|
||||
i/f are provided. On the Network node we could have both the snat
|
||||
namespace and a fip so this is provided back as a list - so in that
|
||||
scenario rules can be applied on both.
|
||||
"""
|
||||
if not router_info.router['distributed']:
|
||||
return [{'ipt': router_info.iptables_manager,
|
||||
'if_prefix': INTERNAL_DEV_PREFIX}]
|
||||
ipt_mgrs = []
|
||||
# TODO(sridar): refactor to get strings to a common location.
|
||||
if agent_mode == 'dvr_snat':
|
||||
if router_info.snat_iptables_manager:
|
||||
ipt_mgrs.append({'ipt': router_info.snat_iptables_manager,
|
||||
'if_prefix': SNAT_INT_DEV_PREFIX})
|
||||
if router_info.dist_fip_count:
|
||||
# handle the fip case on n/w or compute node.
|
||||
ipt_mgrs.append({'ipt': router_info.iptables_manager,
|
||||
'if_prefix': ROUTER_2_FIP_DEV_PREFIX})
|
||||
return ipt_mgrs
|
||||
|
||||
def delete_firewall(self, agent_mode, apply_list, firewall):
|
||||
LOG.debug('Deleting firewall %(fw_id)s for tenant %(tid)s)',
|
||||
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
|
||||
fwid = firewall['id']
|
||||
try:
|
||||
for router_info in apply_list:
|
||||
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
|
||||
agent_mode, router_info)
|
||||
for ipt_if_prefix in ipt_if_prefix_list:
|
||||
ipt_mgr = ipt_if_prefix['ipt']
|
||||
self._remove_chains(fwid, ipt_mgr)
|
||||
self._remove_default_chains(ipt_mgr)
|
||||
# apply the changes immediately (no defer in firewall path)
|
||||
ipt_mgr.defer_apply_off()
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_LE("Failed to delete firewall: %s"), fwid)
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def update_firewall(self, agent_mode, apply_list, firewall):
|
||||
LOG.debug('Updating firewall %(fw_id)s for tenant %(tid)s)',
|
||||
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
|
||||
try:
|
||||
if firewall['admin_state_up']:
|
||||
self._setup_firewall(agent_mode, apply_list, firewall)
|
||||
else:
|
||||
self.apply_default_policy(agent_mode, apply_list, firewall)
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_LE("Failed to update firewall: %s"), firewall['id'])
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def apply_default_policy(self, agent_mode, apply_list, firewall):
|
||||
LOG.debug('Applying firewall %(fw_id)s for tenant %(tid)s)',
|
||||
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
|
||||
fwid = firewall['id']
|
||||
try:
|
||||
for router_info in apply_list:
|
||||
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
|
||||
agent_mode, router_info)
|
||||
for ipt_if_prefix in ipt_if_prefix_list:
|
||||
# the following only updates local memory; no hole in FW
|
||||
ipt_mgr = ipt_if_prefix['ipt']
|
||||
self._remove_chains(fwid, ipt_mgr)
|
||||
self._remove_default_chains(ipt_mgr)
|
||||
|
||||
# create default 'DROP ALL' policy chain
|
||||
self._add_default_policy_chain_v4v6(ipt_mgr)
|
||||
self._enable_policy_chain(fwid, ipt_if_prefix)
|
||||
|
||||
# apply the changes immediately (no defer in firewall path)
|
||||
ipt_mgr.defer_apply_off()
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(
|
||||
_LE("Failed to apply default policy on firewall: %s"), fwid)
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def _setup_firewall(self, agent_mode, apply_list, firewall):
|
||||
fwid = firewall['id']
|
||||
for router_info in apply_list:
|
||||
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
|
||||
agent_mode, router_info)
|
||||
for ipt_if_prefix in ipt_if_prefix_list:
|
||||
ipt_mgr = ipt_if_prefix['ipt']
|
||||
# the following only updates local memory; no hole in FW
|
||||
self._remove_chains(fwid, ipt_mgr)
|
||||
self._remove_default_chains(ipt_mgr)
|
||||
|
||||
# create default 'DROP ALL' policy chain
|
||||
self._add_default_policy_chain_v4v6(ipt_mgr)
|
||||
#create chain based on configured policy
|
||||
self._setup_chains(firewall, ipt_if_prefix)
|
||||
|
||||
# apply the changes immediately (no defer in firewall path)
|
||||
ipt_mgr.defer_apply_off()
|
||||
|
||||
def _get_chain_name(self, fwid, ver, direction):
|
||||
return '%s%s%s' % (CHAIN_NAME_PREFIX[direction],
|
||||
IP_VER_TAG[ver],
|
||||
fwid)
|
||||
|
||||
def _setup_chains(self, firewall, ipt_if_prefix):
|
||||
"""Create Fwaas chain using the rules in the policy
|
||||
"""
|
||||
fw_rules_list = firewall['firewall_rule_list']
|
||||
fwid = firewall['id']
|
||||
ipt_mgr = ipt_if_prefix['ipt']
|
||||
|
||||
#default rules for invalid packets and established sessions
|
||||
invalid_rule = self._drop_invalid_packets_rule()
|
||||
est_rule = self._allow_established_rule()
|
||||
|
||||
for ver in [IPV4, IPV6]:
|
||||
if ver == IPV4:
|
||||
table = ipt_mgr.ipv4['filter']
|
||||
else:
|
||||
table = ipt_mgr.ipv6['filter']
|
||||
ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
|
||||
ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
|
||||
for name in [ichain_name, ochain_name]:
|
||||
table.add_chain(name)
|
||||
table.add_rule(name, invalid_rule)
|
||||
table.add_rule(name, est_rule)
|
||||
|
||||
for rule in fw_rules_list:
|
||||
if not rule['enabled']:
|
||||
continue
|
||||
iptbl_rule = self._convert_fwaas_to_iptables_rule(rule)
|
||||
if rule['ip_version'] == 4:
|
||||
ver = IPV4
|
||||
table = ipt_mgr.ipv4['filter']
|
||||
else:
|
||||
ver = IPV6
|
||||
table = ipt_mgr.ipv6['filter']
|
||||
ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
|
||||
ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
|
||||
table.add_rule(ichain_name, iptbl_rule)
|
||||
table.add_rule(ochain_name, iptbl_rule)
|
||||
self._enable_policy_chain(fwid, ipt_if_prefix)
|
||||
|
||||
def _remove_default_chains(self, nsid):
|
||||
"""Remove fwaas default policy chain."""
|
||||
self._remove_chain_by_name(IPV4, FWAAS_DEFAULT_CHAIN, nsid)
|
||||
self._remove_chain_by_name(IPV6, FWAAS_DEFAULT_CHAIN, nsid)
|
||||
|
||||
def _remove_chains(self, fwid, ipt_mgr):
|
||||
"""Remove fwaas policy chain."""
|
||||
for ver in [IPV4, IPV6]:
|
||||
for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
|
||||
chain_name = self._get_chain_name(fwid, ver, direction)
|
||||
self._remove_chain_by_name(ver, chain_name, ipt_mgr)
|
||||
|
||||
def _add_default_policy_chain_v4v6(self, ipt_mgr):
|
||||
ipt_mgr.ipv4['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
|
||||
ipt_mgr.ipv4['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
|
||||
ipt_mgr.ipv6['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
|
||||
ipt_mgr.ipv6['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
|
||||
|
||||
def _remove_chain_by_name(self, ver, chain_name, ipt_mgr):
|
||||
if ver == IPV4:
|
||||
ipt_mgr.ipv4['filter'].remove_chain(chain_name)
|
||||
else:
|
||||
ipt_mgr.ipv6['filter'].remove_chain(chain_name)
|
||||
|
||||
def _add_rules_to_chain(self, ipt_mgr, ver, chain_name, rules):
|
||||
if ver == IPV4:
|
||||
table = ipt_mgr.ipv4['filter']
|
||||
else:
|
||||
table = ipt_mgr.ipv6['filter']
|
||||
for rule in rules:
|
||||
table.add_rule(chain_name, rule)
|
||||
|
||||
def _enable_policy_chain(self, fwid, ipt_if_prefix):
|
||||
bname = iptables_manager.binary_name
|
||||
ipt_mgr = ipt_if_prefix['ipt']
|
||||
if_prefix = ipt_if_prefix['if_prefix']
|
||||
|
||||
for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']),
|
||||
(IPV6, ipt_mgr.ipv6['filter'])]:
|
||||
for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
|
||||
chain_name = self._get_chain_name(fwid, ver, direction)
|
||||
chain_name = iptables_manager.get_chain_name(chain_name)
|
||||
if chain_name in tbl.chains:
|
||||
jump_rule = ['%s %s+ -j %s-%s' % (IPTABLES_DIR[direction],
|
||||
if_prefix, bname, chain_name)]
|
||||
self._add_rules_to_chain(ipt_mgr,
|
||||
ver, 'FORWARD', jump_rule)
|
||||
|
||||
#jump to DROP_ALL policy
|
||||
chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
|
||||
jump_rule = ['-o %s+ -j %s-%s' % (if_prefix, bname, chain_name)]
|
||||
self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
|
||||
self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
|
||||
|
||||
#jump to DROP_ALL policy
|
||||
chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
|
||||
jump_rule = ['-i %s+ -j %s-%s' % (if_prefix, bname, chain_name)]
|
||||
self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
|
||||
self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
|
||||
|
||||
def _convert_fwaas_to_iptables_rule(self, rule):
|
||||
action = 'ACCEPT' if rule.get('action') == 'allow' else 'DROP'
|
||||
args = [self._protocol_arg(rule.get('protocol')),
|
||||
self._port_arg('dport',
|
||||
rule.get('protocol'),
|
||||
rule.get('destination_port')),
|
||||
self._port_arg('sport',
|
||||
rule.get('protocol'),
|
||||
rule.get('source_port')),
|
||||
self._ip_prefix_arg('s', rule.get('source_ip_address')),
|
||||
self._ip_prefix_arg('d', rule.get('destination_ip_address')),
|
||||
self._action_arg(action)]
|
||||
|
||||
iptables_rule = ' '.join(args)
|
||||
return iptables_rule
|
||||
|
||||
def _drop_invalid_packets_rule(self):
|
||||
return '-m state --state INVALID -j DROP'
|
||||
|
||||
def _allow_established_rule(self):
|
||||
return '-m state --state ESTABLISHED,RELATED -j ACCEPT'
|
||||
|
||||
def _action_arg(self, action):
|
||||
if action:
|
||||
return '-j %s' % action
|
||||
return ''
|
||||
|
||||
def _protocol_arg(self, protocol):
|
||||
if protocol:
|
||||
return '-p %s' % protocol
|
||||
return ''
|
||||
|
||||
def _port_arg(self, direction, protocol, port):
|
||||
if not (protocol in ['udp', 'tcp'] and port):
|
||||
return ''
|
||||
return '--%s %s' % (direction, port)
|
||||
|
||||
def _ip_prefix_arg(self, direction, ip_prefix):
|
||||
if ip_prefix:
|
||||
return '-%s %s' % (direction, ip_prefix)
|
||||
return ''
|
|
@ -1,204 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.i18n import _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.firewall.agents.varmour import varmour_api
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
|
||||
from neutron.services.firewall.drivers import fwaas_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class vArmourFwaasDriver(fwaas_base.FwaasDriverBase):
|
||||
def __init__(self):
|
||||
LOG.debug("Initializing fwaas vArmour driver")
|
||||
|
||||
self.rest = varmour_api.vArmourRestAPI()
|
||||
|
||||
def create_firewall(self, apply_list, firewall):
|
||||
LOG.debug('create_firewall (%s)', firewall['id'])
|
||||
|
||||
return self.update_firewall(apply_list, firewall)
|
||||
|
||||
def update_firewall(self, apply_list, firewall):
|
||||
LOG.debug("update_firewall (%s)", firewall['id'])
|
||||
|
||||
if firewall['admin_state_up']:
|
||||
return self._update_firewall(apply_list, firewall)
|
||||
else:
|
||||
return self.apply_default_policy(apply_list, firewall)
|
||||
|
||||
def delete_firewall(self, apply_list, firewall):
|
||||
LOG.debug("delete_firewall (%s)", firewall['id'])
|
||||
|
||||
return self.apply_default_policy(apply_list, firewall)
|
||||
|
||||
def apply_default_policy(self, apply_list, firewall):
|
||||
LOG.debug("apply_default_policy (%s)", firewall['id'])
|
||||
|
||||
self.rest.auth()
|
||||
|
||||
for ri in apply_list:
|
||||
self._clear_policy(ri, firewall)
|
||||
|
||||
return True
|
||||
|
||||
def _update_firewall(self, apply_list, firewall):
|
||||
LOG.debug("Updating firewall (%s)", firewall['id'])
|
||||
|
||||
self.rest.auth()
|
||||
|
||||
for ri in apply_list:
|
||||
self._clear_policy(ri, firewall)
|
||||
self._setup_policy(ri, firewall)
|
||||
|
||||
return True
|
||||
|
||||
def _setup_policy(self, ri, fw):
|
||||
# create zones no matter if they exist. Interfaces are added by router
|
||||
body = {
|
||||
'type': 'L3',
|
||||
'interface': []
|
||||
}
|
||||
|
||||
body['name'] = va_utils.get_trusted_zone_name(ri)
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
body['name'] = va_utils.get_untrusted_zone_name(ri)
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
servs = dict()
|
||||
addrs = dict()
|
||||
for rule in fw['firewall_rule_list']:
|
||||
if not rule['enabled']:
|
||||
continue
|
||||
|
||||
if rule['ip_version'] == 4:
|
||||
service = self._make_service(ri, fw, rule, servs)
|
||||
s_addr = self._make_address(ri, fw, rule, addrs, True)
|
||||
d_addr = self._make_address(ri, fw, rule, addrs, False)
|
||||
|
||||
policy = va_utils.get_firewall_policy_name(ri, fw, rule)
|
||||
z0 = va_utils.get_trusted_zone_name(ri)
|
||||
z1 = va_utils.get_untrusted_zone_name(ri)
|
||||
body = self._make_policy(policy + '_0', rule,
|
||||
z0, z0, s_addr, d_addr, service)
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
|
||||
body = self._make_policy(policy + '_1', rule,
|
||||
z0, z1, s_addr, d_addr, service)
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
|
||||
body = self._make_policy(policy + '_2', rule,
|
||||
z1, z0, s_addr, d_addr, service)
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body)
|
||||
|
||||
self.rest.commit()
|
||||
else:
|
||||
LOG.warn(_LW("Unsupported IP version rule."))
|
||||
|
||||
def _clear_policy(self, ri, fw):
|
||||
prefix = va_utils.get_firewall_object_prefix(ri, fw)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_POLICY, prefix)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_ADDR, prefix)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_SERVICE, prefix)
|
||||
|
||||
def _make_service(self, ri, fw, rule, servs):
|
||||
prefix = va_utils.get_firewall_object_prefix(ri, fw)
|
||||
|
||||
if rule.get('protocol'):
|
||||
key = rule.get('protocol')
|
||||
if rule.get('source_port'):
|
||||
key += '-' + rule.get('source_port')
|
||||
if rule.get('destination_port'):
|
||||
key += '-' + rule.get('destination_port')
|
||||
else:
|
||||
return
|
||||
|
||||
if key in servs:
|
||||
name = '%s_%d' % (prefix, servs[key])
|
||||
else:
|
||||
# create new service object with index
|
||||
idx = len(servs)
|
||||
servs[key] = idx
|
||||
name = '%s_%d' % (prefix, idx)
|
||||
|
||||
body = {'name': name}
|
||||
self.rest.rest_api('POST',
|
||||
va_utils.REST_URL_CONF_SERVICE,
|
||||
body)
|
||||
body = self._make_service_rule(rule)
|
||||
self.rest.rest_api('POST',
|
||||
va_utils.REST_URL_CONF +
|
||||
va_utils.REST_SERVICE_NAME % name,
|
||||
body)
|
||||
self.rest.commit()
|
||||
|
||||
return name
|
||||
|
||||
def _make_service_rule(self, rule):
|
||||
body = {
|
||||
'name': '1',
|
||||
'protocol': rule.get('protocol')
|
||||
}
|
||||
if 'source_port' in rule:
|
||||
body['source-start'] = rule['source_port']
|
||||
body['source-end'] = rule['source_port']
|
||||
if 'destination_port' in rule:
|
||||
body['dest-start'] = rule['destination_port']
|
||||
body['dest-end'] = rule['destination_port']
|
||||
|
||||
return body
|
||||
|
||||
def _make_address(self, ri, fw, rule, addrs, is_src):
|
||||
prefix = va_utils.get_firewall_object_prefix(ri, fw)
|
||||
|
||||
if is_src:
|
||||
key = rule.get('source_ip_address')
|
||||
else:
|
||||
key = rule.get('destination_ip_address')
|
||||
|
||||
if not key:
|
||||
return
|
||||
|
||||
if key in addrs:
|
||||
name = '%s_%d' % (prefix, addrs[key])
|
||||
else:
|
||||
# create new address object with idx
|
||||
idx = len(addrs)
|
||||
addrs[key] = idx
|
||||
name = '%s_%d' % (prefix, idx)
|
||||
|
||||
body = {
|
||||
'name': name,
|
||||
'type': 'ipv4',
|
||||
'ipv4': key
|
||||
}
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ADDR, body)
|
||||
self.rest.commit()
|
||||
|
||||
return name
|
||||
|
||||
def _make_policy(self, name, rule, zone0, zone1, s_addr, d_addr, service):
|
||||
body = {
|
||||
'name': name,
|
||||
'action': 'permit' if rule.get('action') == 'allow' else 'deny',
|
||||
'from': zone0,
|
||||
'to': zone1,
|
||||
'match-source-address': [s_addr or 'Any'],
|
||||
'match-dest-address': [d_addr or 'Any'],
|
||||
'match-service': [service or 'Any']
|
||||
}
|
||||
|
||||
return body
|
|
@ -1,280 +0,0 @@
|
|||
# Copyright 2013 Big Switch Networks, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
from oslo import messaging
|
||||
|
||||
from neutron.common import exceptions as n_exception
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.common import topics
|
||||
from neutron import context as neutron_context
|
||||
from neutron.db.firewall import firewall_db
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.i18n import _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants as const
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FirewallCallbacks(object):
|
||||
target = messaging.Target(version='1.0')
|
||||
|
||||
def __init__(self, plugin):
|
||||
super(FirewallCallbacks, self).__init__()
|
||||
self.plugin = plugin
|
||||
|
||||
def set_firewall_status(self, context, firewall_id, status, **kwargs):
|
||||
"""Agent uses this to set a firewall's status."""
|
||||
LOG.debug("set_firewall_status() called")
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
# ignore changing status if firewall expects to be deleted
|
||||
# That case means that while some pending operation has been
|
||||
# performed on the backend, neutron server received delete request
|
||||
# and changed firewall status to const.PENDING_DELETE
|
||||
if fw_db.status == const.PENDING_DELETE:
|
||||
LOG.debug("Firewall %(fw_id)s in PENDING_DELETE state, "
|
||||
"not changing to %(status)s",
|
||||
{'fw_id': firewall_id, 'status': status})
|
||||
return False
|
||||
if status in (const.ACTIVE, const.DOWN):
|
||||
fw_db.status = status
|
||||
return True
|
||||
else:
|
||||
fw_db.status = const.ERROR
|
||||
return False
|
||||
|
||||
def firewall_deleted(self, context, firewall_id, **kwargs):
|
||||
"""Agent uses this to indicate firewall is deleted."""
|
||||
LOG.debug("firewall_deleted() called")
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
# allow to delete firewalls in ERROR state
|
||||
if fw_db.status in (const.PENDING_DELETE, const.ERROR):
|
||||
self.plugin.delete_db_firewall_object(context, firewall_id)
|
||||
return True
|
||||
else:
|
||||
LOG.warn(_LW('Firewall %(fw)s unexpectedly deleted by agent, '
|
||||
'status was %(status)s'),
|
||||
{'fw': firewall_id, 'status': fw_db.status})
|
||||
fw_db.status = const.ERROR
|
||||
return False
|
||||
|
||||
def get_firewalls_for_tenant(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls and rules for a tenant."""
|
||||
LOG.debug("get_firewalls_for_tenant() called")
|
||||
fw_list = [
|
||||
self.plugin._make_firewall_dict_with_rules(context, fw['id'])
|
||||
for fw in self.plugin.get_firewalls(context)
|
||||
]
|
||||
return fw_list
|
||||
|
||||
def get_firewalls_for_tenant_without_rules(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls for a tenant."""
|
||||
LOG.debug("get_firewalls_for_tenant_without_rules() called")
|
||||
fw_list = [fw for fw in self.plugin.get_firewalls(context)]
|
||||
return fw_list
|
||||
|
||||
def get_tenants_with_firewalls(self, context, **kwargs):
|
||||
"""Agent uses this to get all tenants that have firewalls."""
|
||||
LOG.debug("get_tenants_with_firewalls() called")
|
||||
ctx = neutron_context.get_admin_context()
|
||||
fw_list = self.plugin.get_firewalls(ctx)
|
||||
fw_tenant_list = list(set(fw['tenant_id'] for fw in fw_list))
|
||||
return fw_tenant_list
|
||||
|
||||
|
||||
class FirewallAgentApi(object):
|
||||
"""Plugin side of plugin to agent RPC API."""
|
||||
|
||||
def __init__(self, topic, host):
|
||||
self.host = host
|
||||
target = messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'create_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
def update_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'update_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
def delete_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'delete_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
|
||||
class FirewallCountExceeded(n_exception.Conflict):
|
||||
|
||||
"""Reference implementation specific exception for firewall count.
|
||||
|
||||
Only one firewall is supported per tenant. When a second
|
||||
firewall is tried to be created, this exception will be raised.
|
||||
"""
|
||||
message = _("Exceeded allowed count of firewalls for tenant "
|
||||
"%(tenant_id)s. Only one firewall is supported per tenant.")
|
||||
|
||||
|
||||
class FirewallPlugin(firewall_db.Firewall_db_mixin):
|
||||
|
||||
"""Implementation of the Neutron Firewall Service Plugin.
|
||||
|
||||
This class manages the workflow of FWaaS request/response.
|
||||
Most DB related works are implemented in class
|
||||
firewall_db.Firewall_db_mixin.
|
||||
"""
|
||||
supported_extension_aliases = ["fwaas"]
|
||||
|
||||
def __init__(self):
|
||||
"""Do the initialization for the firewall service plugin here."""
|
||||
|
||||
self.endpoints = [FirewallCallbacks(self)]
|
||||
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
topics.FIREWALL_PLUGIN, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
|
||||
self.agent_rpc = FirewallAgentApi(
|
||||
topics.L3_AGENT,
|
||||
cfg.CONF.host
|
||||
)
|
||||
|
||||
def _make_firewall_dict_with_rules(self, context, firewall_id):
|
||||
firewall = self.get_firewall(context, firewall_id)
|
||||
fw_policy_id = firewall['firewall_policy_id']
|
||||
if fw_policy_id:
|
||||
fw_policy = self.get_firewall_policy(context, fw_policy_id)
|
||||
fw_rules_list = [self.get_firewall_rule(
|
||||
context, rule_id) for rule_id in fw_policy['firewall_rules']]
|
||||
firewall['firewall_rule_list'] = fw_rules_list
|
||||
else:
|
||||
firewall['firewall_rule_list'] = []
|
||||
# FIXME(Sumit): If the size of the firewall object we are creating
|
||||
# here exceeds the largest message size supported by rabbit/qpid
|
||||
# then we will have a problem.
|
||||
return firewall
|
||||
|
||||
def _rpc_update_firewall(self, context, firewall_id):
|
||||
status_update = {"firewall": {"status": const.PENDING_UPDATE}}
|
||||
super(FirewallPlugin, self).update_firewall(context, firewall_id,
|
||||
status_update)
|
||||
fw_with_rules = self._make_firewall_dict_with_rules(context,
|
||||
firewall_id)
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
|
||||
def _rpc_update_firewall_policy(self, context, firewall_policy_id):
|
||||
firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
|
||||
if firewall_policy:
|
||||
for firewall_id in firewall_policy['firewall_list']:
|
||||
self._rpc_update_firewall(context, firewall_id)
|
||||
|
||||
def _ensure_update_firewall(self, context, firewall_id):
|
||||
fwall = self.get_firewall(context, firewall_id)
|
||||
if fwall['status'] in [const.PENDING_CREATE,
|
||||
const.PENDING_UPDATE,
|
||||
const.PENDING_DELETE]:
|
||||
raise fw_ext.FirewallInPendingState(firewall_id=firewall_id,
|
||||
pending_state=fwall['status'])
|
||||
|
||||
def _ensure_update_firewall_policy(self, context, firewall_policy_id):
|
||||
firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
|
||||
if firewall_policy and 'firewall_list' in firewall_policy:
|
||||
for firewall_id in firewall_policy['firewall_list']:
|
||||
self._ensure_update_firewall(context, firewall_id)
|
||||
|
||||
def _ensure_update_firewall_rule(self, context, firewall_rule_id):
|
||||
fw_rule = self.get_firewall_rule(context, firewall_rule_id)
|
||||
if 'firewall_policy_id' in fw_rule and fw_rule['firewall_policy_id']:
|
||||
self._ensure_update_firewall_policy(context,
|
||||
fw_rule['firewall_policy_id'])
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
LOG.debug("create_firewall() called")
|
||||
tenant_id = self._get_tenant_id_for_create(context,
|
||||
firewall['firewall'])
|
||||
fw_count = self.get_firewalls_count(context,
|
||||
filters={'tenant_id': [tenant_id]})
|
||||
if fw_count:
|
||||
raise FirewallCountExceeded(tenant_id=tenant_id)
|
||||
fw = super(FirewallPlugin, self).create_firewall(context, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.create_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
def update_firewall(self, context, id, firewall):
|
||||
LOG.debug("update_firewall() called")
|
||||
self._ensure_update_firewall(context, id)
|
||||
firewall['firewall']['status'] = const.PENDING_UPDATE
|
||||
fw = super(FirewallPlugin, self).update_firewall(context, id, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
def delete_db_firewall_object(self, context, id):
|
||||
firewall = self.get_firewall(context, id)
|
||||
if firewall['status'] == const.PENDING_DELETE:
|
||||
super(FirewallPlugin, self).delete_firewall(context, id)
|
||||
|
||||
def delete_firewall(self, context, id):
|
||||
LOG.debug("delete_firewall() called")
|
||||
status_update = {"firewall": {"status": const.PENDING_DELETE}}
|
||||
fw = super(FirewallPlugin, self).update_firewall(context, id,
|
||||
status_update)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.delete_firewall(context, fw_with_rules)
|
||||
|
||||
def update_firewall_policy(self, context, id, firewall_policy):
|
||||
LOG.debug("update_firewall_policy() called")
|
||||
self._ensure_update_firewall_policy(context, id)
|
||||
fwp = super(FirewallPlugin,
|
||||
self).update_firewall_policy(context, id, firewall_policy)
|
||||
self._rpc_update_firewall_policy(context, id)
|
||||
return fwp
|
||||
|
||||
def update_firewall_rule(self, context, id, firewall_rule):
|
||||
LOG.debug("update_firewall_rule() called")
|
||||
self._ensure_update_firewall_rule(context, id)
|
||||
fwr = super(FirewallPlugin,
|
||||
self).update_firewall_rule(context, id, firewall_rule)
|
||||
firewall_policy_id = fwr['firewall_policy_id']
|
||||
if firewall_policy_id:
|
||||
self._rpc_update_firewall_policy(context, firewall_policy_id)
|
||||
return fwr
|
||||
|
||||
def insert_rule(self, context, id, rule_info):
|
||||
LOG.debug("insert_rule() called")
|
||||
self._ensure_update_firewall_policy(context, id)
|
||||
fwp = super(FirewallPlugin,
|
||||
self).insert_rule(context, id, rule_info)
|
||||
self._rpc_update_firewall_policy(context, id)
|
||||
return fwp
|
||||
|
||||
def remove_rule(self, context, id, rule_info):
|
||||
LOG.debug("remove_rule() called")
|
||||
self._ensure_update_firewall_policy(context, id)
|
||||
fwp = super(FirewallPlugin,
|
||||
self).remove_rule(context, id, rule_info)
|
||||
self._rpc_update_firewall_policy(context, id)
|
||||
return fwp
|
|
@ -1,68 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent.linux import interface
|
||||
from neutron.common import config as common_config
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.common import topics
|
||||
from neutron.openstack.common import service
|
||||
from neutron.services.loadbalancer.agent import agent_manager as manager
|
||||
|
||||
OPTS = [
|
||||
cfg.IntOpt(
|
||||
'periodic_interval',
|
||||
default=10,
|
||||
help=_('Seconds between periodic task runs')
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class LbaasAgentService(n_rpc.Service):
|
||||
def start(self):
|
||||
super(LbaasAgentService, self).start()
|
||||
self.tg.add_timer(
|
||||
cfg.CONF.periodic_interval,
|
||||
self.manager.run_periodic_tasks,
|
||||
None,
|
||||
None
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
cfg.CONF.register_opts(OPTS)
|
||||
cfg.CONF.register_opts(manager.OPTS)
|
||||
# import interface options just in case the driver uses namespaces
|
||||
cfg.CONF.register_opts(interface.OPTS)
|
||||
config.register_interface_driver_opts_helper(cfg.CONF)
|
||||
config.register_agent_state_opts_helper(cfg.CONF)
|
||||
config.register_root_helper(cfg.CONF)
|
||||
|
||||
common_config.init(sys.argv[1:])
|
||||
config.setup_logging()
|
||||
|
||||
mgr = manager.LbaasAgentManager(cfg.CONF)
|
||||
svc = LbaasAgentService(
|
||||
host=cfg.CONF.host,
|
||||
topic=topics.LOADBALANCER_AGENT,
|
||||
manager=mgr
|
||||
)
|
||||
service.launch(svc).wait()
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 oslo import messaging
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
|
||||
|
||||
class LbaasAgentApi(object):
|
||||
"""Agent side of the Agent to Plugin RPC API."""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
# 2.0 Generic API for agent based drivers
|
||||
# - get_logical_device() handling changed on plugin side;
|
||||
# - pool_deployed() and update_status() methods added;
|
||||
|
||||
def __init__(self, topic, context, host):
|
||||
self.context = context
|
||||
self.host = host
|
||||
target = messaging.Target(topic=topic, version='2.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def get_ready_devices(self):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'get_ready_devices', host=self.host)
|
||||
|
||||
def pool_destroyed(self, pool_id):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'pool_destroyed', pool_id=pool_id)
|
||||
|
||||
def pool_deployed(self, pool_id):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'pool_deployed', pool_id=pool_id)
|
||||
|
||||
def get_logical_device(self, pool_id):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'get_logical_device', pool_id=pool_id)
|
||||
|
||||
def update_status(self, obj_type, obj_id, status):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'update_status', obj_type=obj_type,
|
||||
obj_id=obj_id, status=status)
|
||||
|
||||
def plug_vip_port(self, port_id):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'plug_vip_port', port_id=port_id,
|
||||
host=self.host)
|
||||
|
||||
def unplug_vip_port(self, port_id):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'unplug_vip_port', port_id=port_id,
|
||||
host=self.host)
|
||||
|
||||
def update_pool_stats(self, pool_id, stats):
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(self.context, 'update_pool_stats', pool_id=pool_id,
|
||||
stats=stats, host=self.host)
|
|
@ -1,96 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation. All rights reserved
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AgentDeviceDriver(object):
|
||||
"""Abstract device driver that defines the API required by LBaaS agent."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_name(cls):
|
||||
"""Returns unique name across all LBaaS device drivers."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def deploy_instance(self, logical_config):
|
||||
"""Fully deploys a loadbalancer instance from a given config."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def undeploy_instance(self, pool_id):
|
||||
"""Fully undeploys the loadbalancer instance."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_stats(self, pool_id):
|
||||
pass
|
||||
|
||||
def remove_orphans(self, known_pool_ids):
|
||||
# Not all drivers will support this
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_vip(self, vip):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_vip(self, old_vip, vip):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_vip(self, vip):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_pool(self, pool):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_pool(self, old_pool, pool):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_pool(self, pool):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_member(self, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_member(self, old_member, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_member(self, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_pool_health_monitor(self, health_monitor, pool_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_pool_health_monitor(self,
|
||||
old_health_monitor,
|
||||
health_monitor,
|
||||
pool_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_pool_health_monitor(self, health_monitor, pool_id):
|
||||
pass
|
|
@ -1,336 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
from oslo import messaging
|
||||
from oslo.utils import importutils
|
||||
|
||||
from neutron.agent import rpc as agent_rpc
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.common import topics
|
||||
from neutron import context
|
||||
from neutron.i18n import _LE, _LI
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import loopingcall
|
||||
from neutron.openstack.common import periodic_task
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.agent import agent_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
OPTS = [
|
||||
cfg.MultiStrOpt(
|
||||
'device_driver',
|
||||
default=['neutron.services.loadbalancer.drivers'
|
||||
'.haproxy.namespace_driver.HaproxyNSDriver'],
|
||||
help=_('Drivers used to manage loadbalancing devices'),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class DeviceNotFoundOnAgent(n_exc.NotFound):
|
||||
msg = _('Unknown device with pool_id %(pool_id)s')
|
||||
|
||||
|
||||
class LbaasAgentManager(periodic_task.PeriodicTasks):
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
# 1.1 Support agent_updated call
|
||||
# 2.0 Generic API for agent based drivers
|
||||
# - modify/reload/destroy_pool methods were removed;
|
||||
# - added methods to handle create/update/delete for every lbaas
|
||||
# object individually;
|
||||
target = messaging.Target(version='2.0')
|
||||
|
||||
def __init__(self, conf):
|
||||
super(LbaasAgentManager, self).__init__()
|
||||
self.conf = conf
|
||||
self.context = context.get_admin_context_without_session()
|
||||
self.plugin_rpc = agent_api.LbaasAgentApi(
|
||||
topics.LOADBALANCER_PLUGIN,
|
||||
self.context,
|
||||
self.conf.host
|
||||
)
|
||||
self._load_drivers()
|
||||
|
||||
self.agent_state = {
|
||||
'binary': 'neutron-lbaas-agent',
|
||||
'host': conf.host,
|
||||
'topic': topics.LOADBALANCER_AGENT,
|
||||
'configurations': {'device_drivers': self.device_drivers.keys()},
|
||||
'agent_type': n_const.AGENT_TYPE_LOADBALANCER,
|
||||
'start_flag': True}
|
||||
self.admin_state_up = True
|
||||
|
||||
self._setup_state_rpc()
|
||||
self.needs_resync = False
|
||||
# pool_id->device_driver_name mapping used to store known instances
|
||||
self.instance_mapping = {}
|
||||
|
||||
def _load_drivers(self):
|
||||
self.device_drivers = {}
|
||||
for driver in self.conf.device_driver:
|
||||
try:
|
||||
driver_inst = importutils.import_object(
|
||||
driver,
|
||||
self.conf,
|
||||
self.plugin_rpc
|
||||
)
|
||||
except ImportError:
|
||||
msg = _('Error importing loadbalancer device driver: %s')
|
||||
raise SystemExit(msg % driver)
|
||||
|
||||
driver_name = driver_inst.get_name()
|
||||
if driver_name not in self.device_drivers:
|
||||
self.device_drivers[driver_name] = driver_inst
|
||||
else:
|
||||
msg = _('Multiple device drivers with the same name found: %s')
|
||||
raise SystemExit(msg % driver_name)
|
||||
|
||||
def _setup_state_rpc(self):
|
||||
self.state_rpc = agent_rpc.PluginReportStateAPI(
|
||||
topics.LOADBALANCER_PLUGIN)
|
||||
report_interval = self.conf.AGENT.report_interval
|
||||
if report_interval:
|
||||
heartbeat = loopingcall.FixedIntervalLoopingCall(
|
||||
self._report_state)
|
||||
heartbeat.start(interval=report_interval)
|
||||
|
||||
def _report_state(self):
|
||||
try:
|
||||
instance_count = len(self.instance_mapping)
|
||||
self.agent_state['configurations']['instances'] = instance_count
|
||||
self.state_rpc.report_state(self.context,
|
||||
self.agent_state)
|
||||
self.agent_state.pop('start_flag', None)
|
||||
except Exception:
|
||||
LOG.exception(_LE("Failed reporting state!"))
|
||||
|
||||
def initialize_service_hook(self, started_by):
|
||||
self.sync_state()
|
||||
|
||||
@periodic_task.periodic_task
|
||||
def periodic_resync(self, context):
|
||||
if self.needs_resync:
|
||||
self.needs_resync = False
|
||||
self.sync_state()
|
||||
|
||||
@periodic_task.periodic_task(spacing=6)
|
||||
def collect_stats(self, context):
|
||||
for pool_id, driver_name in self.instance_mapping.items():
|
||||
driver = self.device_drivers[driver_name]
|
||||
try:
|
||||
stats = driver.get_stats(pool_id)
|
||||
if stats:
|
||||
self.plugin_rpc.update_pool_stats(pool_id, stats)
|
||||
except Exception:
|
||||
LOG.exception(_LE('Error updating statistics on pool %s'),
|
||||
pool_id)
|
||||
self.needs_resync = True
|
||||
|
||||
def sync_state(self):
|
||||
known_instances = set(self.instance_mapping.keys())
|
||||
try:
|
||||
ready_instances = set(self.plugin_rpc.get_ready_devices())
|
||||
|
||||
for deleted_id in known_instances - ready_instances:
|
||||
self._destroy_pool(deleted_id)
|
||||
|
||||
for pool_id in ready_instances:
|
||||
self._reload_pool(pool_id)
|
||||
|
||||
except Exception:
|
||||
LOG.exception(_LE('Unable to retrieve ready devices'))
|
||||
self.needs_resync = True
|
||||
|
||||
self.remove_orphans()
|
||||
|
||||
def _get_driver(self, pool_id):
|
||||
if pool_id not in self.instance_mapping:
|
||||
raise DeviceNotFoundOnAgent(pool_id=pool_id)
|
||||
|
||||
driver_name = self.instance_mapping[pool_id]
|
||||
return self.device_drivers[driver_name]
|
||||
|
||||
def _reload_pool(self, pool_id):
|
||||
try:
|
||||
logical_config = self.plugin_rpc.get_logical_device(pool_id)
|
||||
driver_name = logical_config['driver']
|
||||
if driver_name not in self.device_drivers:
|
||||
LOG.error(_LE('No device driver on agent: %s.'), driver_name)
|
||||
self.plugin_rpc.update_status(
|
||||
'pool', pool_id, constants.ERROR)
|
||||
return
|
||||
|
||||
self.device_drivers[driver_name].deploy_instance(logical_config)
|
||||
self.instance_mapping[pool_id] = driver_name
|
||||
self.plugin_rpc.pool_deployed(pool_id)
|
||||
except Exception:
|
||||
LOG.exception(_LE('Unable to deploy instance for pool: %s'),
|
||||
pool_id)
|
||||
self.needs_resync = True
|
||||
|
||||
def _destroy_pool(self, pool_id):
|
||||
driver = self._get_driver(pool_id)
|
||||
try:
|
||||
driver.undeploy_instance(pool_id)
|
||||
del self.instance_mapping[pool_id]
|
||||
self.plugin_rpc.pool_destroyed(pool_id)
|
||||
except Exception:
|
||||
LOG.exception(_LE('Unable to destroy device for pool: %s'),
|
||||
pool_id)
|
||||
self.needs_resync = True
|
||||
|
||||
def remove_orphans(self):
|
||||
for driver_name in self.device_drivers:
|
||||
pool_ids = [pool_id for pool_id in self.instance_mapping
|
||||
if self.instance_mapping[pool_id] == driver_name]
|
||||
try:
|
||||
self.device_drivers[driver_name].remove_orphans(pool_ids)
|
||||
except NotImplementedError:
|
||||
pass # Not all drivers will support this
|
||||
|
||||
def _handle_failed_driver_call(self, operation, obj_type, obj_id, driver):
|
||||
LOG.exception(_LE('%(operation)s %(obj)s %(id)s failed on device '
|
||||
'driver %(driver)s'),
|
||||
{'operation': operation.capitalize(), 'obj': obj_type,
|
||||
'id': obj_id, 'driver': driver})
|
||||
self.plugin_rpc.update_status(obj_type, obj_id, constants.ERROR)
|
||||
|
||||
def create_vip(self, context, vip):
|
||||
driver = self._get_driver(vip['pool_id'])
|
||||
try:
|
||||
driver.create_vip(vip)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('create', 'vip', vip['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status('vip', vip['id'], constants.ACTIVE)
|
||||
|
||||
def update_vip(self, context, old_vip, vip):
|
||||
driver = self._get_driver(vip['pool_id'])
|
||||
try:
|
||||
driver.update_vip(old_vip, vip)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('update', 'vip', vip['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status('vip', vip['id'], constants.ACTIVE)
|
||||
|
||||
def delete_vip(self, context, vip):
|
||||
driver = self._get_driver(vip['pool_id'])
|
||||
driver.delete_vip(vip)
|
||||
|
||||
def create_pool(self, context, pool, driver_name):
|
||||
if driver_name not in self.device_drivers:
|
||||
LOG.error(_LE('No device driver on agent: %s.'), driver_name)
|
||||
self.plugin_rpc.update_status('pool', pool['id'], constants.ERROR)
|
||||
return
|
||||
|
||||
driver = self.device_drivers[driver_name]
|
||||
try:
|
||||
driver.create_pool(pool)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('create', 'pool', pool['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.instance_mapping[pool['id']] = driver_name
|
||||
self.plugin_rpc.update_status('pool', pool['id'], constants.ACTIVE)
|
||||
|
||||
def update_pool(self, context, old_pool, pool):
|
||||
driver = self._get_driver(pool['id'])
|
||||
try:
|
||||
driver.update_pool(old_pool, pool)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('update', 'pool', pool['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status('pool', pool['id'], constants.ACTIVE)
|
||||
|
||||
def delete_pool(self, context, pool):
|
||||
driver = self._get_driver(pool['id'])
|
||||
driver.delete_pool(pool)
|
||||
del self.instance_mapping[pool['id']]
|
||||
|
||||
def create_member(self, context, member):
|
||||
driver = self._get_driver(member['pool_id'])
|
||||
try:
|
||||
driver.create_member(member)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('create', 'member', member['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status('member', member['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def update_member(self, context, old_member, member):
|
||||
driver = self._get_driver(member['pool_id'])
|
||||
try:
|
||||
driver.update_member(old_member, member)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call('update', 'member', member['id'],
|
||||
driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status('member', member['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def delete_member(self, context, member):
|
||||
driver = self._get_driver(member['pool_id'])
|
||||
driver.delete_member(member)
|
||||
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
driver = self._get_driver(pool_id)
|
||||
assoc_id = {'pool_id': pool_id, 'monitor_id': health_monitor['id']}
|
||||
try:
|
||||
driver.create_pool_health_monitor(health_monitor, pool_id)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call(
|
||||
'create', 'health_monitor', assoc_id, driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status(
|
||||
'health_monitor', assoc_id, constants.ACTIVE)
|
||||
|
||||
def update_pool_health_monitor(self, context, old_health_monitor,
|
||||
health_monitor, pool_id):
|
||||
driver = self._get_driver(pool_id)
|
||||
assoc_id = {'pool_id': pool_id, 'monitor_id': health_monitor['id']}
|
||||
try:
|
||||
driver.update_pool_health_monitor(old_health_monitor,
|
||||
health_monitor,
|
||||
pool_id)
|
||||
except Exception:
|
||||
self._handle_failed_driver_call(
|
||||
'update', 'health_monitor', assoc_id, driver.get_name())
|
||||
else:
|
||||
self.plugin_rpc.update_status(
|
||||
'health_monitor', assoc_id, constants.ACTIVE)
|
||||
|
||||
def delete_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
driver = self._get_driver(pool_id)
|
||||
driver.delete_pool_health_monitor(health_monitor, pool_id)
|
||||
|
||||
def agent_updated(self, context, payload):
|
||||
"""Handle the agent_updated notification event."""
|
||||
if payload['admin_state_up'] != self.admin_state_up:
|
||||
self.admin_state_up = payload['admin_state_up']
|
||||
if self.admin_state_up:
|
||||
self.needs_resync = True
|
||||
else:
|
||||
for pool_id in self.instance_mapping.keys():
|
||||
LOG.info(_LI("Destroying pool %s due to agent disabling"),
|
||||
pool_id)
|
||||
self._destroy_pool(pool_id)
|
||||
LOG.info(_LI("Agent_updated by server side %s!"), payload)
|
|
@ -1,48 +0,0 @@
|
|||
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",
|
||||
},
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
# 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)
|
|
@ -1,440 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 uuid
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo import messaging
|
||||
from oslo.utils import importutils
|
||||
|
||||
from neutron.common import constants as q_const
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.common import topics
|
||||
from neutron.db import agents_db
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
from neutron.extensions import lbaas_agentscheduler
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.i18n import _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.drivers import abstract_driver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
AGENT_SCHEDULER_OPTS = [
|
||||
cfg.StrOpt('loadbalancer_pool_scheduler_driver',
|
||||
default='neutron.services.loadbalancer.agent_scheduler'
|
||||
'.ChanceScheduler',
|
||||
help=_('Driver to use for scheduling '
|
||||
'pool to a default loadbalancer agent')),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(AGENT_SCHEDULER_OPTS)
|
||||
|
||||
|
||||
class DriverNotSpecified(n_exc.NeutronException):
|
||||
message = _("Device driver for agent should be specified "
|
||||
"in plugin driver.")
|
||||
|
||||
|
||||
class LoadBalancerCallbacks(object):
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
# 2.0 Generic API for agent based drivers
|
||||
# - get_logical_device() handling changed;
|
||||
# - pool_deployed() and update_status() methods added;
|
||||
target = messaging.Target(version='2.0')
|
||||
|
||||
def __init__(self, plugin):
|
||||
super(LoadBalancerCallbacks, self).__init__()
|
||||
self.plugin = plugin
|
||||
|
||||
def get_ready_devices(self, context, host=None):
|
||||
with context.session.begin(subtransactions=True):
|
||||
agents = self.plugin.get_lbaas_agents(context,
|
||||
filters={'host': [host]})
|
||||
if not agents:
|
||||
return []
|
||||
elif len(agents) > 1:
|
||||
LOG.warning(_LW('Multiple lbaas agents found on host %s'),
|
||||
host)
|
||||
pools = self.plugin.list_pools_on_lbaas_agent(context,
|
||||
agents[0].id)
|
||||
pool_ids = [pool['id'] for pool in pools['pools']]
|
||||
|
||||
qry = context.session.query(loadbalancer_db.Pool.id)
|
||||
qry = qry.filter(loadbalancer_db.Pool.id.in_(pool_ids))
|
||||
qry = qry.filter(
|
||||
loadbalancer_db.Pool.status.in_(
|
||||
constants.ACTIVE_PENDING_STATUSES))
|
||||
up = True # makes pep8 and sqlalchemy happy
|
||||
qry = qry.filter(loadbalancer_db.Pool.admin_state_up == up)
|
||||
return [id for id, in qry]
|
||||
|
||||
def get_logical_device(self, context, pool_id=None):
|
||||
with context.session.begin(subtransactions=True):
|
||||
qry = context.session.query(loadbalancer_db.Pool)
|
||||
qry = qry.filter_by(id=pool_id)
|
||||
pool = qry.one()
|
||||
retval = {}
|
||||
retval['pool'] = self.plugin._make_pool_dict(pool)
|
||||
|
||||
if pool.vip:
|
||||
retval['vip'] = self.plugin._make_vip_dict(pool.vip)
|
||||
retval['vip']['port'] = (
|
||||
self.plugin._core_plugin._make_port_dict(pool.vip.port)
|
||||
)
|
||||
for fixed_ip in retval['vip']['port']['fixed_ips']:
|
||||
fixed_ip['subnet'] = (
|
||||
self.plugin._core_plugin.get_subnet(
|
||||
context,
|
||||
fixed_ip['subnet_id']
|
||||
)
|
||||
)
|
||||
retval['members'] = [
|
||||
self.plugin._make_member_dict(m)
|
||||
for m in pool.members if (
|
||||
m.status in constants.ACTIVE_PENDING_STATUSES or
|
||||
m.status == constants.INACTIVE)
|
||||
]
|
||||
retval['healthmonitors'] = [
|
||||
self.plugin._make_health_monitor_dict(hm.healthmonitor)
|
||||
for hm in pool.monitors
|
||||
if hm.status in constants.ACTIVE_PENDING_STATUSES
|
||||
]
|
||||
retval['driver'] = (
|
||||
self.plugin.drivers[pool.provider.provider_name].device_driver)
|
||||
|
||||
return retval
|
||||
|
||||
def pool_deployed(self, context, pool_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
qry = context.session.query(loadbalancer_db.Pool)
|
||||
qry = qry.filter_by(id=pool_id)
|
||||
pool = qry.one()
|
||||
|
||||
# set all resources to active
|
||||
if pool.status in constants.ACTIVE_PENDING_STATUSES:
|
||||
pool.status = constants.ACTIVE
|
||||
|
||||
if (pool.vip and pool.vip.status in
|
||||
constants.ACTIVE_PENDING_STATUSES):
|
||||
pool.vip.status = constants.ACTIVE
|
||||
|
||||
for m in pool.members:
|
||||
if m.status in constants.ACTIVE_PENDING_STATUSES:
|
||||
m.status = constants.ACTIVE
|
||||
|
||||
for hm in pool.monitors:
|
||||
if hm.status in constants.ACTIVE_PENDING_STATUSES:
|
||||
hm.status = constants.ACTIVE
|
||||
|
||||
def update_status(self, context, obj_type, obj_id, status):
|
||||
model_mapping = {
|
||||
'pool': loadbalancer_db.Pool,
|
||||
'vip': loadbalancer_db.Vip,
|
||||
'member': loadbalancer_db.Member,
|
||||
'health_monitor': loadbalancer_db.PoolMonitorAssociation
|
||||
}
|
||||
if obj_type not in model_mapping:
|
||||
raise n_exc.Invalid(_('Unknown object type: %s') % obj_type)
|
||||
try:
|
||||
if obj_type == 'health_monitor':
|
||||
self.plugin.update_pool_health_monitor(
|
||||
context, obj_id['monitor_id'], obj_id['pool_id'], status)
|
||||
else:
|
||||
self.plugin.update_status(
|
||||
context, model_mapping[obj_type], obj_id, status)
|
||||
except n_exc.NotFound:
|
||||
# update_status may come from agent on an object which was
|
||||
# already deleted from db with other request
|
||||
LOG.warning(_LW('Cannot update status: %(obj_type)s %(obj_id)s '
|
||||
'not found in the DB, it was probably deleted '
|
||||
'concurrently'),
|
||||
{'obj_type': obj_type, 'obj_id': obj_id})
|
||||
|
||||
def pool_destroyed(self, context, pool_id=None):
|
||||
"""Agent confirmation hook that a pool has been destroyed.
|
||||
|
||||
This method exists for subclasses to change the deletion
|
||||
behavior.
|
||||
"""
|
||||
pass
|
||||
|
||||
def plug_vip_port(self, context, port_id=None, host=None):
|
||||
if not port_id:
|
||||
return
|
||||
|
||||
try:
|
||||
port = self.plugin._core_plugin.get_port(
|
||||
context,
|
||||
port_id
|
||||
)
|
||||
except n_exc.PortNotFound:
|
||||
LOG.debug('Unable to find port %s to plug.', port_id)
|
||||
return
|
||||
|
||||
port['admin_state_up'] = True
|
||||
port['device_owner'] = 'neutron:' + constants.LOADBALANCER
|
||||
port['device_id'] = str(uuid.uuid5(uuid.NAMESPACE_DNS, str(host)))
|
||||
port[portbindings.HOST_ID] = host
|
||||
self.plugin._core_plugin.update_port(
|
||||
context,
|
||||
port_id,
|
||||
{'port': port}
|
||||
)
|
||||
|
||||
def unplug_vip_port(self, context, port_id=None, host=None):
|
||||
if not port_id:
|
||||
return
|
||||
|
||||
try:
|
||||
port = self.plugin._core_plugin.get_port(
|
||||
context,
|
||||
port_id
|
||||
)
|
||||
except n_exc.PortNotFound:
|
||||
LOG.debug('Unable to find port %s to unplug. This can occur when '
|
||||
'the Vip has been deleted first.',
|
||||
port_id)
|
||||
return
|
||||
|
||||
port['admin_state_up'] = False
|
||||
port['device_owner'] = ''
|
||||
port['device_id'] = ''
|
||||
|
||||
try:
|
||||
self.plugin._core_plugin.update_port(
|
||||
context,
|
||||
port_id,
|
||||
{'port': port}
|
||||
)
|
||||
|
||||
except n_exc.PortNotFound:
|
||||
LOG.debug('Unable to find port %s to unplug. This can occur when '
|
||||
'the Vip has been deleted first.',
|
||||
port_id)
|
||||
|
||||
def update_pool_stats(self, context, pool_id=None, stats=None, host=None):
|
||||
self.plugin.update_pool_stats(context, pool_id, data=stats)
|
||||
|
||||
|
||||
class LoadBalancerAgentApi(object):
|
||||
"""Plugin side of plugin to agent RPC API."""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
# 1.1 Support agent_updated call
|
||||
# 2.0 Generic API for agent based drivers
|
||||
# - modify/reload/destroy_pool methods were removed;
|
||||
# - added methods to handle create/update/delete for every lbaas
|
||||
# object individually;
|
||||
|
||||
def __init__(self, topic):
|
||||
target = messaging.Target(topic=topic, version='2.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def create_vip(self, context, vip, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'create_vip', vip=vip)
|
||||
|
||||
def update_vip(self, context, old_vip, vip, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'update_vip', old_vip=old_vip, vip=vip)
|
||||
|
||||
def delete_vip(self, context, vip, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'delete_vip', vip=vip)
|
||||
|
||||
def create_pool(self, context, pool, host, driver_name):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'create_pool', pool=pool, driver_name=driver_name)
|
||||
|
||||
def update_pool(self, context, old_pool, pool, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'update_pool', old_pool=old_pool, pool=pool)
|
||||
|
||||
def delete_pool(self, context, pool, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'delete_pool', pool=pool)
|
||||
|
||||
def create_member(self, context, member, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'create_member', member=member)
|
||||
|
||||
def update_member(self, context, old_member, member, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'update_member', old_member=old_member,
|
||||
member=member)
|
||||
|
||||
def delete_member(self, context, member, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'delete_member', member=member)
|
||||
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id,
|
||||
host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'create_pool_health_monitor',
|
||||
health_monitor=health_monitor, pool_id=pool_id)
|
||||
|
||||
def update_pool_health_monitor(self, context, old_health_monitor,
|
||||
health_monitor, pool_id, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'update_pool_health_monitor',
|
||||
old_health_monitor=old_health_monitor,
|
||||
health_monitor=health_monitor, pool_id=pool_id)
|
||||
|
||||
def delete_pool_health_monitor(self, context, health_monitor, pool_id,
|
||||
host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'delete_pool_health_monitor',
|
||||
health_monitor=health_monitor, pool_id=pool_id)
|
||||
|
||||
def agent_updated(self, context, admin_state_up, host):
|
||||
cctxt = self.client.prepare(server=host)
|
||||
cctxt.cast(context, 'agent_updated',
|
||||
payload={'admin_state_up': admin_state_up})
|
||||
|
||||
|
||||
class AgentDriverBase(abstract_driver.LoadBalancerAbstractDriver):
|
||||
|
||||
# name of device driver that should be used by the agent;
|
||||
# vendor specific plugin drivers must override it;
|
||||
device_driver = None
|
||||
|
||||
def __init__(self, plugin):
|
||||
if not self.device_driver:
|
||||
raise DriverNotSpecified()
|
||||
|
||||
self.agent_rpc = LoadBalancerAgentApi(topics.LOADBALANCER_AGENT)
|
||||
|
||||
self.plugin = plugin
|
||||
self._set_callbacks_on_plugin()
|
||||
self.plugin.agent_notifiers.update(
|
||||
{q_const.AGENT_TYPE_LOADBALANCER: self.agent_rpc})
|
||||
|
||||
self.pool_scheduler = importutils.import_object(
|
||||
cfg.CONF.loadbalancer_pool_scheduler_driver)
|
||||
|
||||
def _set_callbacks_on_plugin(self):
|
||||
# other agent based plugin driver might already set callbacks on plugin
|
||||
if hasattr(self.plugin, 'agent_callbacks'):
|
||||
return
|
||||
|
||||
self.plugin.agent_endpoints = [
|
||||
LoadBalancerCallbacks(self.plugin),
|
||||
agents_db.AgentExtRpcCallback(self.plugin)
|
||||
]
|
||||
self.plugin.conn = n_rpc.create_connection(new=True)
|
||||
self.plugin.conn.create_consumer(
|
||||
topics.LOADBALANCER_PLUGIN,
|
||||
self.plugin.agent_endpoints,
|
||||
fanout=False)
|
||||
self.plugin.conn.consume_in_threads()
|
||||
|
||||
def get_pool_agent(self, context, pool_id):
|
||||
agent = self.plugin.get_lbaas_agent_hosting_pool(context, pool_id)
|
||||
if not agent:
|
||||
raise lbaas_agentscheduler.NoActiveLbaasAgent(pool_id=pool_id)
|
||||
return agent['agent']
|
||||
|
||||
def create_vip(self, context, vip):
|
||||
agent = self.get_pool_agent(context, vip['pool_id'])
|
||||
self.agent_rpc.create_vip(context, vip, agent['host'])
|
||||
|
||||
def update_vip(self, context, old_vip, vip):
|
||||
agent = self.get_pool_agent(context, vip['pool_id'])
|
||||
if vip['status'] in constants.ACTIVE_PENDING_STATUSES:
|
||||
self.agent_rpc.update_vip(context, old_vip, vip, agent['host'])
|
||||
else:
|
||||
self.agent_rpc.delete_vip(context, vip, agent['host'])
|
||||
|
||||
def delete_vip(self, context, vip):
|
||||
self.plugin._delete_db_vip(context, vip['id'])
|
||||
agent = self.get_pool_agent(context, vip['pool_id'])
|
||||
self.agent_rpc.delete_vip(context, vip, agent['host'])
|
||||
|
||||
def create_pool(self, context, pool):
|
||||
agent = self.pool_scheduler.schedule(self.plugin, context, pool,
|
||||
self.device_driver)
|
||||
if not agent:
|
||||
raise lbaas_agentscheduler.NoEligibleLbaasAgent(pool_id=pool['id'])
|
||||
self.agent_rpc.create_pool(context, pool, agent['host'],
|
||||
self.device_driver)
|
||||
|
||||
def update_pool(self, context, old_pool, pool):
|
||||
agent = self.get_pool_agent(context, pool['id'])
|
||||
if pool['status'] in constants.ACTIVE_PENDING_STATUSES:
|
||||
self.agent_rpc.update_pool(context, old_pool, pool,
|
||||
agent['host'])
|
||||
else:
|
||||
self.agent_rpc.delete_pool(context, pool, agent['host'])
|
||||
|
||||
def delete_pool(self, context, pool):
|
||||
# get agent first to know host as binding will be deleted
|
||||
# after pool is deleted from db
|
||||
agent = self.plugin.get_lbaas_agent_hosting_pool(context, pool['id'])
|
||||
self.plugin._delete_db_pool(context, pool['id'])
|
||||
if agent:
|
||||
self.agent_rpc.delete_pool(context, pool, agent['agent']['host'])
|
||||
|
||||
def create_member(self, context, member):
|
||||
agent = self.get_pool_agent(context, member['pool_id'])
|
||||
self.agent_rpc.create_member(context, member, agent['host'])
|
||||
|
||||
def update_member(self, context, old_member, member):
|
||||
agent = self.get_pool_agent(context, member['pool_id'])
|
||||
# member may change pool id
|
||||
if member['pool_id'] != old_member['pool_id']:
|
||||
old_pool_agent = self.plugin.get_lbaas_agent_hosting_pool(
|
||||
context, old_member['pool_id'])
|
||||
if old_pool_agent:
|
||||
self.agent_rpc.delete_member(context, old_member,
|
||||
old_pool_agent['agent']['host'])
|
||||
self.agent_rpc.create_member(context, member, agent['host'])
|
||||
else:
|
||||
self.agent_rpc.update_member(context, old_member, member,
|
||||
agent['host'])
|
||||
|
||||
def delete_member(self, context, member):
|
||||
self.plugin._delete_db_member(context, member['id'])
|
||||
agent = self.get_pool_agent(context, member['pool_id'])
|
||||
self.agent_rpc.delete_member(context, member, agent['host'])
|
||||
|
||||
def create_pool_health_monitor(self, context, healthmon, pool_id):
|
||||
# healthmon is not used here
|
||||
agent = self.get_pool_agent(context, pool_id)
|
||||
self.agent_rpc.create_pool_health_monitor(context, healthmon,
|
||||
pool_id, agent['host'])
|
||||
|
||||
def update_pool_health_monitor(self, context, old_health_monitor,
|
||||
health_monitor, pool_id):
|
||||
agent = self.get_pool_agent(context, pool_id)
|
||||
self.agent_rpc.update_pool_health_monitor(context, old_health_monitor,
|
||||
health_monitor, pool_id,
|
||||
agent['host'])
|
||||
|
||||
def delete_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
self.plugin._delete_db_pool_health_monitor(
|
||||
context, health_monitor['id'], pool_id
|
||||
)
|
||||
|
||||
agent = self.get_pool_agent(context, pool_id)
|
||||
self.agent_rpc.delete_pool_health_monitor(context, health_monitor,
|
||||
pool_id, agent['host'])
|
||||
|
||||
def stats(self, context, pool_id):
|
||||
pass
|
|
@ -1,87 +0,0 @@
|
|||
# Copyright 2014 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.
|
||||
|
||||
from neutron.db.loadbalancer import loadbalancer_db as lb_db
|
||||
from neutron.services.loadbalancer.drivers import driver_mixins
|
||||
|
||||
|
||||
class NotImplementedManager(object):
|
||||
"""Helper class to make any subclass of LBAbstractDriver explode if it
|
||||
is missing any of the required object managers.
|
||||
"""
|
||||
|
||||
def create(self, context, obj):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, context, old_obj, obj):
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self, context, obj):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class LoadBalancerBaseDriver(object):
|
||||
"""LBaaSv2 object model drivers should subclass LBAbstractDriver, and
|
||||
initialize the following manager classes to create, update, and delete
|
||||
the various load balancer objects.
|
||||
"""
|
||||
|
||||
load_balancer = NotImplementedManager()
|
||||
listener = NotImplementedManager()
|
||||
pool = NotImplementedManager()
|
||||
member = NotImplementedManager()
|
||||
health_monitor = NotImplementedManager()
|
||||
|
||||
def __init__(self, plugin):
|
||||
self.plugin = plugin
|
||||
|
||||
|
||||
class BaseLoadBalancerManager(driver_mixins.BaseRefreshMixin,
|
||||
driver_mixins.BaseStatsMixin,
|
||||
driver_mixins.BaseStatusUpdateMixin,
|
||||
driver_mixins.BaseManagerMixin):
|
||||
|
||||
def __init__(self, driver):
|
||||
super(BaseLoadBalancerManager, self).__init__(driver)
|
||||
# TODO(dougw), use lb_db.LoadBalancer when v2 lbaas
|
||||
# TODO(dougw), get rid of __init__() in StatusHelperManager, and
|
||||
# the if is not None clauses; after fixing this next line,
|
||||
# it can become a mandatory variable for that subclass.
|
||||
self.model_class = None
|
||||
|
||||
|
||||
class BaseListenerManager(driver_mixins.BaseManagerMixin):
|
||||
pass
|
||||
|
||||
|
||||
class BasePoolManager(driver_mixins.BaseStatusUpdateMixin,
|
||||
driver_mixins.BaseManagerMixin):
|
||||
|
||||
def __init__(self, driver):
|
||||
super(BasePoolManager, self).__init__(driver)
|
||||
self.model_class = lb_db.Pool
|
||||
|
||||
|
||||
class BaseMemberManager(driver_mixins.BaseStatusUpdateMixin,
|
||||
driver_mixins.BaseManagerMixin):
|
||||
|
||||
def __init__(self, driver):
|
||||
super(BaseMemberManager, self).__init__(driver)
|
||||
self.model_class = lb_db.Member
|
||||
|
||||
|
||||
class BaseHealthMonitorManager(
|
||||
driver_mixins.BaseHealthMonitorStatusUpdateMixin,
|
||||
driver_mixins.BaseManagerMixin):
|
||||
pass
|
|
@ -1,85 +0,0 @@
|
|||
# Copyright 2014 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 abc
|
||||
import six
|
||||
|
||||
from neutron.plugins.common import constants
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseManagerMixin(object):
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
@abc.abstractmethod
|
||||
def create(self, context, obj):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, context, obj_old, obj):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete(self, context, obj):
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseRefreshMixin(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def refresh(self, context, obj):
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseStatsMixin(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def stats(self, context, obj):
|
||||
pass
|
||||
|
||||
|
||||
class BaseStatusUpdateMixin(object):
|
||||
|
||||
# Status update helpers
|
||||
# Note: You must set self.model_class to an appropriate neutron model
|
||||
# in your base manager class.
|
||||
|
||||
def active(self, context, model_id):
|
||||
if self.model_class is not None:
|
||||
self.driver.plugin.update_status(context, self.model_class,
|
||||
model_id, constants.ACTIVE)
|
||||
|
||||
def failed(self, context, model_id):
|
||||
if self.model_class is not None:
|
||||
self.driver.plugin.update_status(context, self.model_class,
|
||||
model_id, constants.ERROR)
|
||||
|
||||
|
||||
class BaseHealthMonitorStatusUpdateMixin(object):
|
||||
|
||||
def active(self, context, health_monitor_id, pool_id):
|
||||
self.driver.plugin.update_pool_health_monitor(context,
|
||||
health_monitor_id,
|
||||
pool_id,
|
||||
constants.ACTIVE)
|
||||
|
||||
def failed(self, context, health_monitor_id, pool_id):
|
||||
self.driver.plugin.update_pool_health_monitor(context,
|
||||
health_monitor_id,
|
||||
pool_id,
|
||||
constants.ERROR)
|
|
@ -1,235 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 itertools
|
||||
from six import moves
|
||||
|
||||
from neutron.agent.linux import utils
|
||||
from neutron.plugins.common import constants as qconstants
|
||||
from neutron.services.loadbalancer import constants
|
||||
|
||||
|
||||
PROTOCOL_MAP = {
|
||||
constants.PROTOCOL_TCP: 'tcp',
|
||||
constants.PROTOCOL_HTTP: 'http',
|
||||
constants.PROTOCOL_HTTPS: 'tcp',
|
||||
}
|
||||
|
||||
BALANCE_MAP = {
|
||||
constants.LB_METHOD_ROUND_ROBIN: 'roundrobin',
|
||||
constants.LB_METHOD_LEAST_CONNECTIONS: 'leastconn',
|
||||
constants.LB_METHOD_SOURCE_IP: 'source'
|
||||
}
|
||||
|
||||
STATS_MAP = {
|
||||
constants.STATS_ACTIVE_CONNECTIONS: 'scur',
|
||||
constants.STATS_MAX_CONNECTIONS: 'smax',
|
||||
constants.STATS_CURRENT_SESSIONS: 'scur',
|
||||
constants.STATS_MAX_SESSIONS: 'smax',
|
||||
constants.STATS_TOTAL_CONNECTIONS: 'stot',
|
||||
constants.STATS_TOTAL_SESSIONS: 'stot',
|
||||
constants.STATS_IN_BYTES: 'bin',
|
||||
constants.STATS_OUT_BYTES: 'bout',
|
||||
constants.STATS_CONNECTION_ERRORS: 'econ',
|
||||
constants.STATS_RESPONSE_ERRORS: 'eresp'
|
||||
}
|
||||
|
||||
ACTIVE_PENDING_STATUSES = qconstants.ACTIVE_PENDING_STATUSES
|
||||
INACTIVE = qconstants.INACTIVE
|
||||
|
||||
|
||||
def save_config(conf_path, logical_config, socket_path=None,
|
||||
user_group='nogroup'):
|
||||
"""Convert a logical configuration to the HAProxy version."""
|
||||
data = []
|
||||
data.extend(_build_global(logical_config, socket_path=socket_path,
|
||||
user_group=user_group))
|
||||
data.extend(_build_defaults(logical_config))
|
||||
data.extend(_build_frontend(logical_config))
|
||||
data.extend(_build_backend(logical_config))
|
||||
utils.replace_file(conf_path, '\n'.join(data))
|
||||
|
||||
|
||||
def _build_global(config, socket_path=None, user_group='nogroup'):
|
||||
opts = [
|
||||
'daemon',
|
||||
'user nobody',
|
||||
'group %s' % user_group,
|
||||
'log /dev/log local0',
|
||||
'log /dev/log local1 notice'
|
||||
]
|
||||
|
||||
if socket_path:
|
||||
opts.append('stats socket %s mode 0666 level user' % socket_path)
|
||||
|
||||
return itertools.chain(['global'], ('\t' + o for o in opts))
|
||||
|
||||
|
||||
def _build_defaults(config):
|
||||
opts = [
|
||||
'log global',
|
||||
'retries 3',
|
||||
'option redispatch',
|
||||
'timeout connect 5000',
|
||||
'timeout client 50000',
|
||||
'timeout server 50000',
|
||||
]
|
||||
|
||||
return itertools.chain(['defaults'], ('\t' + o for o in opts))
|
||||
|
||||
|
||||
def _build_frontend(config):
|
||||
protocol = config['vip']['protocol']
|
||||
|
||||
opts = [
|
||||
'option tcplog',
|
||||
'bind %s:%d' % (
|
||||
_get_first_ip_from_port(config['vip']['port']),
|
||||
config['vip']['protocol_port']
|
||||
),
|
||||
'mode %s' % PROTOCOL_MAP[protocol],
|
||||
'default_backend %s' % config['pool']['id'],
|
||||
]
|
||||
|
||||
if config['vip']['connection_limit'] >= 0:
|
||||
opts.append('maxconn %s' % config['vip']['connection_limit'])
|
||||
|
||||
if protocol == constants.PROTOCOL_HTTP:
|
||||
opts.append('option forwardfor')
|
||||
|
||||
return itertools.chain(
|
||||
['frontend %s' % config['vip']['id']],
|
||||
('\t' + o for o in opts)
|
||||
)
|
||||
|
||||
|
||||
def _build_backend(config):
|
||||
protocol = config['pool']['protocol']
|
||||
lb_method = config['pool']['lb_method']
|
||||
|
||||
opts = [
|
||||
'mode %s' % PROTOCOL_MAP[protocol],
|
||||
'balance %s' % BALANCE_MAP.get(lb_method, 'roundrobin')
|
||||
]
|
||||
|
||||
if protocol == constants.PROTOCOL_HTTP:
|
||||
opts.append('option forwardfor')
|
||||
|
||||
# add the first health_monitor (if available)
|
||||
server_addon, health_opts = _get_server_health_option(config)
|
||||
opts.extend(health_opts)
|
||||
|
||||
# add session persistence (if available)
|
||||
persist_opts = _get_session_persistence(config)
|
||||
opts.extend(persist_opts)
|
||||
|
||||
# add the members
|
||||
for member in config['members']:
|
||||
if ((member['status'] in ACTIVE_PENDING_STATUSES or
|
||||
member['status'] == INACTIVE)
|
||||
and member['admin_state_up']):
|
||||
server = (('server %(id)s %(address)s:%(protocol_port)s '
|
||||
'weight %(weight)s') % member) + server_addon
|
||||
if _has_http_cookie_persistence(config):
|
||||
server += ' cookie %d' % config['members'].index(member)
|
||||
opts.append(server)
|
||||
|
||||
return itertools.chain(
|
||||
['backend %s' % config['pool']['id']],
|
||||
('\t' + o for o in opts)
|
||||
)
|
||||
|
||||
|
||||
def _get_first_ip_from_port(port):
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
return fixed_ip['ip_address']
|
||||
|
||||
|
||||
def _get_server_health_option(config):
|
||||
"""return the first active health option."""
|
||||
for m in config['healthmonitors']:
|
||||
# not checking the status of healthmonitor for two reasons:
|
||||
# 1) status field is absent in HealthMonitor model
|
||||
# 2) only active HealthMonitors are fetched with
|
||||
# LoadBalancerCallbacks.get_logical_device
|
||||
if m['admin_state_up']:
|
||||
monitor = m
|
||||
break
|
||||
else:
|
||||
return '', []
|
||||
|
||||
server_addon = ' check inter %(delay)ds fall %(max_retries)d' % monitor
|
||||
opts = [
|
||||
'timeout check %ds' % monitor['timeout']
|
||||
]
|
||||
|
||||
if monitor['type'] in (constants.HEALTH_MONITOR_HTTP,
|
||||
constants.HEALTH_MONITOR_HTTPS):
|
||||
opts.append('option httpchk %(http_method)s %(url_path)s' % monitor)
|
||||
opts.append(
|
||||
'http-check expect rstatus %s' %
|
||||
'|'.join(_expand_expected_codes(monitor['expected_codes']))
|
||||
)
|
||||
|
||||
if monitor['type'] == constants.HEALTH_MONITOR_HTTPS:
|
||||
opts.append('option ssl-hello-chk')
|
||||
|
||||
return server_addon, opts
|
||||
|
||||
|
||||
def _get_session_persistence(config):
|
||||
persistence = config['vip'].get('session_persistence')
|
||||
if not persistence:
|
||||
return []
|
||||
|
||||
opts = []
|
||||
if persistence['type'] == constants.SESSION_PERSISTENCE_SOURCE_IP:
|
||||
opts.append('stick-table type ip size 10k')
|
||||
opts.append('stick on src')
|
||||
elif (persistence['type'] == constants.SESSION_PERSISTENCE_HTTP_COOKIE and
|
||||
config.get('members')):
|
||||
opts.append('cookie SRV insert indirect nocache')
|
||||
elif (persistence['type'] == constants.SESSION_PERSISTENCE_APP_COOKIE and
|
||||
persistence.get('cookie_name')):
|
||||
opts.append('appsession %s len 56 timeout 3h' %
|
||||
persistence['cookie_name'])
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def _has_http_cookie_persistence(config):
|
||||
return (config['vip'].get('session_persistence') and
|
||||
config['vip']['session_persistence']['type'] ==
|
||||
constants.SESSION_PERSISTENCE_HTTP_COOKIE)
|
||||
|
||||
|
||||
def _expand_expected_codes(codes):
|
||||
"""Expand the expected code string in set of codes.
|
||||
|
||||
200-204 -> 200, 201, 202, 204
|
||||
200, 203 -> 200, 203
|
||||
"""
|
||||
|
||||
retval = set()
|
||||
for code in codes.replace(',', ' ').split(' '):
|
||||
code = code.strip()
|
||||
|
||||
if not code:
|
||||
continue
|
||||
elif '-' in code:
|
||||
low, hi = code.split('-')[:2]
|
||||
retval.update(str(i) for i in moves.xrange(int(low), int(hi) + 1))
|
||||
else:
|
||||
retval.add(code)
|
||||
return retval
|
|
@ -1,395 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 os
|
||||
import shutil
|
||||
import socket
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import excutils
|
||||
from oslo.utils import importutils
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils
|
||||
from neutron.common import exceptions
|
||||
from neutron.common import utils as n_utils
|
||||
from neutron.i18n import _LE, _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.agent import agent_device_driver
|
||||
from neutron.services.loadbalancer import constants as lb_const
|
||||
from neutron.services.loadbalancer.drivers.haproxy import cfg as hacfg
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
NS_PREFIX = 'qlbaas-'
|
||||
DRIVER_NAME = 'haproxy_ns'
|
||||
|
||||
STATE_PATH_DEFAULT = '$state_path/lbaas'
|
||||
USER_GROUP_DEFAULT = 'nogroup'
|
||||
OPTS = [
|
||||
cfg.StrOpt(
|
||||
'loadbalancer_state_path',
|
||||
default=STATE_PATH_DEFAULT,
|
||||
help=_('Location to store config and state files'),
|
||||
deprecated_opts=[cfg.DeprecatedOpt('loadbalancer_state_path',
|
||||
group='DEFAULT')],
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'user_group',
|
||||
default=USER_GROUP_DEFAULT,
|
||||
help=_('The user group'),
|
||||
deprecated_opts=[cfg.DeprecatedOpt('user_group', group='DEFAULT')],
|
||||
),
|
||||
cfg.IntOpt(
|
||||
'send_gratuitous_arp',
|
||||
default=3,
|
||||
help=_('When delete and re-add the same vip, send this many '
|
||||
'gratuitous ARPs to flush the ARP cache in the Router. '
|
||||
'Set it below or equal to 0 to disable this feature.'),
|
||||
)
|
||||
]
|
||||
cfg.CONF.register_opts(OPTS, 'haproxy')
|
||||
|
||||
|
||||
class HaproxyNSDriver(agent_device_driver.AgentDeviceDriver):
|
||||
def __init__(self, conf, plugin_rpc):
|
||||
self.conf = conf
|
||||
self.root_helper = config.get_root_helper(conf)
|
||||
self.state_path = conf.haproxy.loadbalancer_state_path
|
||||
try:
|
||||
vif_driver = importutils.import_object(conf.interface_driver, conf)
|
||||
except ImportError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
msg = (_('Error importing interface driver: %s')
|
||||
% conf.interface_driver)
|
||||
LOG.error(msg)
|
||||
|
||||
self.vif_driver = vif_driver
|
||||
self.plugin_rpc = plugin_rpc
|
||||
self.pool_to_port_id = {}
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return DRIVER_NAME
|
||||
|
||||
def create(self, logical_config):
|
||||
pool_id = logical_config['pool']['id']
|
||||
namespace = get_ns_name(pool_id)
|
||||
|
||||
self._plug(namespace, logical_config['vip']['port'])
|
||||
self._spawn(logical_config)
|
||||
|
||||
def update(self, logical_config):
|
||||
pool_id = logical_config['pool']['id']
|
||||
pid_path = self._get_state_file_path(pool_id, 'pid')
|
||||
|
||||
extra_args = ['-sf']
|
||||
extra_args.extend(p.strip() for p in open(pid_path, 'r'))
|
||||
self._spawn(logical_config, extra_args)
|
||||
|
||||
def _spawn(self, logical_config, extra_cmd_args=()):
|
||||
pool_id = logical_config['pool']['id']
|
||||
namespace = get_ns_name(pool_id)
|
||||
conf_path = self._get_state_file_path(pool_id, 'conf')
|
||||
pid_path = self._get_state_file_path(pool_id, 'pid')
|
||||
sock_path = self._get_state_file_path(pool_id, 'sock')
|
||||
user_group = self.conf.haproxy.user_group
|
||||
|
||||
hacfg.save_config(conf_path, logical_config, sock_path, user_group)
|
||||
cmd = ['haproxy', '-f', conf_path, '-p', pid_path]
|
||||
cmd.extend(extra_cmd_args)
|
||||
|
||||
ns = ip_lib.IPWrapper(self.root_helper, namespace)
|
||||
ns.netns.execute(cmd)
|
||||
|
||||
# remember the pool<>port mapping
|
||||
self.pool_to_port_id[pool_id] = logical_config['vip']['port']['id']
|
||||
|
||||
@n_utils.synchronized('haproxy-driver')
|
||||
def undeploy_instance(self, pool_id, cleanup_namespace=False):
|
||||
namespace = get_ns_name(pool_id)
|
||||
ns = ip_lib.IPWrapper(self.root_helper, namespace)
|
||||
pid_path = self._get_state_file_path(pool_id, 'pid')
|
||||
|
||||
# kill the process
|
||||
kill_pids_in_file(self.root_helper, pid_path)
|
||||
|
||||
# unplug the ports
|
||||
if pool_id in self.pool_to_port_id:
|
||||
self._unplug(namespace, self.pool_to_port_id[pool_id])
|
||||
|
||||
# delete all devices from namespace;
|
||||
# used when deleting orphans and port_id is not known for pool_id
|
||||
if cleanup_namespace:
|
||||
for device in ns.get_devices(exclude_loopback=True):
|
||||
self.vif_driver.unplug(device.name, namespace=namespace)
|
||||
|
||||
# remove the configuration directory
|
||||
conf_dir = os.path.dirname(self._get_state_file_path(pool_id, ''))
|
||||
if os.path.isdir(conf_dir):
|
||||
shutil.rmtree(conf_dir)
|
||||
ns.garbage_collect_namespace()
|
||||
|
||||
def exists(self, pool_id):
|
||||
namespace = get_ns_name(pool_id)
|
||||
root_ns = ip_lib.IPWrapper(self.root_helper)
|
||||
|
||||
socket_path = self._get_state_file_path(pool_id, 'sock', False)
|
||||
if root_ns.netns.exists(namespace) and os.path.exists(socket_path):
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.connect(socket_path)
|
||||
return True
|
||||
except socket.error:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_stats(self, pool_id):
|
||||
socket_path = self._get_state_file_path(pool_id, 'sock', False)
|
||||
TYPE_BACKEND_REQUEST = 2
|
||||
TYPE_SERVER_REQUEST = 4
|
||||
if os.path.exists(socket_path):
|
||||
parsed_stats = self._get_stats_from_socket(
|
||||
socket_path,
|
||||
entity_type=TYPE_BACKEND_REQUEST | TYPE_SERVER_REQUEST)
|
||||
pool_stats = self._get_backend_stats(parsed_stats)
|
||||
pool_stats['members'] = self._get_servers_stats(parsed_stats)
|
||||
return pool_stats
|
||||
else:
|
||||
LOG.warn(_LW('Stats socket not found for pool %s'), pool_id)
|
||||
return {}
|
||||
|
||||
def _get_backend_stats(self, parsed_stats):
|
||||
TYPE_BACKEND_RESPONSE = '1'
|
||||
for stats in parsed_stats:
|
||||
if stats.get('type') == TYPE_BACKEND_RESPONSE:
|
||||
unified_stats = dict((k, stats.get(v, ''))
|
||||
for k, v in hacfg.STATS_MAP.items())
|
||||
return unified_stats
|
||||
|
||||
return {}
|
||||
|
||||
def _get_servers_stats(self, parsed_stats):
|
||||
TYPE_SERVER_RESPONSE = '2'
|
||||
res = {}
|
||||
for stats in parsed_stats:
|
||||
if stats.get('type') == TYPE_SERVER_RESPONSE:
|
||||
res[stats['svname']] = {
|
||||
lb_const.STATS_STATUS: (constants.INACTIVE
|
||||
if stats['status'] == 'DOWN'
|
||||
else constants.ACTIVE),
|
||||
lb_const.STATS_HEALTH: stats['check_status'],
|
||||
lb_const.STATS_FAILED_CHECKS: stats['chkfail']
|
||||
}
|
||||
return res
|
||||
|
||||
def _get_stats_from_socket(self, socket_path, entity_type):
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.connect(socket_path)
|
||||
s.send('show stat -1 %s -1\n' % entity_type)
|
||||
raw_stats = ''
|
||||
chunk_size = 1024
|
||||
while True:
|
||||
chunk = s.recv(chunk_size)
|
||||
raw_stats += chunk
|
||||
if len(chunk) < chunk_size:
|
||||
break
|
||||
|
||||
return self._parse_stats(raw_stats)
|
||||
except socket.error as e:
|
||||
LOG.warn(_LW('Error while connecting to stats socket: %s'), e)
|
||||
return {}
|
||||
|
||||
def _parse_stats(self, raw_stats):
|
||||
stat_lines = raw_stats.splitlines()
|
||||
if len(stat_lines) < 2:
|
||||
return []
|
||||
stat_names = [name.strip('# ') for name in stat_lines[0].split(',')]
|
||||
res_stats = []
|
||||
for raw_values in stat_lines[1:]:
|
||||
if not raw_values:
|
||||
continue
|
||||
stat_values = [value.strip() for value in raw_values.split(',')]
|
||||
res_stats.append(dict(zip(stat_names, stat_values)))
|
||||
|
||||
return res_stats
|
||||
|
||||
def _get_state_file_path(self, pool_id, kind, ensure_state_dir=True):
|
||||
"""Returns the file name for a given kind of config file."""
|
||||
confs_dir = os.path.abspath(os.path.normpath(self.state_path))
|
||||
conf_dir = os.path.join(confs_dir, pool_id)
|
||||
if ensure_state_dir:
|
||||
if not os.path.isdir(conf_dir):
|
||||
os.makedirs(conf_dir, 0o755)
|
||||
return os.path.join(conf_dir, kind)
|
||||
|
||||
def _plug(self, namespace, port, reuse_existing=True):
|
||||
self.plugin_rpc.plug_vip_port(port['id'])
|
||||
interface_name = self.vif_driver.get_device_name(Wrap(port))
|
||||
|
||||
if ip_lib.device_exists(interface_name, self.root_helper, namespace):
|
||||
if not reuse_existing:
|
||||
raise exceptions.PreexistingDeviceFailure(
|
||||
dev_name=interface_name
|
||||
)
|
||||
else:
|
||||
self.vif_driver.plug(
|
||||
port['network_id'],
|
||||
port['id'],
|
||||
interface_name,
|
||||
port['mac_address'],
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
cidrs = [
|
||||
'%s/%s' % (ip['ip_address'],
|
||||
netaddr.IPNetwork(ip['subnet']['cidr']).prefixlen)
|
||||
for ip in port['fixed_ips']
|
||||
]
|
||||
self.vif_driver.init_l3(interface_name, cidrs, namespace=namespace)
|
||||
|
||||
gw_ip = port['fixed_ips'][0]['subnet'].get('gateway_ip')
|
||||
|
||||
if not gw_ip:
|
||||
host_routes = port['fixed_ips'][0]['subnet'].get('host_routes', [])
|
||||
for host_route in host_routes:
|
||||
if host_route['destination'] == "0.0.0.0/0":
|
||||
gw_ip = host_route['nexthop']
|
||||
break
|
||||
|
||||
if gw_ip:
|
||||
cmd = ['route', 'add', 'default', 'gw', gw_ip]
|
||||
ip_wrapper = ip_lib.IPWrapper(self.root_helper,
|
||||
namespace=namespace)
|
||||
ip_wrapper.netns.execute(cmd, check_exit_code=False)
|
||||
# When delete and re-add the same vip, we need to
|
||||
# send gratuitous ARP to flush the ARP cache in the Router.
|
||||
gratuitous_arp = self.conf.haproxy.send_gratuitous_arp
|
||||
if gratuitous_arp > 0:
|
||||
for ip in port['fixed_ips']:
|
||||
cmd_arping = ['arping', '-U',
|
||||
'-I', interface_name,
|
||||
'-c', gratuitous_arp,
|
||||
ip['ip_address']]
|
||||
ip_wrapper.netns.execute(cmd_arping, check_exit_code=False)
|
||||
|
||||
def _unplug(self, namespace, port_id):
|
||||
port_stub = {'id': port_id}
|
||||
self.plugin_rpc.unplug_vip_port(port_id)
|
||||
interface_name = self.vif_driver.get_device_name(Wrap(port_stub))
|
||||
self.vif_driver.unplug(interface_name, namespace=namespace)
|
||||
|
||||
@n_utils.synchronized('haproxy-driver')
|
||||
def deploy_instance(self, logical_config):
|
||||
# do actual deploy only if vip and pool are configured and active
|
||||
if (not logical_config or
|
||||
'vip' not in logical_config or
|
||||
(logical_config['vip']['status'] not in
|
||||
constants.ACTIVE_PENDING_STATUSES) or
|
||||
not logical_config['vip']['admin_state_up'] or
|
||||
(logical_config['pool']['status'] not in
|
||||
constants.ACTIVE_PENDING_STATUSES) or
|
||||
not logical_config['pool']['admin_state_up']):
|
||||
return
|
||||
|
||||
if self.exists(logical_config['pool']['id']):
|
||||
self.update(logical_config)
|
||||
else:
|
||||
self.create(logical_config)
|
||||
|
||||
def _refresh_device(self, pool_id):
|
||||
logical_config = self.plugin_rpc.get_logical_device(pool_id)
|
||||
self.deploy_instance(logical_config)
|
||||
|
||||
def create_vip(self, vip):
|
||||
self._refresh_device(vip['pool_id'])
|
||||
|
||||
def update_vip(self, old_vip, vip):
|
||||
self._refresh_device(vip['pool_id'])
|
||||
|
||||
def delete_vip(self, vip):
|
||||
self.undeploy_instance(vip['pool_id'])
|
||||
|
||||
def create_pool(self, pool):
|
||||
# nothing to do here because a pool needs a vip to be useful
|
||||
pass
|
||||
|
||||
def update_pool(self, old_pool, pool):
|
||||
self._refresh_device(pool['id'])
|
||||
|
||||
def delete_pool(self, pool):
|
||||
# delete_pool may be called before vip deletion in case
|
||||
# pool's admin state set to down
|
||||
if self.exists(pool['id']):
|
||||
self.undeploy_instance(pool['id'])
|
||||
|
||||
def create_member(self, member):
|
||||
self._refresh_device(member['pool_id'])
|
||||
|
||||
def update_member(self, old_member, member):
|
||||
self._refresh_device(member['pool_id'])
|
||||
|
||||
def delete_member(self, member):
|
||||
self._refresh_device(member['pool_id'])
|
||||
|
||||
def create_pool_health_monitor(self, health_monitor, pool_id):
|
||||
self._refresh_device(pool_id)
|
||||
|
||||
def update_pool_health_monitor(self, old_health_monitor, health_monitor,
|
||||
pool_id):
|
||||
self._refresh_device(pool_id)
|
||||
|
||||
def delete_pool_health_monitor(self, health_monitor, pool_id):
|
||||
self._refresh_device(pool_id)
|
||||
|
||||
def remove_orphans(self, known_pool_ids):
|
||||
if not os.path.exists(self.state_path):
|
||||
return
|
||||
|
||||
orphans = (pool_id for pool_id in os.listdir(self.state_path)
|
||||
if pool_id not in known_pool_ids)
|
||||
for pool_id in orphans:
|
||||
if self.exists(pool_id):
|
||||
self.undeploy_instance(pool_id, cleanup_namespace=True)
|
||||
|
||||
|
||||
# NOTE (markmcclain) For compliance with interface.py which expects objects
|
||||
class Wrap(object):
|
||||
"""A light attribute wrapper for compatibility with the interface lib."""
|
||||
def __init__(self, d):
|
||||
self.__dict__.update(d)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__dict__[key]
|
||||
|
||||
|
||||
def get_ns_name(namespace_id):
|
||||
return NS_PREFIX + namespace_id
|
||||
|
||||
|
||||
def kill_pids_in_file(root_helper, pid_path):
|
||||
if os.path.exists(pid_path):
|
||||
with open(pid_path, 'r') as pids:
|
||||
for pid in pids:
|
||||
pid = pid.strip()
|
||||
try:
|
||||
utils.execute(['kill', '-9', pid], root_helper)
|
||||
except RuntimeError:
|
||||
LOG.exception(
|
||||
_LE('Unable to kill haproxy process: %s'),
|
||||
pid
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.services.loadbalancer.drivers.common import agent_driver_base
|
||||
from neutron.services.loadbalancer.drivers.haproxy import namespace_driver
|
||||
|
||||
|
||||
class HaproxyOnHostPluginDriver(agent_driver_base.AgentDriverBase):
|
||||
device_driver = namespace_driver.DRIVER_NAME
|
|
@ -1,106 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.loadbalancer.drivers import driver_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoggingNoopLoadBalancerDriver(driver_base.LoadBalancerBaseDriver):
|
||||
|
||||
def __init__(self, plugin):
|
||||
self.plugin = plugin
|
||||
|
||||
# Each of the major LBaaS objects in the neutron database
|
||||
# need a corresponding manager/handler class.
|
||||
#
|
||||
# Put common things that are shared across the entire driver, like
|
||||
# config or a rest client handle, here.
|
||||
#
|
||||
# This function is executed when neutron-server starts.
|
||||
|
||||
self.load_balancer = LoggingNoopLoadBalancerManager(self)
|
||||
self.listener = LoggingNoopListenerManager(self)
|
||||
self.pool = LoggingNoopPoolManager(self)
|
||||
self.member = LoggingNoopMemberManager(self)
|
||||
self.health_monitor = LoggingNoopHealthMonitorManager(self)
|
||||
|
||||
|
||||
class LoggingNoopCommonManager(object):
|
||||
|
||||
def create(self, context, obj):
|
||||
LOG.debug("LB %s no-op, create %s", self.__class__.__name__, obj.id)
|
||||
self.active(context, obj.id)
|
||||
|
||||
def update(self, context, old_obj, obj):
|
||||
LOG.debug("LB %s no-op, update %s", self.__class__.__name__, obj.id)
|
||||
self.active(context, obj.id)
|
||||
|
||||
def delete(self, context, obj):
|
||||
LOG.debug("LB %s no-op, delete %s", self.__class__.__name__, obj.id)
|
||||
|
||||
|
||||
class LoggingNoopLoadBalancerManager(LoggingNoopCommonManager,
|
||||
driver_base.BaseLoadBalancerManager):
|
||||
|
||||
def refresh(self, context, lb_obj, force=False):
|
||||
# This is intended to trigger the backend to check and repair
|
||||
# the state of this load balancer and all of its dependent objects
|
||||
LOG.debug("LB pool refresh %s, force=%s", lb_obj.id, force)
|
||||
|
||||
def stats(self, context, lb_obj):
|
||||
LOG.debug("LB stats %s", lb_obj.id)
|
||||
return {
|
||||
"bytes_in": 0,
|
||||
"bytes_out": 0,
|
||||
"active_connections": 0,
|
||||
"total_connections": 0
|
||||
}
|
||||
|
||||
|
||||
class LoggingNoopListenerManager(LoggingNoopCommonManager,
|
||||
driver_base.BaseListenerManager):
|
||||
|
||||
def create(self, context, obj):
|
||||
LOG.debug("LB listener no-op, create %s", self.__class__.__name__,
|
||||
obj.id)
|
||||
|
||||
def update(self, context, old_obj, obj):
|
||||
LOG.debug("LB listener no-op, update %s", self.__class__.__name__,
|
||||
obj.id)
|
||||
|
||||
|
||||
class LoggingNoopPoolManager(LoggingNoopCommonManager,
|
||||
driver_base.BasePoolManager):
|
||||
pass
|
||||
|
||||
|
||||
class LoggingNoopMemberManager(LoggingNoopCommonManager,
|
||||
driver_base.BaseMemberManager):
|
||||
pass
|
||||
|
||||
|
||||
class LoggingNoopHealthMonitorManager(LoggingNoopCommonManager,
|
||||
driver_base.BaseHealthMonitorManager):
|
||||
|
||||
def create(self, context, obj):
|
||||
LOG.debug("LB health monitor no-op, create %s",
|
||||
self.__class__.__name__, obj.id)
|
||||
self.active(context, obj.id, obj.id)
|
||||
|
||||
def update(self, context, old_obj, obj):
|
||||
LOG.debug("LB health monitor no-op, update %s",
|
||||
self.__class__.__name__, obj.id)
|
||||
self.active(context, obj.id, obj.id)
|
|
@ -1,177 +0,0 @@
|
|||
# Copyright 2014 Citrix Systems
|
||||
#
|
||||
# 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 base64
|
||||
|
||||
from oslo.serialization import jsonutils
|
||||
import requests
|
||||
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.i18n import _LE
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONTENT_TYPE_HEADER = 'Content-type'
|
||||
ACCEPT_HEADER = 'Accept'
|
||||
AUTH_HEADER = 'Authorization'
|
||||
DRIVER_HEADER = 'X-OpenStack-LBaaS'
|
||||
TENANT_HEADER = 'X-Tenant-ID'
|
||||
JSON_CONTENT_TYPE = 'application/json'
|
||||
DRIVER_HEADER_VALUE = 'netscaler-openstack-lbaas'
|
||||
|
||||
|
||||
class NCCException(n_exc.NeutronException):
|
||||
|
||||
"""Represents exceptions thrown by NSClient."""
|
||||
|
||||
CONNECTION_ERROR = 1
|
||||
REQUEST_ERROR = 2
|
||||
RESPONSE_ERROR = 3
|
||||
UNKNOWN_ERROR = 4
|
||||
|
||||
def __init__(self, error):
|
||||
self.message = _("NCC Error %d") % error
|
||||
super(NCCException, self).__init__()
|
||||
self.error = error
|
||||
|
||||
|
||||
class NSClient(object):
|
||||
|
||||
"""Client to operate on REST resources of NetScaler Control Center."""
|
||||
|
||||
def __init__(self, service_uri, username, password):
|
||||
if not service_uri:
|
||||
LOG.exception(_LE("No NetScaler Control Center URI specified. "
|
||||
"Cannot connect."))
|
||||
raise NCCException(NCCException.CONNECTION_ERROR)
|
||||
self.service_uri = service_uri.strip('/')
|
||||
self.auth = None
|
||||
if username and password:
|
||||
base64string = base64.encodestring("%s:%s" % (username, password))
|
||||
base64string = base64string[:-1]
|
||||
self.auth = 'Basic %s' % base64string
|
||||
|
||||
def create_resource(self, tenant_id, resource_path, object_name,
|
||||
object_data):
|
||||
"""Create a resource of NetScaler Control Center."""
|
||||
return self._resource_operation('POST', tenant_id,
|
||||
resource_path,
|
||||
object_name=object_name,
|
||||
object_data=object_data)
|
||||
|
||||
def retrieve_resource(self, tenant_id, resource_path, parse_response=True):
|
||||
"""Retrieve a resource of NetScaler Control Center."""
|
||||
return self._resource_operation('GET', tenant_id, resource_path)
|
||||
|
||||
def update_resource(self, tenant_id, resource_path, object_name,
|
||||
object_data):
|
||||
"""Update a resource of the NetScaler Control Center."""
|
||||
return self._resource_operation('PUT', tenant_id,
|
||||
resource_path,
|
||||
object_name=object_name,
|
||||
object_data=object_data)
|
||||
|
||||
def remove_resource(self, tenant_id, resource_path, parse_response=True):
|
||||
"""Remove a resource of NetScaler Control Center."""
|
||||
return self._resource_operation('DELETE', tenant_id, resource_path)
|
||||
|
||||
def _resource_operation(self, method, tenant_id, resource_path,
|
||||
object_name=None, object_data=None):
|
||||
resource_uri = "%s/%s" % (self.service_uri, resource_path)
|
||||
headers = self._setup_req_headers(tenant_id)
|
||||
request_body = None
|
||||
if object_data:
|
||||
if isinstance(object_data, str):
|
||||
request_body = object_data
|
||||
else:
|
||||
obj_dict = {object_name: object_data}
|
||||
request_body = jsonutils.dumps(obj_dict)
|
||||
|
||||
response_status, resp_dict = self._execute_request(method,
|
||||
resource_uri,
|
||||
headers,
|
||||
body=request_body)
|
||||
return response_status, resp_dict
|
||||
|
||||
def _is_valid_response(self, response_status):
|
||||
# when status is less than 400, the response is fine
|
||||
return response_status < requests.codes.bad_request
|
||||
|
||||
def _setup_req_headers(self, tenant_id):
|
||||
headers = {ACCEPT_HEADER: JSON_CONTENT_TYPE,
|
||||
CONTENT_TYPE_HEADER: JSON_CONTENT_TYPE,
|
||||
DRIVER_HEADER: DRIVER_HEADER_VALUE,
|
||||
TENANT_HEADER: tenant_id,
|
||||
AUTH_HEADER: self.auth}
|
||||
return headers
|
||||
|
||||
def _get_response_dict(self, response):
|
||||
response_dict = {'status': response.status_code,
|
||||
'body': response.text,
|
||||
'headers': response.headers}
|
||||
if self._is_valid_response(response.status_code):
|
||||
if response.text:
|
||||
response_dict['dict'] = response.json()
|
||||
return response_dict
|
||||
|
||||
def _execute_request(self, method, resource_uri, headers, body=None):
|
||||
try:
|
||||
response = requests.request(method, url=resource_uri,
|
||||
headers=headers, data=body)
|
||||
except requests.exceptions.SSLError:
|
||||
LOG.exception(_LE("SSL error occurred while connecting to %s"),
|
||||
self.service_uri)
|
||||
raise NCCException(NCCException.CONNECTION_ERROR)
|
||||
except requests.exceptions.ConnectionError:
|
||||
LOG.exception(_LE("Connection error occurred while connecting "
|
||||
"to %s"),
|
||||
self.service_uri)
|
||||
raise NCCException(NCCException.CONNECTION_ERROR)
|
||||
except requests.exceptions.Timeout:
|
||||
LOG.exception(_LE("Request to %s timed out"), self.service_uri)
|
||||
raise NCCException(NCCException.CONNECTION_ERROR)
|
||||
except (requests.exceptions.URLRequired,
|
||||
requests.exceptions.InvalidURL,
|
||||
requests.exceptions.MissingSchema,
|
||||
requests.exceptions.InvalidSchema):
|
||||
LOG.exception(_LE("Request did not specify a valid URL"))
|
||||
raise NCCException(NCCException.REQUEST_ERROR)
|
||||
except requests.exceptions.TooManyRedirects:
|
||||
LOG.exception(_LE("Too many redirects occurred for request to %s"))
|
||||
raise NCCException(NCCException.REQUEST_ERROR)
|
||||
except requests.exceptions.RequestException:
|
||||
LOG.exception(_LE("A request error while connecting to %s"),
|
||||
self.service_uri)
|
||||
raise NCCException(NCCException.REQUEST_ERROR)
|
||||
except Exception:
|
||||
LOG.exception(_LE("A unknown error occurred during request to %s"),
|
||||
self.service_uri)
|
||||
raise NCCException(NCCException.UNKNOWN_ERROR)
|
||||
resp_dict = self._get_response_dict(response)
|
||||
LOG.debug("Response: %s", resp_dict['body'])
|
||||
response_status = resp_dict['status']
|
||||
if response_status == requests.codes.unauthorized:
|
||||
LOG.exception(_LE("Unable to login. Invalid credentials passed."
|
||||
"for: %s"),
|
||||
self.service_uri)
|
||||
raise NCCException(NCCException.RESPONSE_ERROR)
|
||||
if not self._is_valid_response(response_status):
|
||||
LOG.exception(_LE("Failed %(method)s operation on %(url)s "
|
||||
"status code: %(response_status)s"),
|
||||
{"method": method,
|
||||
"url": resource_uri,
|
||||
"response_status": response_status})
|
||||
raise NCCException(NCCException.RESPONSE_ERROR)
|
||||
return response_status, resp_dict
|
|
@ -1,469 +0,0 @@
|
|||
# Copyright 2014 Citrix Systems, 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 oslo.config import cfg
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
from neutron.i18n import _LI
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.drivers import abstract_driver
|
||||
from neutron.services.loadbalancer.drivers.netscaler import ncc_client
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
NETSCALER_CC_OPTS = [
|
||||
cfg.StrOpt(
|
||||
'netscaler_ncc_uri',
|
||||
help=_('The URL to reach the NetScaler Control Center Server.'),
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'netscaler_ncc_username',
|
||||
help=_('Username to login to the NetScaler Control Center Server.'),
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'netscaler_ncc_password',
|
||||
help=_('Password to login to the NetScaler Control Center Server.'),
|
||||
)
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(NETSCALER_CC_OPTS, 'netscaler_driver')
|
||||
|
||||
VIPS_RESOURCE = 'vips'
|
||||
VIP_RESOURCE = 'vip'
|
||||
POOLS_RESOURCE = 'pools'
|
||||
POOL_RESOURCE = 'pool'
|
||||
POOLMEMBERS_RESOURCE = 'members'
|
||||
POOLMEMBER_RESOURCE = 'member'
|
||||
MONITORS_RESOURCE = 'healthmonitors'
|
||||
MONITOR_RESOURCE = 'healthmonitor'
|
||||
POOLSTATS_RESOURCE = 'statistics'
|
||||
PROV_SEGMT_ID = 'provider:segmentation_id'
|
||||
PROV_NET_TYPE = 'provider:network_type'
|
||||
DRIVER_NAME = 'netscaler_driver'
|
||||
|
||||
|
||||
class NetScalerPluginDriver(abstract_driver.LoadBalancerAbstractDriver):
|
||||
|
||||
"""NetScaler LBaaS Plugin driver class."""
|
||||
|
||||
def __init__(self, plugin):
|
||||
self.plugin = plugin
|
||||
ncc_uri = cfg.CONF.netscaler_driver.netscaler_ncc_uri
|
||||
ncc_username = cfg.CONF.netscaler_driver.netscaler_ncc_username
|
||||
ncc_password = cfg.CONF.netscaler_driver.netscaler_ncc_password
|
||||
self.client = ncc_client.NSClient(ncc_uri,
|
||||
ncc_username,
|
||||
ncc_password)
|
||||
|
||||
def create_vip(self, context, vip):
|
||||
"""Create a vip on a NetScaler device."""
|
||||
network_info = self._get_vip_network_info(context, vip)
|
||||
ncc_vip = self._prepare_vip_for_creation(vip)
|
||||
ncc_vip = dict(ncc_vip.items() + network_info.items())
|
||||
LOG.debug("NetScaler driver vip creation: %r", ncc_vip)
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.create_resource(context.tenant_id, VIPS_RESOURCE,
|
||||
VIP_RESOURCE, ncc_vip)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Vip, vip["id"],
|
||||
status)
|
||||
|
||||
def update_vip(self, context, old_vip, vip):
|
||||
"""Update a vip on a NetScaler device."""
|
||||
update_vip = self._prepare_vip_for_update(vip)
|
||||
resource_path = "%s/%s" % (VIPS_RESOURCE, vip["id"])
|
||||
LOG.debug("NetScaler driver vip %(vip_id)s update: %(vip_obj)r",
|
||||
{"vip_id": vip["id"], "vip_obj": vip})
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.update_resource(context.tenant_id, resource_path,
|
||||
VIP_RESOURCE, update_vip)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Vip, old_vip["id"],
|
||||
status)
|
||||
|
||||
def delete_vip(self, context, vip):
|
||||
"""Delete a vip on a NetScaler device."""
|
||||
resource_path = "%s/%s" % (VIPS_RESOURCE, vip["id"])
|
||||
LOG.debug("NetScaler driver vip removal: %s", vip["id"])
|
||||
try:
|
||||
self.client.remove_resource(context.tenant_id, resource_path)
|
||||
except ncc_client.NCCException:
|
||||
self.plugin.update_status(context, loadbalancer_db.Vip,
|
||||
vip["id"],
|
||||
constants.ERROR)
|
||||
else:
|
||||
self.plugin._delete_db_vip(context, vip['id'])
|
||||
|
||||
def create_pool(self, context, pool):
|
||||
"""Create a pool on a NetScaler device."""
|
||||
network_info = self._get_pool_network_info(context, pool)
|
||||
#allocate a snat port/ipaddress on the subnet if one doesn't exist
|
||||
self._create_snatport_for_subnet_if_not_exists(context,
|
||||
pool['tenant_id'],
|
||||
pool['subnet_id'],
|
||||
network_info)
|
||||
ncc_pool = self._prepare_pool_for_creation(pool)
|
||||
ncc_pool = dict(ncc_pool.items() + network_info.items())
|
||||
LOG.debug("NetScaler driver pool creation: %r", ncc_pool)
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.create_resource(context.tenant_id, POOLS_RESOURCE,
|
||||
POOL_RESOURCE, ncc_pool)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Pool,
|
||||
ncc_pool["id"], status)
|
||||
|
||||
def update_pool(self, context, old_pool, pool):
|
||||
"""Update a pool on a NetScaler device."""
|
||||
ncc_pool = self._prepare_pool_for_update(pool)
|
||||
resource_path = "%s/%s" % (POOLS_RESOURCE, old_pool["id"])
|
||||
LOG.debug("NetScaler driver pool %(pool_id)s update: %(pool_obj)r",
|
||||
{"pool_id": old_pool["id"], "pool_obj": ncc_pool})
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.update_resource(context.tenant_id, resource_path,
|
||||
POOL_RESOURCE, ncc_pool)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Pool,
|
||||
old_pool["id"], status)
|
||||
|
||||
def delete_pool(self, context, pool):
|
||||
"""Delete a pool on a NetScaler device."""
|
||||
resource_path = "%s/%s" % (POOLS_RESOURCE, pool['id'])
|
||||
LOG.debug("NetScaler driver pool removal: %s", pool["id"])
|
||||
try:
|
||||
self.client.remove_resource(context.tenant_id, resource_path)
|
||||
except ncc_client.NCCException:
|
||||
self.plugin.update_status(context, loadbalancer_db.Pool,
|
||||
pool["id"],
|
||||
constants.ERROR)
|
||||
else:
|
||||
self.plugin._delete_db_pool(context, pool['id'])
|
||||
self._remove_snatport_for_subnet_if_not_used(context,
|
||||
pool['tenant_id'],
|
||||
pool['subnet_id'])
|
||||
|
||||
def create_member(self, context, member):
|
||||
"""Create a pool member on a NetScaler device."""
|
||||
ncc_member = self._prepare_member_for_creation(member)
|
||||
LOG.info(_LI("NetScaler driver poolmember creation: %r"),
|
||||
ncc_member)
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.create_resource(context.tenant_id,
|
||||
POOLMEMBERS_RESOURCE,
|
||||
POOLMEMBER_RESOURCE,
|
||||
ncc_member)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Member,
|
||||
member["id"], status)
|
||||
|
||||
def update_member(self, context, old_member, member):
|
||||
"""Update a pool member on a NetScaler device."""
|
||||
ncc_member = self._prepare_member_for_update(member)
|
||||
resource_path = "%s/%s" % (POOLMEMBERS_RESOURCE, old_member["id"])
|
||||
LOG.debug("NetScaler driver poolmember %(member_id)s update: "
|
||||
"%(member_obj)r",
|
||||
{"member_id": old_member["id"],
|
||||
"member_obj": ncc_member})
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.update_resource(context.tenant_id, resource_path,
|
||||
POOLMEMBER_RESOURCE, ncc_member)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_status(context, loadbalancer_db.Member,
|
||||
old_member["id"], status)
|
||||
|
||||
def delete_member(self, context, member):
|
||||
"""Delete a pool member on a NetScaler device."""
|
||||
resource_path = "%s/%s" % (POOLMEMBERS_RESOURCE, member['id'])
|
||||
LOG.debug("NetScaler driver poolmember removal: %s", member["id"])
|
||||
try:
|
||||
self.client.remove_resource(context.tenant_id, resource_path)
|
||||
except ncc_client.NCCException:
|
||||
self.plugin.update_status(context, loadbalancer_db.Member,
|
||||
member["id"],
|
||||
constants.ERROR)
|
||||
else:
|
||||
self.plugin._delete_db_member(context, member['id'])
|
||||
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
"""Create a pool health monitor on a NetScaler device."""
|
||||
ncc_hm = self._prepare_healthmonitor_for_creation(health_monitor,
|
||||
pool_id)
|
||||
resource_path = "%s/%s/%s" % (POOLS_RESOURCE, pool_id,
|
||||
MONITORS_RESOURCE)
|
||||
LOG.debug("NetScaler driver healthmonitor creation for pool "
|
||||
"%(pool_id)s: %(monitor_obj)r",
|
||||
{"pool_id": pool_id, "monitor_obj": ncc_hm})
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.create_resource(context.tenant_id, resource_path,
|
||||
MONITOR_RESOURCE,
|
||||
ncc_hm)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_pool_health_monitor(context,
|
||||
health_monitor['id'],
|
||||
pool_id,
|
||||
status, "")
|
||||
|
||||
def update_pool_health_monitor(self, context, old_health_monitor,
|
||||
health_monitor, pool_id):
|
||||
"""Update a pool health monitor on a NetScaler device."""
|
||||
ncc_hm = self._prepare_healthmonitor_for_update(health_monitor)
|
||||
resource_path = "%s/%s" % (MONITORS_RESOURCE,
|
||||
old_health_monitor["id"])
|
||||
LOG.debug("NetScaler driver healthmonitor %(monitor_id)s update: "
|
||||
"%(monitor_obj)r",
|
||||
{"monitor_id": old_health_monitor["id"],
|
||||
"monitor_obj": ncc_hm})
|
||||
status = constants.ACTIVE
|
||||
try:
|
||||
self.client.update_resource(context.tenant_id, resource_path,
|
||||
MONITOR_RESOURCE, ncc_hm)
|
||||
except ncc_client.NCCException:
|
||||
status = constants.ERROR
|
||||
self.plugin.update_pool_health_monitor(context,
|
||||
old_health_monitor['id'],
|
||||
pool_id,
|
||||
status, "")
|
||||
|
||||
def delete_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
"""Delete a pool health monitor on a NetScaler device."""
|
||||
resource_path = "%s/%s/%s/%s" % (POOLS_RESOURCE, pool_id,
|
||||
MONITORS_RESOURCE,
|
||||
health_monitor["id"])
|
||||
LOG.debug("NetScaler driver healthmonitor %(monitor_id)s"
|
||||
"removal for pool %(pool_id)s",
|
||||
{"monitor_id": health_monitor["id"],
|
||||
"pool_id": pool_id})
|
||||
try:
|
||||
self.client.remove_resource(context.tenant_id, resource_path)
|
||||
except ncc_client.NCCException:
|
||||
self.plugin.update_pool_health_monitor(context,
|
||||
health_monitor['id'],
|
||||
pool_id,
|
||||
constants.ERROR, "")
|
||||
else:
|
||||
self.plugin._delete_db_pool_health_monitor(context,
|
||||
health_monitor['id'],
|
||||
pool_id)
|
||||
|
||||
def stats(self, context, pool_id):
|
||||
"""Retrieve pool statistics from the NetScaler device."""
|
||||
resource_path = "%s/%s" % (POOLSTATS_RESOURCE, pool_id)
|
||||
LOG.debug("NetScaler driver pool stats retrieval: %s", pool_id)
|
||||
try:
|
||||
stats = self.client.retrieve_resource(context.tenant_id,
|
||||
resource_path)[1]
|
||||
except ncc_client.NCCException:
|
||||
self.plugin.update_status(context, loadbalancer_db.Pool,
|
||||
pool_id, constants.ERROR)
|
||||
else:
|
||||
return stats
|
||||
|
||||
def _prepare_vip_for_creation(self, vip):
|
||||
creation_attrs = {
|
||||
'id': vip['id'],
|
||||
'tenant_id': vip['tenant_id'],
|
||||
'protocol': vip['protocol'],
|
||||
'address': vip['address'],
|
||||
'protocol_port': vip['protocol_port'],
|
||||
}
|
||||
if 'session_persistence' in vip:
|
||||
creation_attrs['session_persistence'] = vip['session_persistence']
|
||||
update_attrs = self._prepare_vip_for_update(vip)
|
||||
creation_attrs.update(update_attrs)
|
||||
return creation_attrs
|
||||
|
||||
def _prepare_vip_for_update(self, vip):
|
||||
return {
|
||||
'name': vip['name'],
|
||||
'description': vip['description'],
|
||||
'pool_id': vip['pool_id'],
|
||||
'connection_limit': vip['connection_limit'],
|
||||
'admin_state_up': vip['admin_state_up']
|
||||
}
|
||||
|
||||
def _prepare_pool_for_creation(self, pool):
|
||||
creation_attrs = {
|
||||
'id': pool['id'],
|
||||
'tenant_id': pool['tenant_id'],
|
||||
'vip_id': pool['vip_id'],
|
||||
'protocol': pool['protocol'],
|
||||
'subnet_id': pool['subnet_id'],
|
||||
}
|
||||
update_attrs = self._prepare_pool_for_update(pool)
|
||||
creation_attrs.update(update_attrs)
|
||||
return creation_attrs
|
||||
|
||||
def _prepare_pool_for_update(self, pool):
|
||||
return {
|
||||
'name': pool['name'],
|
||||
'description': pool['description'],
|
||||
'lb_method': pool['lb_method'],
|
||||
'admin_state_up': pool['admin_state_up']
|
||||
}
|
||||
|
||||
def _prepare_member_for_creation(self, member):
|
||||
creation_attrs = {
|
||||
'id': member['id'],
|
||||
'tenant_id': member['tenant_id'],
|
||||
'address': member['address'],
|
||||
'protocol_port': member['protocol_port'],
|
||||
}
|
||||
update_attrs = self._prepare_member_for_update(member)
|
||||
creation_attrs.update(update_attrs)
|
||||
return creation_attrs
|
||||
|
||||
def _prepare_member_for_update(self, member):
|
||||
return {
|
||||
'pool_id': member['pool_id'],
|
||||
'weight': member['weight'],
|
||||
'admin_state_up': member['admin_state_up']
|
||||
}
|
||||
|
||||
def _prepare_healthmonitor_for_creation(self, health_monitor, pool_id):
|
||||
creation_attrs = {
|
||||
'id': health_monitor['id'],
|
||||
'tenant_id': health_monitor['tenant_id'],
|
||||
'type': health_monitor['type'],
|
||||
}
|
||||
update_attrs = self._prepare_healthmonitor_for_update(health_monitor)
|
||||
creation_attrs.update(update_attrs)
|
||||
return creation_attrs
|
||||
|
||||
def _prepare_healthmonitor_for_update(self, health_monitor):
|
||||
ncc_hm = {
|
||||
'delay': health_monitor['delay'],
|
||||
'timeout': health_monitor['timeout'],
|
||||
'max_retries': health_monitor['max_retries'],
|
||||
'admin_state_up': health_monitor['admin_state_up']
|
||||
}
|
||||
if health_monitor['type'] in ['HTTP', 'HTTPS']:
|
||||
ncc_hm['http_method'] = health_monitor['http_method']
|
||||
ncc_hm['url_path'] = health_monitor['url_path']
|
||||
ncc_hm['expected_codes'] = health_monitor['expected_codes']
|
||||
return ncc_hm
|
||||
|
||||
def _get_network_info(self, context, entity):
|
||||
network_info = {}
|
||||
subnet_id = entity['subnet_id']
|
||||
subnet = self.plugin._core_plugin.get_subnet(context, subnet_id)
|
||||
network_id = subnet['network_id']
|
||||
network = self.plugin._core_plugin.get_network(context, network_id)
|
||||
network_info['network_id'] = network_id
|
||||
network_info['subnet_id'] = subnet_id
|
||||
if PROV_NET_TYPE in network:
|
||||
network_info['network_type'] = network[PROV_NET_TYPE]
|
||||
if PROV_SEGMT_ID in network:
|
||||
network_info['segmentation_id'] = network[PROV_SEGMT_ID]
|
||||
return network_info
|
||||
|
||||
def _get_vip_network_info(self, context, vip):
|
||||
network_info = self._get_network_info(context, vip)
|
||||
network_info['port_id'] = vip['port_id']
|
||||
return network_info
|
||||
|
||||
def _get_pool_network_info(self, context, pool):
|
||||
return self._get_network_info(context, pool)
|
||||
|
||||
def _get_pools_on_subnet(self, context, tenant_id, subnet_id):
|
||||
filter_dict = {'subnet_id': [subnet_id], 'tenant_id': [tenant_id]}
|
||||
return self.plugin.get_pools(context, filters=filter_dict)
|
||||
|
||||
def _get_snatport_for_subnet(self, context, tenant_id, subnet_id):
|
||||
device_id = '_lb-snatport-' + subnet_id
|
||||
subnet = self.plugin._core_plugin.get_subnet(context, subnet_id)
|
||||
network_id = subnet['network_id']
|
||||
LOG.debug("Filtering ports based on network_id=%(network_id)s, "
|
||||
"tenant_id=%(tenant_id)s, device_id=%(device_id)s",
|
||||
{'network_id': network_id,
|
||||
'tenant_id': tenant_id,
|
||||
'device_id': device_id})
|
||||
filter_dict = {
|
||||
'network_id': [network_id],
|
||||
'tenant_id': [tenant_id],
|
||||
'device_id': [device_id],
|
||||
'device-owner': [DRIVER_NAME]
|
||||
}
|
||||
ports = self.plugin._core_plugin.get_ports(context,
|
||||
filters=filter_dict)
|
||||
if ports:
|
||||
LOG.info(_LI("Found an existing SNAT port for subnet %s"),
|
||||
subnet_id)
|
||||
return ports[0]
|
||||
LOG.info(_LI("Found no SNAT ports for subnet %s"), subnet_id)
|
||||
|
||||
def _create_snatport_for_subnet(self, context, tenant_id, subnet_id,
|
||||
ip_address):
|
||||
subnet = self.plugin._core_plugin.get_subnet(context, subnet_id)
|
||||
fixed_ip = {'subnet_id': subnet['id']}
|
||||
if ip_address and ip_address != attributes.ATTR_NOT_SPECIFIED:
|
||||
fixed_ip['ip_address'] = ip_address
|
||||
port_data = {
|
||||
'tenant_id': tenant_id,
|
||||
'name': '_lb-snatport-' + subnet_id,
|
||||
'network_id': subnet['network_id'],
|
||||
'mac_address': attributes.ATTR_NOT_SPECIFIED,
|
||||
'admin_state_up': False,
|
||||
'device_id': '_lb-snatport-' + subnet_id,
|
||||
'device_owner': DRIVER_NAME,
|
||||
'fixed_ips': [fixed_ip],
|
||||
}
|
||||
port = self.plugin._core_plugin.create_port(context,
|
||||
{'port': port_data})
|
||||
LOG.info(_LI("Created SNAT port: %r"), port)
|
||||
return port
|
||||
|
||||
def _remove_snatport_for_subnet(self, context, tenant_id, subnet_id):
|
||||
port = self._get_snatport_for_subnet(context, tenant_id, subnet_id)
|
||||
if port:
|
||||
self.plugin._core_plugin.delete_port(context, port['id'])
|
||||
LOG.info(_LI("Removed SNAT port: %r"), port)
|
||||
|
||||
def _create_snatport_for_subnet_if_not_exists(self, context, tenant_id,
|
||||
subnet_id, network_info):
|
||||
port = self._get_snatport_for_subnet(context, tenant_id, subnet_id)
|
||||
if not port:
|
||||
LOG.info(_LI("No SNAT port found for subnet %s. Creating one..."),
|
||||
subnet_id)
|
||||
port = self._create_snatport_for_subnet(context, tenant_id,
|
||||
subnet_id,
|
||||
ip_address=None)
|
||||
network_info['port_id'] = port['id']
|
||||
network_info['snat_ip'] = port['fixed_ips'][0]['ip_address']
|
||||
LOG.info(_LI("SNAT port: %r"), port)
|
||||
|
||||
def _remove_snatport_for_subnet_if_not_used(self, context, tenant_id,
|
||||
subnet_id):
|
||||
pools = self._get_pools_on_subnet(context, tenant_id, subnet_id)
|
||||
if not pools:
|
||||
#No pools left on the old subnet.
|
||||
#We can remove the SNAT port/ipaddress
|
||||
self._remove_snatport_for_subnet(context, tenant_id, subnet_id)
|
||||
LOG.info(_LI("Removing SNAT port for subnet %s "
|
||||
"as this is the last pool using it..."),
|
||||
subnet_id)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,40 +0,0 @@
|
|||
# Copyright 2013 Radware LTD.
|
||||
#
|
||||
# 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 neutron.common import exceptions
|
||||
|
||||
|
||||
class RadwareLBaasException(exceptions.NeutronException):
|
||||
message = _('An unknown exception occurred in Radware LBaaS provider.')
|
||||
|
||||
|
||||
class AuthenticationMissing(RadwareLBaasException):
|
||||
message = _('vDirect user/password missing. '
|
||||
'Specify in configuration file, under [radware] section')
|
||||
|
||||
|
||||
class WorkflowMissing(RadwareLBaasException):
|
||||
message = _('Workflow %(workflow)s is missing on vDirect server. '
|
||||
'Upload missing workflow')
|
||||
|
||||
|
||||
class RESTRequestFailure(RadwareLBaasException):
|
||||
message = _('REST request failed with status %(status)s. '
|
||||
'Reason: %(reason)s, Description: %(description)s. '
|
||||
'Success status codes are %(success_codes)s')
|
||||
|
||||
|
||||
class UnsupportedEntityOperation(RadwareLBaasException):
|
||||
message = _('%(operation)s operation is not supported for %(entity)s.')
|
|
@ -1,325 +0,0 @@
|
|||
#
|
||||
# Copyright 2013 Radware LTD.
|
||||
#
|
||||
# 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 oslo.utils import excutils
|
||||
|
||||
from neutron.api.v2 import attributes as attrs
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron import context
|
||||
from neutron.db.loadbalancer import loadbalancer_db as ldb
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron.i18n import _LE
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer import agent_scheduler
|
||||
from neutron.services import provider_configuration as pconf
|
||||
from neutron.services import service_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadBalancerPlugin(ldb.LoadBalancerPluginDb,
|
||||
agent_scheduler.LbaasAgentSchedulerDbMixin):
|
||||
"""Implementation of the Neutron Loadbalancer Service Plugin.
|
||||
|
||||
This class manages the workflow of LBaaS request/response.
|
||||
Most DB related works are implemented in class
|
||||
loadbalancer_db.LoadBalancerPluginDb.
|
||||
"""
|
||||
supported_extension_aliases = ["lbaas",
|
||||
"lbaas_agent_scheduler",
|
||||
"service-type"]
|
||||
|
||||
# lbaas agent notifiers to handle agent update operations;
|
||||
# can be updated by plugin drivers while loading;
|
||||
# will be extracted by neutron manager when loading service plugins;
|
||||
agent_notifiers = {}
|
||||
|
||||
def __init__(self):
|
||||
"""Initialization for the loadbalancer service plugin."""
|
||||
|
||||
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
|
||||
self._load_drivers()
|
||||
|
||||
def _load_drivers(self):
|
||||
"""Loads plugin-drivers specified in configuration."""
|
||||
self.drivers, self.default_provider = service_base.load_drivers(
|
||||
constants.LOADBALANCER, self)
|
||||
|
||||
# we're at the point when extensions are not loaded yet
|
||||
# so prevent policy from being loaded
|
||||
ctx = context.get_admin_context(load_admin_roles=False)
|
||||
# stop service in case provider was removed, but resources were not
|
||||
self._check_orphan_pool_associations(ctx, self.drivers.keys())
|
||||
|
||||
def _check_orphan_pool_associations(self, context, provider_names):
|
||||
"""Checks remaining associations between pools and providers.
|
||||
|
||||
If admin has not undeployed resources with provider that was deleted
|
||||
from configuration, neutron service is stopped. Admin must delete
|
||||
resources prior to removing providers from configuration.
|
||||
"""
|
||||
pools = self.get_pools(context)
|
||||
lost_providers = set([pool['provider'] for pool in pools
|
||||
if pool['provider'] not in provider_names])
|
||||
# resources are left without provider - stop the service
|
||||
if lost_providers:
|
||||
LOG.exception(_LE("Delete associated loadbalancer pools before "
|
||||
"removing providers %s"),
|
||||
list(lost_providers))
|
||||
raise SystemExit(1)
|
||||
|
||||
def _get_driver_for_provider(self, provider):
|
||||
if provider in self.drivers:
|
||||
return self.drivers[provider]
|
||||
# raise if not associated (should never be reached)
|
||||
raise n_exc.Invalid(_("Error retrieving driver for provider %s") %
|
||||
provider)
|
||||
|
||||
def _get_driver_for_pool(self, context, pool_id):
|
||||
pool = self.get_pool(context, pool_id)
|
||||
try:
|
||||
return self.drivers[pool['provider']]
|
||||
except KeyError:
|
||||
raise n_exc.Invalid(_("Error retrieving provider for pool %s") %
|
||||
pool_id)
|
||||
|
||||
def get_plugin_type(self):
|
||||
return constants.LOADBALANCER
|
||||
|
||||
def get_plugin_description(self):
|
||||
return "Neutron LoadBalancer Service Plugin"
|
||||
|
||||
def create_vip(self, context, vip):
|
||||
v = super(LoadBalancerPlugin, self).create_vip(context, vip)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.create_vip(context, v)
|
||||
return v
|
||||
|
||||
def update_vip(self, context, id, vip):
|
||||
if 'status' not in vip['vip']:
|
||||
vip['vip']['status'] = constants.PENDING_UPDATE
|
||||
old_vip = self.get_vip(context, id)
|
||||
v = super(LoadBalancerPlugin, self).update_vip(context, id, vip)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.update_vip(context, old_vip, v)
|
||||
return v
|
||||
|
||||
def _delete_db_vip(self, context, id):
|
||||
# proxy the call until plugin inherits from DBPlugin
|
||||
super(LoadBalancerPlugin, self).delete_vip(context, id)
|
||||
|
||||
def delete_vip(self, context, id):
|
||||
self.update_status(context, ldb.Vip,
|
||||
id, constants.PENDING_DELETE)
|
||||
v = self.get_vip(context, id)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.delete_vip(context, v)
|
||||
|
||||
def _get_provider_name(self, context, pool):
|
||||
if ('provider' in pool and
|
||||
pool['provider'] != attrs.ATTR_NOT_SPECIFIED):
|
||||
provider_name = pconf.normalize_provider_name(pool['provider'])
|
||||
self.validate_provider(provider_name)
|
||||
return provider_name
|
||||
else:
|
||||
if not self.default_provider:
|
||||
raise pconf.DefaultServiceProviderNotFound(
|
||||
service_type=constants.LOADBALANCER)
|
||||
return self.default_provider
|
||||
|
||||
def create_pool(self, context, pool):
|
||||
provider_name = self._get_provider_name(context, pool['pool'])
|
||||
p = super(LoadBalancerPlugin, self).create_pool(context, pool)
|
||||
|
||||
self.service_type_manager.add_resource_association(
|
||||
context,
|
||||
constants.LOADBALANCER,
|
||||
provider_name, p['id'])
|
||||
#need to add provider name to pool dict,
|
||||
#because provider was not known to db plugin at pool creation
|
||||
p['provider'] = provider_name
|
||||
driver = self.drivers[provider_name]
|
||||
try:
|
||||
driver.create_pool(context, p)
|
||||
except loadbalancer.NoEligibleBackend:
|
||||
# that should catch cases when backend of any kind
|
||||
# is not available (agent, appliance, etc)
|
||||
self.update_status(context, ldb.Pool,
|
||||
p['id'], constants.ERROR,
|
||||
"No eligible backend")
|
||||
raise loadbalancer.NoEligibleBackend(pool_id=p['id'])
|
||||
return p
|
||||
|
||||
def update_pool(self, context, id, pool):
|
||||
if 'status' not in pool['pool']:
|
||||
pool['pool']['status'] = constants.PENDING_UPDATE
|
||||
old_pool = self.get_pool(context, id)
|
||||
p = super(LoadBalancerPlugin, self).update_pool(context, id, pool)
|
||||
driver = self._get_driver_for_provider(p['provider'])
|
||||
driver.update_pool(context, old_pool, p)
|
||||
return p
|
||||
|
||||
def _delete_db_pool(self, context, id):
|
||||
# proxy the call until plugin inherits from DBPlugin
|
||||
# rely on uuid uniqueness:
|
||||
try:
|
||||
with context.session.begin(subtransactions=True):
|
||||
self.service_type_manager.del_resource_associations(
|
||||
context, [id])
|
||||
super(LoadBalancerPlugin, self).delete_pool(context, id)
|
||||
except Exception:
|
||||
# that should not happen
|
||||
# if it's still a case - something goes wrong
|
||||
# log the error and mark the pool as ERROR
|
||||
LOG.error(_LE('Failed to delete pool %s, putting it in ERROR '
|
||||
'state'),
|
||||
id)
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.update_status(context, ldb.Pool,
|
||||
id, constants.ERROR)
|
||||
|
||||
def delete_pool(self, context, id):
|
||||
# check for delete conditions and update the status
|
||||
# within a transaction to avoid a race
|
||||
with context.session.begin(subtransactions=True):
|
||||
self.update_status(context, ldb.Pool,
|
||||
id, constants.PENDING_DELETE)
|
||||
self._ensure_pool_delete_conditions(context, id)
|
||||
p = self.get_pool(context, id)
|
||||
driver = self._get_driver_for_provider(p['provider'])
|
||||
driver.delete_pool(context, p)
|
||||
|
||||
def create_member(self, context, member):
|
||||
m = super(LoadBalancerPlugin, self).create_member(context, member)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.create_member(context, m)
|
||||
return m
|
||||
|
||||
def update_member(self, context, id, member):
|
||||
if 'status' not in member['member']:
|
||||
member['member']['status'] = constants.PENDING_UPDATE
|
||||
old_member = self.get_member(context, id)
|
||||
m = super(LoadBalancerPlugin, self).update_member(context, id, member)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.update_member(context, old_member, m)
|
||||
return m
|
||||
|
||||
def _delete_db_member(self, context, id):
|
||||
# proxy the call until plugin inherits from DBPlugin
|
||||
super(LoadBalancerPlugin, self).delete_member(context, id)
|
||||
|
||||
def delete_member(self, context, id):
|
||||
self.update_status(context, ldb.Member,
|
||||
id, constants.PENDING_DELETE)
|
||||
m = self.get_member(context, id)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.delete_member(context, m)
|
||||
|
||||
def _validate_hm_parameters(self, delay, timeout):
|
||||
if delay < timeout:
|
||||
raise loadbalancer.DelayOrTimeoutInvalid()
|
||||
|
||||
def create_health_monitor(self, context, health_monitor):
|
||||
new_hm = health_monitor['health_monitor']
|
||||
self._validate_hm_parameters(new_hm['delay'], new_hm['timeout'])
|
||||
|
||||
hm = super(LoadBalancerPlugin, self).create_health_monitor(
|
||||
context,
|
||||
health_monitor
|
||||
)
|
||||
return hm
|
||||
|
||||
def update_health_monitor(self, context, id, health_monitor):
|
||||
new_hm = health_monitor['health_monitor']
|
||||
old_hm = self.get_health_monitor(context, id)
|
||||
delay = new_hm.get('delay', old_hm.get('delay'))
|
||||
timeout = new_hm.get('timeout', old_hm.get('timeout'))
|
||||
self._validate_hm_parameters(delay, timeout)
|
||||
|
||||
hm = super(LoadBalancerPlugin, self).update_health_monitor(
|
||||
context,
|
||||
id,
|
||||
health_monitor
|
||||
)
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
qry = context.session.query(
|
||||
ldb.PoolMonitorAssociation
|
||||
).filter_by(monitor_id=hm['id']).join(ldb.Pool)
|
||||
for assoc in qry:
|
||||
driver = self._get_driver_for_pool(context, assoc['pool_id'])
|
||||
driver.update_pool_health_monitor(context, old_hm,
|
||||
hm, assoc['pool_id'])
|
||||
return hm
|
||||
|
||||
def _delete_db_pool_health_monitor(self, context, hm_id, pool_id):
|
||||
super(LoadBalancerPlugin, self).delete_pool_health_monitor(context,
|
||||
hm_id,
|
||||
pool_id)
|
||||
|
||||
def _delete_db_health_monitor(self, context, id):
|
||||
super(LoadBalancerPlugin, self).delete_health_monitor(context, id)
|
||||
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
retval = super(LoadBalancerPlugin, self).create_pool_health_monitor(
|
||||
context,
|
||||
health_monitor,
|
||||
pool_id
|
||||
)
|
||||
monitor_id = health_monitor['health_monitor']['id']
|
||||
hm = self.get_health_monitor(context, monitor_id)
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
driver.create_pool_health_monitor(context, hm, pool_id)
|
||||
return retval
|
||||
|
||||
def delete_pool_health_monitor(self, context, id, pool_id):
|
||||
self.update_pool_health_monitor(context, id, pool_id,
|
||||
constants.PENDING_DELETE)
|
||||
hm = self.get_health_monitor(context, id)
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
driver.delete_pool_health_monitor(context, hm, pool_id)
|
||||
|
||||
def stats(self, context, pool_id):
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
stats_data = driver.stats(context, pool_id)
|
||||
# if we get something from the driver -
|
||||
# update the db and return the value from db
|
||||
# else - return what we have in db
|
||||
if stats_data:
|
||||
super(LoadBalancerPlugin, self).update_pool_stats(
|
||||
context,
|
||||
pool_id,
|
||||
stats_data
|
||||
)
|
||||
return super(LoadBalancerPlugin, self).stats(context,
|
||||
pool_id)
|
||||
|
||||
def populate_vip_graph(self, context, vip):
|
||||
"""Populate the vip with: pool, members, healthmonitors."""
|
||||
|
||||
pool = self.get_pool(context, vip['pool_id'])
|
||||
vip['pool'] = pool
|
||||
vip['members'] = [self.get_member(context, member_id)
|
||||
for member_id in pool['members']]
|
||||
vip['health_monitors'] = [self.get_health_monitor(context, hm_id)
|
||||
for hm_id in pool['health_monitors']]
|
||||
return vip
|
||||
|
||||
def validate_provider(self, provider):
|
||||
if provider not in self.drivers:
|
||||
raise pconf.ServiceProviderNotFound(
|
||||
provider=provider, service_type=constants.LOADBALANCER)
|
|
@ -1,147 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
|
||||
from neutron.agent import l3_agent
|
||||
from neutron.extensions import vpnaas
|
||||
|
||||
vpn_agent_opts = [
|
||||
cfg.MultiStrOpt(
|
||||
'vpn_device_driver',
|
||||
default=['neutron.services.vpn.device_drivers.'
|
||||
'ipsec.OpenSwanDriver'],
|
||||
help=_("The vpn device drivers Neutron will use")),
|
||||
]
|
||||
cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent')
|
||||
|
||||
|
||||
class VPNAgent(l3_agent.L3NATAgentWithStateReport):
|
||||
"""VPNAgent class which can handle vpn service drivers."""
|
||||
def __init__(self, host, conf=None):
|
||||
super(VPNAgent, self).__init__(host=host, conf=conf)
|
||||
self.setup_device_drivers(host)
|
||||
|
||||
def setup_device_drivers(self, host):
|
||||
"""Setting up device drivers.
|
||||
|
||||
:param host: hostname. This is needed for rpc
|
||||
Each devices will stays as processes.
|
||||
They will communicate with
|
||||
server side service plugin using rpc with
|
||||
device specific rpc topic.
|
||||
:returns: None
|
||||
"""
|
||||
device_drivers = cfg.CONF.vpnagent.vpn_device_driver
|
||||
self.devices = []
|
||||
for device_driver in device_drivers:
|
||||
try:
|
||||
self.devices.append(
|
||||
importutils.import_object(device_driver, self, host))
|
||||
except ImportError:
|
||||
raise vpnaas.DeviceDriverImportError(
|
||||
device_driver=device_driver)
|
||||
|
||||
def get_namespace(self, router_id):
|
||||
"""Get namespace of router.
|
||||
|
||||
:router_id: router_id
|
||||
:returns: namespace string.
|
||||
Note if the router is not exist, this function
|
||||
returns None
|
||||
"""
|
||||
router_info = self.router_info.get(router_id)
|
||||
if not router_info:
|
||||
return
|
||||
return router_info.ns_name
|
||||
|
||||
def add_nat_rule(self, router_id, chain, rule, top=False):
|
||||
"""Add nat rule in namespace.
|
||||
|
||||
:param router_id: router_id
|
||||
:param chain: a string of chain name
|
||||
:param rule: a string of rule
|
||||
:param top: if top is true, the rule
|
||||
will be placed on the top of chain
|
||||
Note if there is no rotuer, this method do nothing
|
||||
"""
|
||||
router_info = self.router_info.get(router_id)
|
||||
if not router_info:
|
||||
return
|
||||
router_info.iptables_manager.ipv4['nat'].add_rule(
|
||||
chain, rule, top=top)
|
||||
|
||||
def remove_nat_rule(self, router_id, chain, rule, top=False):
|
||||
"""Remove nat rule in namespace.
|
||||
|
||||
:param router_id: router_id
|
||||
:param chain: a string of chain name
|
||||
:param rule: a string of rule
|
||||
:param top: unused
|
||||
needed to have same argument with add_nat_rule
|
||||
"""
|
||||
router_info = self.router_info.get(router_id)
|
||||
if not router_info:
|
||||
return
|
||||
router_info.iptables_manager.ipv4['nat'].remove_rule(
|
||||
chain, rule, top=top)
|
||||
|
||||
def iptables_apply(self, router_id):
|
||||
"""Apply IPtables.
|
||||
|
||||
:param router_id: router_id
|
||||
This method do nothing if there is no router
|
||||
"""
|
||||
router_info = self.router_info.get(router_id)
|
||||
if not router_info:
|
||||
return
|
||||
router_info.iptables_manager.apply()
|
||||
|
||||
def _router_added(self, router_id, router):
|
||||
"""Router added event.
|
||||
|
||||
This method overwrites parent class method.
|
||||
:param router_id: id of added router
|
||||
:param router: dict of rotuer
|
||||
"""
|
||||
super(VPNAgent, self)._router_added(router_id, router)
|
||||
for device in self.devices:
|
||||
device.create_router(router_id)
|
||||
|
||||
def _router_removed(self, router_id):
|
||||
"""Router removed event.
|
||||
|
||||
This method overwrites parent class method.
|
||||
:param router_id: id of removed router
|
||||
"""
|
||||
super(VPNAgent, self)._router_removed(router_id)
|
||||
for device in self.devices:
|
||||
device.destroy_router(router_id)
|
||||
|
||||
def _process_router_if_compatible(self, router):
|
||||
"""Router sync event.
|
||||
|
||||
This method overwrites parent class method.
|
||||
:param router: a router
|
||||
"""
|
||||
super(VPNAgent, self)._process_router_if_compatible(router)
|
||||
for device in self.devices:
|
||||
device.sync(self.context, [router])
|
||||
|
||||
|
||||
def main():
|
||||
l3_agent.main(
|
||||
manager='neutron.services.vpn.agent.VPNAgent')
|
|
@ -1,20 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
IPSEC_DRIVER_TOPIC = 'ipsec_driver'
|
||||
IPSEC_AGENT_TOPIC = 'ipsec_agent'
|
||||
CISCO_IPSEC_DRIVER_TOPIC = 'cisco_csr_ipsec_driver'
|
||||
CISCO_IPSEC_AGENT_TOPIC = 'cisco_csr_ipsec_agent'
|
|
@ -1,36 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class DeviceDriver(object):
|
||||
|
||||
def __init__(self, agent, host):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def sync(self, context, processes):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_router(self, process_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def destroy_router(self, process_id):
|
||||
pass
|
|
@ -1,293 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# 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 time
|
||||
|
||||
import netaddr
|
||||
from oslo.serialization import jsonutils
|
||||
import requests
|
||||
from requests import exceptions as r_exc
|
||||
|
||||
from neutron.i18n import _LE, _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
|
||||
TIMEOUT = 20.0
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
HEADER_CONTENT_TYPE_JSON = {'content-type': 'application/json'}
|
||||
URL_BASE = 'https://%(host)s/api/v1/%(resource)s'
|
||||
|
||||
# CSR RESTapi URIs
|
||||
URI_VPN_IPSEC_POLICIES = 'vpn-svc/ipsec/policies'
|
||||
URI_VPN_IPSEC_POLICIES_ID = URI_VPN_IPSEC_POLICIES + '/%s'
|
||||
URI_VPN_IKE_POLICIES = 'vpn-svc/ike/policies'
|
||||
URI_VPN_IKE_POLICIES_ID = URI_VPN_IKE_POLICIES + '/%s'
|
||||
URI_VPN_IKE_KEYRINGS = 'vpn-svc/ike/keyrings'
|
||||
URI_VPN_IKE_KEYRINGS_ID = URI_VPN_IKE_KEYRINGS + '/%s'
|
||||
URI_VPN_IKE_KEEPALIVE = 'vpn-svc/ike/keepalive'
|
||||
URI_VPN_SITE_TO_SITE = 'vpn-svc/site-to-site'
|
||||
URI_VPN_SITE_TO_SITE_ID = URI_VPN_SITE_TO_SITE + '/%s'
|
||||
URI_VPN_SITE_TO_SITE_STATE = URI_VPN_SITE_TO_SITE + '/%s/state'
|
||||
URI_VPN_SITE_ACTIVE_SESSIONS = URI_VPN_SITE_TO_SITE + '/active/sessions'
|
||||
URI_ROUTING_STATIC_ROUTES = 'routing-svc/static-routes'
|
||||
URI_ROUTING_STATIC_ROUTES_ID = URI_ROUTING_STATIC_ROUTES + '/%s'
|
||||
|
||||
|
||||
def make_route_id(cidr, interface):
|
||||
"""Build ID that will be used to identify route for later deletion."""
|
||||
net = netaddr.IPNetwork(cidr)
|
||||
return '%(network)s_%(prefix)s_%(interface)s' % {
|
||||
'network': net.network,
|
||||
'prefix': net.prefixlen,
|
||||
'interface': interface}
|
||||
|
||||
|
||||
class CsrRestClient(object):
|
||||
|
||||
"""REST CsrRestClient for accessing the Cisco Cloud Services Router."""
|
||||
|
||||
def __init__(self, settings):
|
||||
self.port = str(settings.get('protocol_port', 55443))
|
||||
self.host = ':'.join([settings.get('rest_mgmt_ip', ''), self.port])
|
||||
self.auth = (settings['username'], settings['password'])
|
||||
self.inner_if_name = settings.get('inner_if_name', '')
|
||||
self.outer_if_name = settings.get('outer_if_name', '')
|
||||
self.token = None
|
||||
self.vrf = settings.get('vrf', '')
|
||||
self.vrf_prefix = 'vrf/%s/' % self.vrf if self.vrf else ""
|
||||
self.status = requests.codes.OK
|
||||
self.timeout = settings.get('timeout')
|
||||
self.max_tries = 5
|
||||
self.session = requests.Session()
|
||||
|
||||
def _response_info_for(self, response, method):
|
||||
"""Return contents or location from response.
|
||||
|
||||
For a POST or GET with a 200 response, the response content
|
||||
is returned.
|
||||
|
||||
For a POST with a 201 response, return the header's location,
|
||||
which contains the identifier for the created resource.
|
||||
|
||||
If there is an error, return the response content, so that
|
||||
it can be used in error processing ('error-code', 'error-message',
|
||||
and 'detail' fields).
|
||||
"""
|
||||
if method in ('POST', 'GET') and self.status == requests.codes.OK:
|
||||
LOG.debug('RESPONSE: %s', response.json())
|
||||
return response.json()
|
||||
if method == 'POST' and self.status == requests.codes.CREATED:
|
||||
return response.headers.get('location', '')
|
||||
if self.status >= requests.codes.BAD_REQUEST and response.content:
|
||||
if 'error-code' in response.content:
|
||||
content = jsonutils.loads(response.content)
|
||||
LOG.debug("Error response content %s", content)
|
||||
return content
|
||||
|
||||
def _request(self, method, url, **kwargs):
|
||||
"""Perform REST request and save response info."""
|
||||
try:
|
||||
LOG.debug("%(method)s: Request for %(resource)s payload: "
|
||||
"%(payload)s",
|
||||
{'method': method.upper(), 'resource': url,
|
||||
'payload': kwargs.get('data')})
|
||||
start_time = time.time()
|
||||
response = self.session.request(method, url, verify=False,
|
||||
timeout=self.timeout, **kwargs)
|
||||
LOG.debug("%(method)s Took %(time).2f seconds to process",
|
||||
{'method': method.upper(),
|
||||
'time': time.time() - start_time})
|
||||
except (r_exc.Timeout, r_exc.SSLError) as te:
|
||||
# Should never see SSLError, unless requests package is old (<2.0)
|
||||
timeout_val = 0.0 if self.timeout is None else self.timeout
|
||||
LOG.warning(_LW("%(method)s: Request timeout%(ssl)s "
|
||||
"(%(timeout).3f sec) for CSR(%(host)s)"),
|
||||
{'method': method,
|
||||
'timeout': timeout_val,
|
||||
'ssl': '(SSLError)'
|
||||
if isinstance(te, r_exc.SSLError) else '',
|
||||
'host': self.host})
|
||||
self.status = requests.codes.REQUEST_TIMEOUT
|
||||
except r_exc.ConnectionError:
|
||||
LOG.exception(_LE("%(method)s: Unable to connect to "
|
||||
"CSR(%(host)s)"),
|
||||
{'method': method, 'host': self.host})
|
||||
self.status = requests.codes.NOT_FOUND
|
||||
except Exception as e:
|
||||
LOG.error(_LE("%(method)s: Unexpected error for CSR (%(host)s): "
|
||||
"%(error)s"),
|
||||
{'method': method, 'host': self.host, 'error': e})
|
||||
self.status = requests.codes.INTERNAL_SERVER_ERROR
|
||||
else:
|
||||
self.status = response.status_code
|
||||
LOG.debug("%(method)s: Completed [%(status)s]",
|
||||
{'method': method, 'status': self.status})
|
||||
return self._response_info_for(response, method)
|
||||
|
||||
def authenticate(self):
|
||||
"""Obtain a token to use for subsequent CSR REST requests.
|
||||
|
||||
This is called when there is no token yet, or if the token has expired
|
||||
and attempts to use it resulted in an UNAUTHORIZED REST response.
|
||||
"""
|
||||
|
||||
url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'}
|
||||
headers = {'Content-Length': '0',
|
||||
'Accept': 'application/json'}
|
||||
headers.update(HEADER_CONTENT_TYPE_JSON)
|
||||
LOG.debug("%(auth)s with CSR %(host)s",
|
||||
{'auth': 'Authenticating' if self.token is None
|
||||
else 'Reauthenticating', 'host': self.host})
|
||||
self.token = None
|
||||
response = self._request("POST", url, headers=headers, auth=self.auth)
|
||||
if response:
|
||||
self.token = response['token-id']
|
||||
LOG.debug("Successfully authenticated with CSR %s", self.host)
|
||||
return True
|
||||
LOG.error(_LE("Failed authentication with CSR %(host)s [%(status)s]"),
|
||||
{'host': self.host, 'status': self.status})
|
||||
|
||||
def _do_request(self, method, resource, payload=None, more_headers=None,
|
||||
full_url=False):
|
||||
"""Perform a REST request to a CSR resource.
|
||||
|
||||
If this is the first time interacting with the CSR, a token will
|
||||
be obtained. If the request fails, due to an expired token, the
|
||||
token will be obtained and the request will be retried once more.
|
||||
"""
|
||||
|
||||
if self.token is None:
|
||||
if not self.authenticate():
|
||||
return
|
||||
|
||||
if full_url:
|
||||
url = resource
|
||||
else:
|
||||
url = ('https://%(host)s/api/v1/%(resource)s' %
|
||||
{'host': self.host, 'resource': resource})
|
||||
headers = {'Accept': 'application/json', 'X-auth-token': self.token}
|
||||
if more_headers:
|
||||
headers.update(more_headers)
|
||||
if payload:
|
||||
payload = jsonutils.dumps(payload)
|
||||
response = self._request(method, url, data=payload, headers=headers)
|
||||
if self.status == requests.codes.UNAUTHORIZED:
|
||||
if not self.authenticate():
|
||||
return
|
||||
headers['X-auth-token'] = self.token
|
||||
response = self._request(method, url, data=payload,
|
||||
headers=headers)
|
||||
if self.status != requests.codes.REQUEST_TIMEOUT:
|
||||
return response
|
||||
LOG.error(_LE("%(method)s: Request timeout for CSR(%(host)s)"),
|
||||
{'method': method, 'host': self.host})
|
||||
|
||||
def get_request(self, resource, full_url=False):
|
||||
"""Perform a REST GET requests for a CSR resource."""
|
||||
return self._do_request('GET', resource, full_url=full_url)
|
||||
|
||||
def post_request(self, resource, payload=None):
|
||||
"""Perform a POST request to a CSR resource."""
|
||||
return self._do_request('POST', resource, payload=payload,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
def put_request(self, resource, payload=None):
|
||||
"""Perform a PUT request to a CSR resource."""
|
||||
return self._do_request('PUT', resource, payload=payload,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
def delete_request(self, resource):
|
||||
"""Perform a DELETE request on a CSR resource."""
|
||||
return self._do_request('DELETE', resource,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
# VPN Specific APIs
|
||||
|
||||
def create_ike_policy(self, policy_info):
|
||||
base_ike_policy_info = {u'version': u'v1',
|
||||
u'local-auth-method': u'pre-share'}
|
||||
base_ike_policy_info.update(policy_info)
|
||||
return self.post_request(URI_VPN_IKE_POLICIES,
|
||||
payload=base_ike_policy_info)
|
||||
|
||||
def create_ipsec_policy(self, policy_info):
|
||||
base_ipsec_policy_info = {u'mode': u'tunnel'}
|
||||
base_ipsec_policy_info.update(policy_info)
|
||||
return self.post_request(URI_VPN_IPSEC_POLICIES,
|
||||
payload=base_ipsec_policy_info)
|
||||
|
||||
def create_pre_shared_key(self, psk_info):
|
||||
return self.post_request(self.vrf_prefix + URI_VPN_IKE_KEYRINGS,
|
||||
payload=psk_info)
|
||||
|
||||
def create_ipsec_connection(self, connection_info):
|
||||
base_conn_info = {
|
||||
u'vpn-type': u'site-to-site',
|
||||
u'ip-version': u'ipv4',
|
||||
u'local-device': {
|
||||
u'tunnel-ip-address': self.outer_if_name,
|
||||
u'ip-address': self.inner_if_name
|
||||
}
|
||||
}
|
||||
connection_info.update(base_conn_info)
|
||||
if self.vrf:
|
||||
connection_info[u'tunnel-vrf'] = self.vrf
|
||||
return self.post_request(self.vrf_prefix + URI_VPN_SITE_TO_SITE,
|
||||
payload=connection_info)
|
||||
|
||||
def configure_ike_keepalive(self, keepalive_info):
|
||||
base_keepalive_info = {u'periodic': True}
|
||||
keepalive_info.update(base_keepalive_info)
|
||||
return self.put_request(URI_VPN_IKE_KEEPALIVE, keepalive_info)
|
||||
|
||||
def create_static_route(self, route_info):
|
||||
return self.post_request(self.vrf_prefix + URI_ROUTING_STATIC_ROUTES,
|
||||
payload=route_info)
|
||||
|
||||
def delete_static_route(self, route_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_ROUTING_STATIC_ROUTES_ID % route_id)
|
||||
|
||||
def set_ipsec_connection_state(self, tunnel, admin_up=True):
|
||||
"""Set the IPSec site-to-site connection (tunnel) admin state.
|
||||
|
||||
Note: When a tunnel is created, it will be admin up.
|
||||
"""
|
||||
info = {u'vpn-interface-name': tunnel, u'enabled': admin_up}
|
||||
return self.put_request(
|
||||
self.vrf_prefix + URI_VPN_SITE_TO_SITE_STATE % tunnel, info)
|
||||
|
||||
def delete_ipsec_connection(self, conn_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_VPN_SITE_TO_SITE_ID % conn_id)
|
||||
|
||||
def delete_ipsec_policy(self, policy_id):
|
||||
return self.delete_request(URI_VPN_IPSEC_POLICIES_ID % policy_id)
|
||||
|
||||
def delete_ike_policy(self, policy_id):
|
||||
return self.delete_request(URI_VPN_IKE_POLICIES_ID % policy_id)
|
||||
|
||||
def delete_pre_shared_key(self, key_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_VPN_IKE_KEYRINGS_ID % key_id)
|
||||
|
||||
def read_tunnel_statuses(self):
|
||||
results = self.get_request(self.vrf_prefix +
|
||||
URI_VPN_SITE_ACTIVE_SESSIONS)
|
||||
if self.status != requests.codes.OK or not results:
|
||||
return []
|
||||
tunnels = [(t[u'vpn-interface-name'], t[u'status'])
|
||||
for t in results['items']]
|
||||
return tunnels
|
|
@ -1,742 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# 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 abc
|
||||
import collections
|
||||
import requests
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo import messaging
|
||||
import six
|
||||
|
||||
from neutron.common import exceptions
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron import context as ctx
|
||||
from neutron.i18n import _LE, _LI, _LW
|
||||
from neutron.openstack.common import lockutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import loopingcall
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.plugins.common import utils as plugin_utils
|
||||
from neutron.services.vpn.common import topics
|
||||
from neutron.services.vpn import device_drivers
|
||||
from neutron.services.vpn.device_drivers import (
|
||||
cisco_csr_rest_client as csr_client)
|
||||
|
||||
|
||||
ipsec_opts = [
|
||||
cfg.IntOpt('status_check_interval',
|
||||
default=60,
|
||||
help=_("Status check interval for Cisco CSR IPSec connections"))
|
||||
]
|
||||
cfg.CONF.register_opts(ipsec_opts, 'cisco_csr_ipsec')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RollbackStep = collections.namedtuple('RollbackStep',
|
||||
['action', 'resource_id', 'title'])
|
||||
|
||||
|
||||
class CsrResourceCreateFailure(exceptions.NeutronException):
|
||||
message = _("Cisco CSR failed to create %(resource)s (%(which)s)")
|
||||
|
||||
|
||||
class CsrAdminStateChangeFailure(exceptions.NeutronException):
|
||||
message = _("Cisco CSR failed to change %(tunnel)s admin state to "
|
||||
"%(state)s")
|
||||
|
||||
|
||||
class CsrDriverMismatchError(exceptions.NeutronException):
|
||||
message = _("Required %(resource)s attribute %(attr)s mapping for Cisco "
|
||||
"CSR is missing in device driver")
|
||||
|
||||
|
||||
class CsrUnknownMappingError(exceptions.NeutronException):
|
||||
message = _("Device driver does not have a mapping of '%(value)s for "
|
||||
"attribute %(attr)s of %(resource)s")
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnDriverApi(object):
|
||||
"""RPC API for agent to plugin messaging."""
|
||||
|
||||
def __init__(self, topic):
|
||||
target = messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def get_vpn_services_on_host(self, context, host):
|
||||
"""Get list of vpnservices on this host.
|
||||
|
||||
The vpnservices including related ipsec_site_connection,
|
||||
ikepolicy, ipsecpolicy, and Cisco info on this host.
|
||||
"""
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(context, 'get_vpn_services_on_host', host=host)
|
||||
|
||||
def update_status(self, context, status):
|
||||
"""Update status for all VPN services and connections."""
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(context, 'update_status', status=status)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class CiscoCsrIPsecDriver(device_drivers.DeviceDriver):
|
||||
"""Cisco CSR VPN Device Driver for IPSec.
|
||||
|
||||
This class is designed for use with L3-agent now.
|
||||
However this driver will be used with another agent in future.
|
||||
so the use of "Router" is kept minimul now.
|
||||
Instead of router_id, we are using process_id in this code.
|
||||
"""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
target = messaging.Target(version='1.0')
|
||||
|
||||
def __init__(self, agent, host):
|
||||
self.host = host
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
context = ctx.get_admin_context_without_session()
|
||||
node_topic = '%s.%s' % (topics.CISCO_IPSEC_AGENT_TOPIC, self.host)
|
||||
|
||||
self.service_state = {}
|
||||
|
||||
self.endpoints = [self]
|
||||
self.conn.create_consumer(node_topic, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
self.agent_rpc = (
|
||||
CiscoCsrIPsecVpnDriverApi(topics.CISCO_IPSEC_DRIVER_TOPIC))
|
||||
self.periodic_report = loopingcall.FixedIntervalLoopingCall(
|
||||
self.report_status, context)
|
||||
self.periodic_report.start(
|
||||
interval=agent.conf.cisco_csr_ipsec.status_check_interval)
|
||||
LOG.debug("Device driver initialized for %s", node_topic)
|
||||
|
||||
def vpnservice_updated(self, context, **kwargs):
|
||||
"""Handle VPNaaS service driver change notifications."""
|
||||
LOG.debug("Handling VPN service update notification '%s'",
|
||||
kwargs.get('reason', ''))
|
||||
self.sync(context, [])
|
||||
|
||||
def create_vpn_service(self, service_data):
|
||||
"""Create new entry to track VPN service and its connections."""
|
||||
csr = csr_client.CsrRestClient(service_data['router_info'])
|
||||
vpn_service_id = service_data['id']
|
||||
self.service_state[vpn_service_id] = CiscoCsrVpnService(
|
||||
service_data, csr)
|
||||
return self.service_state[vpn_service_id]
|
||||
|
||||
def update_connection(self, context, vpn_service_id, conn_data):
|
||||
"""Handle notification for a single IPSec connection."""
|
||||
vpn_service = self.service_state[vpn_service_id]
|
||||
conn_id = conn_data['id']
|
||||
conn_is_admin_up = conn_data[u'admin_state_up']
|
||||
|
||||
if conn_id in vpn_service.conn_state: # Existing connection...
|
||||
ipsec_conn = vpn_service.conn_state[conn_id]
|
||||
config_changed = ipsec_conn.check_for_changes(conn_data)
|
||||
if config_changed:
|
||||
LOG.debug("Update: Existing connection %s changed", conn_id)
|
||||
ipsec_conn.delete_ipsec_site_connection(context, conn_id)
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
ipsec_conn.conn_info = conn_data
|
||||
|
||||
if ipsec_conn.forced_down:
|
||||
if vpn_service.is_admin_up and conn_is_admin_up:
|
||||
LOG.debug("Update: Connection %s no longer admin down",
|
||||
conn_id)
|
||||
ipsec_conn.set_admin_state(is_up=True)
|
||||
ipsec_conn.forced_down = False
|
||||
else:
|
||||
if not vpn_service.is_admin_up or not conn_is_admin_up:
|
||||
LOG.debug("Update: Connection %s forced to admin down",
|
||||
conn_id)
|
||||
ipsec_conn.set_admin_state(is_up=False)
|
||||
ipsec_conn.forced_down = True
|
||||
else: # New connection...
|
||||
ipsec_conn = vpn_service.create_connection(conn_data)
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
if not vpn_service.is_admin_up or not conn_is_admin_up:
|
||||
LOG.debug("Update: Created new connection %s in admin down "
|
||||
"state", conn_id)
|
||||
ipsec_conn.set_admin_state(is_up=False)
|
||||
ipsec_conn.forced_down = True
|
||||
else:
|
||||
LOG.debug("Update: Created new connection %s", conn_id)
|
||||
|
||||
ipsec_conn.is_dirty = False
|
||||
ipsec_conn.last_status = conn_data['status']
|
||||
ipsec_conn.is_admin_up = conn_is_admin_up
|
||||
return ipsec_conn
|
||||
|
||||
def update_service(self, context, service_data):
|
||||
"""Handle notification for a single VPN Service and its connections."""
|
||||
vpn_service_id = service_data['id']
|
||||
if vpn_service_id in self.service_state:
|
||||
LOG.debug("Update: Existing VPN service %s detected",
|
||||
vpn_service_id)
|
||||
vpn_service = self.service_state[vpn_service_id]
|
||||
else:
|
||||
LOG.debug("Update: New VPN service %s detected", vpn_service_id)
|
||||
vpn_service = self.create_vpn_service(service_data)
|
||||
if not vpn_service:
|
||||
return
|
||||
|
||||
vpn_service.is_dirty = False
|
||||
vpn_service.connections_removed = False
|
||||
vpn_service.last_status = service_data['status']
|
||||
vpn_service.is_admin_up = service_data[u'admin_state_up']
|
||||
for conn_data in service_data['ipsec_conns']:
|
||||
self.update_connection(context, vpn_service_id, conn_data)
|
||||
LOG.debug("Update: Completed update processing")
|
||||
return vpn_service
|
||||
|
||||
def update_all_services_and_connections(self, context):
|
||||
"""Update services and connections based on plugin info.
|
||||
|
||||
Perform any create and update operations and then update status.
|
||||
Mark every visited connection as no longer "dirty" so they will
|
||||
not be deleted at end of sync processing.
|
||||
"""
|
||||
services_data = self.agent_rpc.get_vpn_services_on_host(context,
|
||||
self.host)
|
||||
LOG.debug("Sync updating for %d VPN services", len(services_data))
|
||||
vpn_services = []
|
||||
for service_data in services_data:
|
||||
vpn_service = self.update_service(context, service_data)
|
||||
if vpn_service:
|
||||
vpn_services.append(vpn_service)
|
||||
return vpn_services
|
||||
|
||||
def mark_existing_connections_as_dirty(self):
|
||||
"""Mark all existing connections as "dirty" for sync."""
|
||||
service_count = 0
|
||||
connection_count = 0
|
||||
for service_state in self.service_state.values():
|
||||
service_state.is_dirty = True
|
||||
service_count += 1
|
||||
for conn_id in service_state.conn_state:
|
||||
service_state.conn_state[conn_id].is_dirty = True
|
||||
connection_count += 1
|
||||
LOG.debug("Mark: %(service)d VPN services and %(conn)d IPSec "
|
||||
"connections marked dirty", {'service': service_count,
|
||||
'conn': connection_count})
|
||||
|
||||
def remove_unknown_connections(self, context):
|
||||
"""Remove connections that are not known by service driver."""
|
||||
service_count = 0
|
||||
connection_count = 0
|
||||
for vpn_service_id, vpn_service in self.service_state.items():
|
||||
dirty = [c_id for c_id, c in vpn_service.conn_state.items()
|
||||
if c.is_dirty]
|
||||
vpn_service.connections_removed = len(dirty) > 0
|
||||
for conn_id in dirty:
|
||||
conn_state = vpn_service.conn_state[conn_id]
|
||||
conn_state.delete_ipsec_site_connection(context, conn_id)
|
||||
connection_count += 1
|
||||
del vpn_service.conn_state[conn_id]
|
||||
if vpn_service.is_dirty:
|
||||
service_count += 1
|
||||
del self.service_state[vpn_service_id]
|
||||
elif dirty:
|
||||
self.connections_removed = True
|
||||
LOG.debug("Sweep: Removed %(service)d dirty VPN service%(splural)s "
|
||||
"and %(conn)d dirty IPSec connection%(cplural)s",
|
||||
{'service': service_count, 'conn': connection_count,
|
||||
'splural': 's'[service_count == 1:],
|
||||
'cplural': 's'[connection_count == 1:]})
|
||||
|
||||
def build_report_for_connections_on(self, vpn_service):
|
||||
"""Create the report fragment for IPSec connections on a service.
|
||||
|
||||
Collect the current status from the Cisco CSR and use that to update
|
||||
the status and generate report fragment for each connection on the
|
||||
service. If there is no status information, or no change, then no
|
||||
report info will be created for the connection. The combined report
|
||||
data is returned.
|
||||
"""
|
||||
LOG.debug("Report: Collecting status for IPSec connections on VPN "
|
||||
"service %s", vpn_service.service_id)
|
||||
tunnels = vpn_service.get_ipsec_connections_status()
|
||||
report = {}
|
||||
for connection in vpn_service.conn_state.values():
|
||||
if connection.forced_down:
|
||||
LOG.debug("Connection %s forced down", connection.conn_id)
|
||||
current_status = constants.DOWN
|
||||
else:
|
||||
current_status = connection.find_current_status_in(tunnels)
|
||||
LOG.debug("Connection %(conn)s reported %(status)s",
|
||||
{'conn': connection.conn_id,
|
||||
'status': current_status})
|
||||
frag = connection.update_status_and_build_report(current_status)
|
||||
if frag:
|
||||
LOG.debug("Report: Adding info for IPSec connection %s",
|
||||
connection.conn_id)
|
||||
report.update(frag)
|
||||
return report
|
||||
|
||||
def build_report_for_service(self, vpn_service):
|
||||
"""Create the report info for a VPN service and its IPSec connections.
|
||||
|
||||
Get the report info for the connections on the service, and include
|
||||
it into the report info for the VPN service. If there is no report
|
||||
info for the connection, then no change has occurred and no report
|
||||
will be generated. If there is only one connection for the service,
|
||||
we'll set the service state to match the connection (with ERROR seen
|
||||
as DOWN).
|
||||
"""
|
||||
conn_report = self.build_report_for_connections_on(vpn_service)
|
||||
if conn_report or vpn_service.connections_removed:
|
||||
pending_handled = plugin_utils.in_pending_status(
|
||||
vpn_service.last_status)
|
||||
vpn_service.update_last_status()
|
||||
LOG.debug("Report: Adding info for VPN service %s",
|
||||
vpn_service.service_id)
|
||||
return {u'id': vpn_service.service_id,
|
||||
u'status': vpn_service.last_status,
|
||||
u'updated_pending_status': pending_handled,
|
||||
u'ipsec_site_connections': conn_report}
|
||||
else:
|
||||
return {}
|
||||
|
||||
@lockutils.synchronized('vpn-agent', 'neutron-')
|
||||
def report_status(self, context):
|
||||
"""Report status of all VPN services and IPSec connections to plugin.
|
||||
|
||||
This is called periodically by the agent, to push up changes in
|
||||
status. Use a lock to serialize access to (and changing of)
|
||||
running state.
|
||||
"""
|
||||
return self.report_status_internal(context)
|
||||
|
||||
def report_status_internal(self, context):
|
||||
"""Generate report and send to plugin, if anything changed."""
|
||||
service_report = []
|
||||
LOG.debug("Report: Starting status report processing")
|
||||
for vpn_service_id, vpn_service in self.service_state.items():
|
||||
LOG.debug("Report: Collecting status for VPN service %s",
|
||||
vpn_service_id)
|
||||
report = self.build_report_for_service(vpn_service)
|
||||
if report:
|
||||
service_report.append(report)
|
||||
if service_report:
|
||||
LOG.info(_LI("Sending status report update to plugin"))
|
||||
self.agent_rpc.update_status(context, service_report)
|
||||
LOG.debug("Report: Completed status report processing")
|
||||
return service_report
|
||||
|
||||
@lockutils.synchronized('vpn-agent', 'neutron-')
|
||||
def sync(self, context, routers):
|
||||
"""Synchronize with plugin and report current status.
|
||||
|
||||
Mark all "known" services/connections as dirty, update them based on
|
||||
information from the plugin, remove (sweep) any connections that are
|
||||
not updated (dirty), and report updates, if any, back to plugin.
|
||||
Called when update/delete a service or create/update/delete a
|
||||
connection (vpnservice_updated message), or router change
|
||||
(_process_routers).
|
||||
|
||||
Use lock to serialize access (and changes) to running state for VPN
|
||||
service and IPsec connections.
|
||||
"""
|
||||
self.mark_existing_connections_as_dirty()
|
||||
self.update_all_services_and_connections(context)
|
||||
self.remove_unknown_connections(context)
|
||||
self.report_status_internal(context)
|
||||
|
||||
def create_router(self, process_id):
|
||||
"""Actions taken when router created."""
|
||||
# Note: Since Cisco CSR is running out-of-band, nothing to do here
|
||||
pass
|
||||
|
||||
def destroy_router(self, process_id):
|
||||
"""Actions taken when router deleted."""
|
||||
# Note: Since Cisco CSR is running out-of-band, nothing to do here
|
||||
pass
|
||||
|
||||
|
||||
class CiscoCsrVpnService(object):
|
||||
|
||||
"""Maintains state/status information for a service and its connections."""
|
||||
|
||||
def __init__(self, service_data, csr):
|
||||
self.service_id = service_data['id']
|
||||
self.conn_state = {}
|
||||
self.csr = csr
|
||||
self.is_admin_up = True
|
||||
# TODO(pcm) FUTURE - handle sharing of policies
|
||||
|
||||
def create_connection(self, conn_data):
|
||||
conn_id = conn_data['id']
|
||||
self.conn_state[conn_id] = CiscoCsrIPSecConnection(conn_data, self.csr)
|
||||
return self.conn_state[conn_id]
|
||||
|
||||
def get_connection(self, conn_id):
|
||||
return self.conn_state.get(conn_id)
|
||||
|
||||
def conn_status(self, conn_id):
|
||||
conn_state = self.get_connection(conn_id)
|
||||
if conn_state:
|
||||
return conn_state.last_status
|
||||
|
||||
def snapshot_conn_state(self, ipsec_conn):
|
||||
"""Create/obtain connection state and save current status."""
|
||||
conn_state = self.conn_state.setdefault(
|
||||
ipsec_conn['id'], CiscoCsrIPSecConnection(ipsec_conn, self.csr))
|
||||
conn_state.last_status = ipsec_conn['status']
|
||||
conn_state.is_dirty = False
|
||||
return conn_state
|
||||
|
||||
STATUS_MAP = {'ERROR': constants.ERROR,
|
||||
'UP-ACTIVE': constants.ACTIVE,
|
||||
'UP-IDLE': constants.ACTIVE,
|
||||
'UP-NO-IKE': constants.ACTIVE,
|
||||
'DOWN': constants.DOWN,
|
||||
'DOWN-NEGOTIATING': constants.DOWN}
|
||||
|
||||
def get_ipsec_connections_status(self):
|
||||
"""Obtain current status of all tunnels on a Cisco CSR.
|
||||
|
||||
Convert them to OpenStack status values.
|
||||
"""
|
||||
tunnels = self.csr.read_tunnel_statuses()
|
||||
for tunnel in tunnels:
|
||||
LOG.debug("CSR Reports %(tunnel)s status '%(status)s'",
|
||||
{'tunnel': tunnel[0], 'status': tunnel[1]})
|
||||
return dict(map(lambda x: (x[0], self.STATUS_MAP[x[1]]), tunnels))
|
||||
|
||||
def find_matching_connection(self, tunnel_id):
|
||||
"""Find IPSec connection using Cisco CSR tunnel specified, if any."""
|
||||
for connection in self.conn_state.values():
|
||||
if connection.tunnel == tunnel_id:
|
||||
return connection.conn_id
|
||||
|
||||
def no_connections_up(self):
|
||||
return not any(c.last_status == 'ACTIVE'
|
||||
for c in self.conn_state.values())
|
||||
|
||||
def update_last_status(self):
|
||||
if not self.is_admin_up or self.no_connections_up():
|
||||
self.last_status = constants.DOWN
|
||||
else:
|
||||
self.last_status = constants.ACTIVE
|
||||
|
||||
|
||||
class CiscoCsrIPSecConnection(object):
|
||||
|
||||
"""State and actions for IPSec site-to-site connections."""
|
||||
|
||||
def __init__(self, conn_info, csr):
|
||||
self.conn_info = conn_info
|
||||
self.csr = csr
|
||||
self.steps = []
|
||||
self.forced_down = False
|
||||
self.changed = False
|
||||
|
||||
@property
|
||||
def conn_id(self):
|
||||
return self.conn_info['id']
|
||||
|
||||
@property
|
||||
def is_admin_up(self):
|
||||
return self.conn_info['admin_state_up']
|
||||
|
||||
@is_admin_up.setter
|
||||
def is_admin_up(self, is_up):
|
||||
self.conn_info['admin_state_up'] = is_up
|
||||
|
||||
@property
|
||||
def tunnel(self):
|
||||
return self.conn_info['cisco']['site_conn_id']
|
||||
|
||||
def check_for_changes(self, curr_conn):
|
||||
return not all([self.conn_info[attr] == curr_conn[attr]
|
||||
for attr in ('mtu', 'psk', 'peer_address',
|
||||
'peer_cidrs', 'ike_policy',
|
||||
'ipsec_policy', 'cisco')])
|
||||
|
||||
def find_current_status_in(self, statuses):
|
||||
if self.tunnel in statuses:
|
||||
return statuses[self.tunnel]
|
||||
else:
|
||||
return constants.ERROR
|
||||
|
||||
def update_status_and_build_report(self, current_status):
|
||||
if current_status != self.last_status:
|
||||
pending_handled = plugin_utils.in_pending_status(self.last_status)
|
||||
self.last_status = current_status
|
||||
return {self.conn_id: {'status': current_status,
|
||||
'updated_pending_status': pending_handled}}
|
||||
else:
|
||||
return {}
|
||||
|
||||
DIALECT_MAP = {'ike_policy': {'name': 'IKE Policy',
|
||||
'v1': u'v1',
|
||||
# auth_algorithm -> hash
|
||||
'sha1': u'sha',
|
||||
# encryption_algorithm -> encryption
|
||||
'3des': u'3des',
|
||||
'aes-128': u'aes',
|
||||
'aes-192': u'aes192',
|
||||
'aes-256': u'aes256',
|
||||
# pfs -> dhGroup
|
||||
'group2': 2,
|
||||
'group5': 5,
|
||||
'group14': 14},
|
||||
'ipsec_policy': {'name': 'IPSec Policy',
|
||||
# auth_algorithm -> esp-authentication
|
||||
'sha1': u'esp-sha-hmac',
|
||||
# transform_protocol -> ah
|
||||
'esp': None,
|
||||
'ah': u'ah-sha-hmac',
|
||||
'ah-esp': u'ah-sha-hmac',
|
||||
# encryption_algorithm -> esp-encryption
|
||||
'3des': u'esp-3des',
|
||||
'aes-128': u'esp-aes',
|
||||
'aes-192': u'esp-192-aes',
|
||||
'aes-256': u'esp-256-aes',
|
||||
# pfs -> pfs
|
||||
'group2': u'group2',
|
||||
'group5': u'group5',
|
||||
'group14': u'group14'}}
|
||||
|
||||
def translate_dialect(self, resource, attribute, info):
|
||||
"""Map VPNaaS attributes values to CSR values for a resource."""
|
||||
name = self.DIALECT_MAP[resource]['name']
|
||||
if attribute not in info:
|
||||
raise CsrDriverMismatchError(resource=name, attr=attribute)
|
||||
value = info[attribute].lower()
|
||||
if value in self.DIALECT_MAP[resource]:
|
||||
return self.DIALECT_MAP[resource][value]
|
||||
raise CsrUnknownMappingError(resource=name, attr=attribute,
|
||||
value=value)
|
||||
|
||||
def create_psk_info(self, psk_id, conn_info):
|
||||
"""Collect/create attributes needed for pre-shared key."""
|
||||
return {u'keyring-name': psk_id,
|
||||
u'pre-shared-key-list': [
|
||||
{u'key': conn_info['psk'],
|
||||
u'encrypted': False,
|
||||
u'peer-address': conn_info['peer_address']}]}
|
||||
|
||||
def create_ike_policy_info(self, ike_policy_id, conn_info):
|
||||
"""Collect/create/map attributes needed for IKE policy."""
|
||||
for_ike = 'ike_policy'
|
||||
policy_info = conn_info[for_ike]
|
||||
version = self.translate_dialect(for_ike,
|
||||
'ike_version',
|
||||
policy_info)
|
||||
encrypt_algorithm = self.translate_dialect(for_ike,
|
||||
'encryption_algorithm',
|
||||
policy_info)
|
||||
auth_algorithm = self.translate_dialect(for_ike,
|
||||
'auth_algorithm',
|
||||
policy_info)
|
||||
group = self.translate_dialect(for_ike,
|
||||
'pfs',
|
||||
policy_info)
|
||||
lifetime = policy_info['lifetime_value']
|
||||
return {u'version': version,
|
||||
u'priority-id': ike_policy_id,
|
||||
u'encryption': encrypt_algorithm,
|
||||
u'hash': auth_algorithm,
|
||||
u'dhGroup': group,
|
||||
u'lifetime': lifetime}
|
||||
|
||||
def create_ipsec_policy_info(self, ipsec_policy_id, info):
|
||||
"""Collect/create attributes needed for IPSec policy.
|
||||
|
||||
Note: OpenStack will provide a default encryption algorithm, if one is
|
||||
not provided, so a authentication only configuration of (ah, sha1),
|
||||
which maps to ah-sha-hmac transform protocol, cannot be selected.
|
||||
As a result, we'll always configure the encryption algorithm, and
|
||||
will select ah-sha-hmac for transform protocol.
|
||||
"""
|
||||
|
||||
for_ipsec = 'ipsec_policy'
|
||||
policy_info = info[for_ipsec]
|
||||
transform_protocol = self.translate_dialect(for_ipsec,
|
||||
'transform_protocol',
|
||||
policy_info)
|
||||
auth_algorithm = self.translate_dialect(for_ipsec,
|
||||
'auth_algorithm',
|
||||
policy_info)
|
||||
encrypt_algorithm = self.translate_dialect(for_ipsec,
|
||||
'encryption_algorithm',
|
||||
policy_info)
|
||||
group = self.translate_dialect(for_ipsec, 'pfs', policy_info)
|
||||
lifetime = policy_info['lifetime_value']
|
||||
settings = {u'policy-id': ipsec_policy_id,
|
||||
u'protection-suite': {
|
||||
u'esp-encryption': encrypt_algorithm,
|
||||
u'esp-authentication': auth_algorithm},
|
||||
u'lifetime-sec': lifetime,
|
||||
u'pfs': group,
|
||||
u'anti-replay-window-size': u'disable'}
|
||||
if transform_protocol:
|
||||
settings[u'protection-suite'][u'ah'] = transform_protocol
|
||||
return settings
|
||||
|
||||
def create_site_connection_info(self, site_conn_id, ipsec_policy_id,
|
||||
conn_info):
|
||||
"""Collect/create attributes needed for the IPSec connection."""
|
||||
mtu = conn_info['mtu']
|
||||
return {
|
||||
u'vpn-interface-name': site_conn_id,
|
||||
u'ipsec-policy-id': ipsec_policy_id,
|
||||
u'remote-device': {
|
||||
u'tunnel-ip-address': conn_info['peer_address']
|
||||
},
|
||||
u'mtu': mtu
|
||||
}
|
||||
|
||||
def create_routes_info(self, site_conn_id, conn_info):
|
||||
"""Collect/create attributes for static routes."""
|
||||
routes_info = []
|
||||
for peer_cidr in conn_info.get('peer_cidrs', []):
|
||||
route = {u'destination-network': peer_cidr,
|
||||
u'outgoing-interface': site_conn_id}
|
||||
route_id = csr_client.make_route_id(peer_cidr, site_conn_id)
|
||||
routes_info.append((route_id, route))
|
||||
return routes_info
|
||||
|
||||
def _check_create(self, resource, which):
|
||||
"""Determine if REST create request was successful."""
|
||||
if self.csr.status == requests.codes.CREATED:
|
||||
LOG.debug("%(resource)s %(which)s is configured",
|
||||
{'resource': resource, 'which': which})
|
||||
return
|
||||
LOG.error(_LE("Unable to create %(resource)s %(which)s: "
|
||||
"%(status)d"),
|
||||
{'resource': resource, 'which': which,
|
||||
'status': self.csr.status})
|
||||
# ToDO(pcm): Set state to error
|
||||
raise CsrResourceCreateFailure(resource=resource, which=which)
|
||||
|
||||
def do_create_action(self, action_suffix, info, resource_id, title):
|
||||
"""Perform a single REST step for IPSec site connection create."""
|
||||
create_action = 'create_%s' % action_suffix
|
||||
try:
|
||||
getattr(self.csr, create_action)(info)
|
||||
except AttributeError:
|
||||
LOG.exception(_LE("Internal error - '%s' is not defined"),
|
||||
create_action)
|
||||
raise CsrResourceCreateFailure(resource=title,
|
||||
which=resource_id)
|
||||
self._check_create(title, resource_id)
|
||||
self.steps.append(RollbackStep(action_suffix, resource_id, title))
|
||||
|
||||
def _verify_deleted(self, status, resource, which):
|
||||
"""Determine if REST delete request was successful."""
|
||||
if status in (requests.codes.NO_CONTENT, requests.codes.NOT_FOUND):
|
||||
LOG.debug("%(resource)s configuration %(which)s was removed",
|
||||
{'resource': resource, 'which': which})
|
||||
else:
|
||||
LOG.warning(_LW("Unable to delete %(resource)s %(which)s: "
|
||||
"%(status)d"), {'resource': resource,
|
||||
'which': which,
|
||||
'status': status})
|
||||
|
||||
def do_rollback(self):
|
||||
"""Undo create steps that were completed successfully."""
|
||||
for step in reversed(self.steps):
|
||||
delete_action = 'delete_%s' % step.action
|
||||
LOG.debug("Performing rollback action %(action)s for "
|
||||
"resource %(resource)s", {'action': delete_action,
|
||||
'resource': step.title})
|
||||
try:
|
||||
getattr(self.csr, delete_action)(step.resource_id)
|
||||
except AttributeError:
|
||||
LOG.exception(_LE("Internal error - '%s' is not defined"),
|
||||
delete_action)
|
||||
raise CsrResourceCreateFailure(resource=step.title,
|
||||
which=step.resource_id)
|
||||
self._verify_deleted(self.csr.status, step.title, step.resource_id)
|
||||
self.steps = []
|
||||
|
||||
def create_ipsec_site_connection(self, context, conn_info):
|
||||
"""Creates an IPSec site-to-site connection on CSR.
|
||||
|
||||
Create the PSK, IKE policy, IPSec policy, connection, static route,
|
||||
and (future) DPD.
|
||||
"""
|
||||
# Get all the IDs
|
||||
conn_id = conn_info['id']
|
||||
psk_id = conn_id
|
||||
site_conn_id = conn_info['cisco']['site_conn_id']
|
||||
ike_policy_id = conn_info['cisco']['ike_policy_id']
|
||||
ipsec_policy_id = conn_info['cisco']['ipsec_policy_id']
|
||||
|
||||
LOG.debug('Creating IPSec connection %s', conn_id)
|
||||
# Get all the attributes needed to create
|
||||
try:
|
||||
psk_info = self.create_psk_info(psk_id, conn_info)
|
||||
ike_policy_info = self.create_ike_policy_info(ike_policy_id,
|
||||
conn_info)
|
||||
ipsec_policy_info = self.create_ipsec_policy_info(ipsec_policy_id,
|
||||
conn_info)
|
||||
connection_info = self.create_site_connection_info(site_conn_id,
|
||||
ipsec_policy_id,
|
||||
conn_info)
|
||||
routes_info = self.create_routes_info(site_conn_id, conn_info)
|
||||
except (CsrUnknownMappingError, CsrDriverMismatchError) as e:
|
||||
LOG.exception(e)
|
||||
return
|
||||
|
||||
try:
|
||||
self.do_create_action('pre_shared_key', psk_info,
|
||||
conn_id, 'Pre-Shared Key')
|
||||
self.do_create_action('ike_policy', ike_policy_info,
|
||||
ike_policy_id, 'IKE Policy')
|
||||
self.do_create_action('ipsec_policy', ipsec_policy_info,
|
||||
ipsec_policy_id, 'IPSec Policy')
|
||||
self.do_create_action('ipsec_connection', connection_info,
|
||||
site_conn_id, 'IPSec Connection')
|
||||
|
||||
# TODO(pcm): FUTURE - Do DPD for v1 and handle if >1 connection
|
||||
# and different DPD settings
|
||||
for route_id, route_info in routes_info:
|
||||
self.do_create_action('static_route', route_info,
|
||||
route_id, 'Static Route')
|
||||
except CsrResourceCreateFailure:
|
||||
self.do_rollback()
|
||||
LOG.info(_LI("FAILED: Create of IPSec site-to-site connection %s"),
|
||||
conn_id)
|
||||
else:
|
||||
LOG.info(_LI("SUCCESS: Created IPSec site-to-site connection %s"),
|
||||
conn_id)
|
||||
|
||||
def delete_ipsec_site_connection(self, context, conn_id):
|
||||
"""Delete the site-to-site IPSec connection.
|
||||
|
||||
This will be best effort and will continue, if there are any
|
||||
failures.
|
||||
"""
|
||||
LOG.debug('Deleting IPSec connection %s', conn_id)
|
||||
if not self.steps:
|
||||
LOG.warning(_LW('Unable to find connection %s'), conn_id)
|
||||
else:
|
||||
self.do_rollback()
|
||||
|
||||
LOG.info(_LI("SUCCESS: Deleted IPSec site-to-site connection %s"),
|
||||
conn_id)
|
||||
|
||||
def set_admin_state(self, is_up):
|
||||
"""Change the admin state for the IPSec connection."""
|
||||
self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up)
|
||||
if self.csr.status != requests.codes.NO_CONTENT:
|
||||
state = "UP" if is_up else "DOWN"
|
||||
LOG.error(_LE("Unable to change %(tunnel)s admin state to "
|
||||
"%(state)s"), {'tunnel': self.tunnel,
|
||||
'state': state})
|
||||
raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state)
|
|
@ -1,702 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 abc
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import jinja2
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from oslo import messaging
|
||||
import six
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron import context
|
||||
from neutron.i18n import _LE
|
||||
from neutron.openstack.common import lockutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import loopingcall
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.plugins.common import utils as plugin_utils
|
||||
from neutron.services.vpn.common import topics
|
||||
from neutron.services.vpn import device_drivers
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
TEMPLATE_PATH = os.path.dirname(__file__)
|
||||
|
||||
ipsec_opts = [
|
||||
cfg.StrOpt(
|
||||
'config_base_dir',
|
||||
default='$state_path/ipsec',
|
||||
help=_('Location to store ipsec server config files')),
|
||||
cfg.IntOpt('ipsec_status_check_interval',
|
||||
default=60,
|
||||
help=_("Interval for checking ipsec status"))
|
||||
]
|
||||
cfg.CONF.register_opts(ipsec_opts, 'ipsec')
|
||||
|
||||
openswan_opts = [
|
||||
cfg.StrOpt(
|
||||
'ipsec_config_template',
|
||||
default=os.path.join(
|
||||
TEMPLATE_PATH,
|
||||
'template/openswan/ipsec.conf.template'),
|
||||
help=_('Template file for ipsec configuration')),
|
||||
cfg.StrOpt(
|
||||
'ipsec_secret_template',
|
||||
default=os.path.join(
|
||||
TEMPLATE_PATH,
|
||||
'template/openswan/ipsec.secret.template'),
|
||||
help=_('Template file for ipsec secret configuration'))
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(openswan_opts, 'openswan')
|
||||
|
||||
JINJA_ENV = None
|
||||
|
||||
STATUS_MAP = {
|
||||
'erouted': constants.ACTIVE,
|
||||
'unrouted': constants.DOWN
|
||||
}
|
||||
|
||||
IPSEC_CONNS = 'ipsec_site_connections'
|
||||
|
||||
|
||||
def _get_template(template_file):
|
||||
global JINJA_ENV
|
||||
if not JINJA_ENV:
|
||||
templateLoader = jinja2.FileSystemLoader(searchpath="/")
|
||||
JINJA_ENV = jinja2.Environment(loader=templateLoader)
|
||||
return JINJA_ENV.get_template(template_file)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseSwanProcess():
|
||||
"""Swan Family Process Manager
|
||||
|
||||
This class manages start/restart/stop ipsec process.
|
||||
This class create/delete config template
|
||||
"""
|
||||
|
||||
binary = "ipsec"
|
||||
CONFIG_DIRS = [
|
||||
'var/run',
|
||||
'log',
|
||||
'etc',
|
||||
'etc/ipsec.d/aacerts',
|
||||
'etc/ipsec.d/acerts',
|
||||
'etc/ipsec.d/cacerts',
|
||||
'etc/ipsec.d/certs',
|
||||
'etc/ipsec.d/crls',
|
||||
'etc/ipsec.d/ocspcerts',
|
||||
'etc/ipsec.d/policies',
|
||||
'etc/ipsec.d/private',
|
||||
'etc/ipsec.d/reqs',
|
||||
'etc/pki/nssdb/'
|
||||
]
|
||||
|
||||
DIALECT_MAP = {
|
||||
"3des": "3des",
|
||||
"aes-128": "aes128",
|
||||
"aes-256": "aes256",
|
||||
"aes-192": "aes192",
|
||||
"group2": "modp1024",
|
||||
"group5": "modp1536",
|
||||
"group14": "modp2048",
|
||||
"group15": "modp3072",
|
||||
"bi-directional": "start",
|
||||
"response-only": "add",
|
||||
"v2": "insist",
|
||||
"v1": "never"
|
||||
}
|
||||
|
||||
def __init__(self, conf, root_helper, process_id,
|
||||
vpnservice, namespace):
|
||||
self.conf = conf
|
||||
self.id = process_id
|
||||
self.root_helper = root_helper
|
||||
self.updated_pending_status = False
|
||||
self.namespace = namespace
|
||||
self.connection_status = {}
|
||||
self.config_dir = os.path.join(
|
||||
cfg.CONF.ipsec.config_base_dir, self.id)
|
||||
self.etc_dir = os.path.join(self.config_dir, 'etc')
|
||||
self.update_vpnservice(vpnservice)
|
||||
|
||||
def translate_dialect(self):
|
||||
if not self.vpnservice:
|
||||
return
|
||||
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
|
||||
self._dialect(ipsec_site_conn, 'initiator')
|
||||
self._dialect(ipsec_site_conn['ikepolicy'], 'ike_version')
|
||||
for key in ['encryption_algorithm',
|
||||
'auth_algorithm',
|
||||
'pfs']:
|
||||
self._dialect(ipsec_site_conn['ikepolicy'], key)
|
||||
self._dialect(ipsec_site_conn['ipsecpolicy'], key)
|
||||
|
||||
def update_vpnservice(self, vpnservice):
|
||||
self.vpnservice = vpnservice
|
||||
self.translate_dialect()
|
||||
|
||||
def _dialect(self, obj, key):
|
||||
obj[key] = self.DIALECT_MAP.get(obj[key], obj[key])
|
||||
|
||||
@abc.abstractmethod
|
||||
def ensure_configs(self):
|
||||
pass
|
||||
|
||||
def ensure_config_file(self, kind, template, vpnservice):
|
||||
"""Update config file, based on current settings for service."""
|
||||
config_str = self._gen_config_content(template, vpnservice)
|
||||
config_file_name = self._get_config_filename(kind)
|
||||
utils.replace_file(config_file_name, config_str)
|
||||
|
||||
def remove_config(self):
|
||||
"""Remove whole config file."""
|
||||
shutil.rmtree(self.config_dir, ignore_errors=True)
|
||||
|
||||
def _get_config_filename(self, kind):
|
||||
config_dir = self.etc_dir
|
||||
return os.path.join(config_dir, kind)
|
||||
|
||||
def _ensure_dir(self, dir_path):
|
||||
if not os.path.isdir(dir_path):
|
||||
os.makedirs(dir_path, 0o755)
|
||||
|
||||
def ensure_config_dir(self, vpnservice):
|
||||
"""Create config directory if it does not exist."""
|
||||
self._ensure_dir(self.config_dir)
|
||||
for subdir in self.CONFIG_DIRS:
|
||||
dir_path = os.path.join(self.config_dir, subdir)
|
||||
self._ensure_dir(dir_path)
|
||||
|
||||
def _gen_config_content(self, template_file, vpnservice):
|
||||
template = _get_template(template_file)
|
||||
return template.render(
|
||||
{'vpnservice': vpnservice,
|
||||
'state_path': cfg.CONF.state_path})
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_status(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.active:
|
||||
return constants.ACTIVE
|
||||
return constants.DOWN
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""Check if the process is active or not."""
|
||||
if not self.namespace:
|
||||
return False
|
||||
try:
|
||||
status = self.get_status()
|
||||
self._update_connection_status(status)
|
||||
except RuntimeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
"""Update Status based on vpnservice configuration."""
|
||||
if self.vpnservice and not self.vpnservice['admin_state_up']:
|
||||
self.disable()
|
||||
else:
|
||||
self.enable()
|
||||
|
||||
if plugin_utils.in_pending_status(self.vpnservice['status']):
|
||||
self.updated_pending_status = True
|
||||
|
||||
self.vpnservice['status'] = self.status
|
||||
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
|
||||
if plugin_utils.in_pending_status(ipsec_site_conn['status']):
|
||||
conn_id = ipsec_site_conn['id']
|
||||
conn_status = self.connection_status.get(conn_id)
|
||||
if not conn_status:
|
||||
continue
|
||||
conn_status['updated_pending_status'] = True
|
||||
ipsec_site_conn['status'] = conn_status['status']
|
||||
|
||||
def enable(self):
|
||||
"""Enabling the process."""
|
||||
try:
|
||||
self.ensure_configs()
|
||||
if self.active:
|
||||
self.restart()
|
||||
else:
|
||||
self.start()
|
||||
except RuntimeError:
|
||||
LOG.exception(
|
||||
_LE("Failed to enable vpn process on router %s"),
|
||||
self.id)
|
||||
|
||||
def disable(self):
|
||||
"""Disabling the process."""
|
||||
try:
|
||||
if self.active:
|
||||
self.stop()
|
||||
self.remove_config()
|
||||
except RuntimeError:
|
||||
LOG.exception(
|
||||
_LE("Failed to disable vpn process on router %s"),
|
||||
self.id)
|
||||
|
||||
@abc.abstractmethod
|
||||
def restart(self):
|
||||
"""Restart process."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def start(self):
|
||||
"""Start process."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def stop(self):
|
||||
"""Stop process."""
|
||||
|
||||
def _update_connection_status(self, status_output):
|
||||
for line in status_output.split('\n'):
|
||||
m = re.search('\d\d\d "([a-f0-9\-]+).* (unrouted|erouted);', line)
|
||||
if not m:
|
||||
continue
|
||||
connection_id = m.group(1)
|
||||
status = m.group(2)
|
||||
if not self.connection_status.get(connection_id):
|
||||
self.connection_status[connection_id] = {
|
||||
'status': None,
|
||||
'updated_pending_status': False
|
||||
}
|
||||
self.connection_status[
|
||||
connection_id]['status'] = STATUS_MAP[status]
|
||||
|
||||
|
||||
class OpenSwanProcess(BaseSwanProcess):
|
||||
"""OpenSwan Process manager class.
|
||||
|
||||
This process class uses three commands
|
||||
(1) ipsec pluto: IPsec IKE keying daemon
|
||||
(2) ipsec addconn: Adds new ipsec addconn
|
||||
(3) ipsec whack: control interface for IPSEC keying daemon
|
||||
"""
|
||||
def __init__(self, conf, root_helper, process_id,
|
||||
vpnservice, namespace):
|
||||
super(OpenSwanProcess, self).__init__(
|
||||
conf, root_helper, process_id,
|
||||
vpnservice, namespace)
|
||||
self.secrets_file = os.path.join(
|
||||
self.etc_dir, 'ipsec.secrets')
|
||||
self.config_file = os.path.join(
|
||||
self.etc_dir, 'ipsec.conf')
|
||||
self.pid_path = os.path.join(
|
||||
self.config_dir, 'var', 'run', 'pluto')
|
||||
|
||||
def _execute(self, cmd, check_exit_code=True):
|
||||
"""Execute command on namespace."""
|
||||
ip_wrapper = ip_lib.IPWrapper(self.root_helper, self.namespace)
|
||||
return ip_wrapper.netns.execute(
|
||||
cmd,
|
||||
check_exit_code=check_exit_code)
|
||||
|
||||
def ensure_configs(self):
|
||||
"""Generate config files which are needed for OpenSwan.
|
||||
|
||||
If there is no directory, this function will create
|
||||
dirs.
|
||||
"""
|
||||
self.ensure_config_dir(self.vpnservice)
|
||||
self.ensure_config_file(
|
||||
'ipsec.conf',
|
||||
self.conf.openswan.ipsec_config_template,
|
||||
self.vpnservice)
|
||||
self.ensure_config_file(
|
||||
'ipsec.secrets',
|
||||
self.conf.openswan.ipsec_secret_template,
|
||||
self.vpnservice)
|
||||
|
||||
def get_status(self):
|
||||
return self._execute([self.binary,
|
||||
'whack',
|
||||
'--ctlbase',
|
||||
self.pid_path,
|
||||
'--status'])
|
||||
|
||||
def restart(self):
|
||||
"""Restart the process."""
|
||||
self.stop()
|
||||
self.start()
|
||||
return
|
||||
|
||||
def _get_nexthop(self, address):
|
||||
routes = self._execute(
|
||||
['ip', 'route', 'get', address])
|
||||
if routes.find('via') >= 0:
|
||||
return routes.split(' ')[2]
|
||||
return address
|
||||
|
||||
def _virtual_privates(self):
|
||||
"""Returns line of virtual_privates.
|
||||
|
||||
virtual_private contains the networks
|
||||
that are allowed as subnet for the remote client.
|
||||
"""
|
||||
virtual_privates = []
|
||||
nets = [self.vpnservice['subnet']['cidr']]
|
||||
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
|
||||
nets += ipsec_site_conn['peer_cidrs']
|
||||
for net in nets:
|
||||
version = netaddr.IPNetwork(net).version
|
||||
virtual_privates.append('%%v%s:%s' % (version, net))
|
||||
return ','.join(virtual_privates)
|
||||
|
||||
def start(self):
|
||||
"""Start the process.
|
||||
|
||||
Note: if there is not namespace yet,
|
||||
just do nothing, and wait next event.
|
||||
"""
|
||||
if not self.namespace:
|
||||
return
|
||||
virtual_private = self._virtual_privates()
|
||||
#start pluto IKE keying daemon
|
||||
self._execute([self.binary,
|
||||
'pluto',
|
||||
'--ctlbase', self.pid_path,
|
||||
'--ipsecdir', self.etc_dir,
|
||||
'--use-netkey',
|
||||
'--uniqueids',
|
||||
'--nat_traversal',
|
||||
'--secretsfile', self.secrets_file,
|
||||
'--virtual_private', virtual_private
|
||||
])
|
||||
#add connections
|
||||
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
|
||||
nexthop = self._get_nexthop(ipsec_site_conn['peer_address'])
|
||||
self._execute([self.binary,
|
||||
'addconn',
|
||||
'--ctlbase', '%s.ctl' % self.pid_path,
|
||||
'--defaultroutenexthop', nexthop,
|
||||
'--config', self.config_file,
|
||||
ipsec_site_conn['id']
|
||||
])
|
||||
#TODO(nati) fix this when openswan is fixed
|
||||
#Due to openswan bug, this command always exit with 3
|
||||
#start whack ipsec keying daemon
|
||||
self._execute([self.binary,
|
||||
'whack',
|
||||
'--ctlbase', self.pid_path,
|
||||
'--listen',
|
||||
], check_exit_code=False)
|
||||
|
||||
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
|
||||
if not ipsec_site_conn['initiator'] == 'start':
|
||||
continue
|
||||
#initiate ipsec connection
|
||||
self._execute([self.binary,
|
||||
'whack',
|
||||
'--ctlbase', self.pid_path,
|
||||
'--name', ipsec_site_conn['id'],
|
||||
'--asynchronous',
|
||||
'--initiate'
|
||||
])
|
||||
|
||||
def disconnect(self):
|
||||
if not self.namespace:
|
||||
return
|
||||
if not self.vpnservice:
|
||||
return
|
||||
for conn_id in self.connection_status:
|
||||
self._execute([self.binary,
|
||||
'whack',
|
||||
'--ctlbase', self.pid_path,
|
||||
'--name', '%s/0x1' % conn_id,
|
||||
'--terminate'
|
||||
])
|
||||
|
||||
def stop(self):
|
||||
#Stop process using whack
|
||||
#Note this will also stop pluto
|
||||
self.disconnect()
|
||||
self._execute([self.binary,
|
||||
'whack',
|
||||
'--ctlbase', self.pid_path,
|
||||
'--shutdown',
|
||||
])
|
||||
#clean connection_status info
|
||||
self.connection_status = {}
|
||||
|
||||
|
||||
class IPsecVpnDriverApi(object):
|
||||
"""IPSecVpnDriver RPC api."""
|
||||
|
||||
def __init__(self, topic):
|
||||
target = messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def get_vpn_services_on_host(self, context, host):
|
||||
"""Get list of vpnservices.
|
||||
|
||||
The vpnservices including related ipsec_site_connection,
|
||||
ikepolicy and ipsecpolicy on this host
|
||||
"""
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(context, 'get_vpn_services_on_host', host=host)
|
||||
|
||||
def update_status(self, context, status):
|
||||
"""Update local status.
|
||||
|
||||
This method call updates status attribute of
|
||||
VPNServices.
|
||||
"""
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(context, 'update_status', status=status)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class IPsecDriver(device_drivers.DeviceDriver):
|
||||
"""VPN Device Driver for IPSec.
|
||||
|
||||
This class is designed for use with L3-agent now.
|
||||
However this driver will be used with another agent in future.
|
||||
so the use of "Router" is kept minimul now.
|
||||
Instead of router_id, we are using process_id in this code.
|
||||
"""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
target = messaging.Target(version='1.0')
|
||||
|
||||
def __init__(self, agent, host):
|
||||
self.agent = agent
|
||||
self.conf = self.agent.conf
|
||||
self.root_helper = self.agent.root_helper
|
||||
self.host = host
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.context = context.get_admin_context_without_session()
|
||||
self.topic = topics.IPSEC_AGENT_TOPIC
|
||||
node_topic = '%s.%s' % (self.topic, self.host)
|
||||
|
||||
self.processes = {}
|
||||
self.process_status_cache = {}
|
||||
|
||||
self.endpoints = [self]
|
||||
self.conn.create_consumer(node_topic, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
self.agent_rpc = IPsecVpnDriverApi(topics.IPSEC_DRIVER_TOPIC)
|
||||
self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall(
|
||||
self.report_status, self.context)
|
||||
self.process_status_cache_check.start(
|
||||
interval=self.conf.ipsec.ipsec_status_check_interval)
|
||||
|
||||
def _update_nat(self, vpnservice, func):
|
||||
"""Setting up nat rule in iptables.
|
||||
|
||||
We need to setup nat rule for ipsec packet.
|
||||
:param vpnservice: vpnservices
|
||||
:param func: self.add_nat_rule or self.remove_nat_rule
|
||||
"""
|
||||
local_cidr = vpnservice['subnet']['cidr']
|
||||
router_id = vpnservice['router_id']
|
||||
for ipsec_site_connection in vpnservice['ipsec_site_connections']:
|
||||
for peer_cidr in ipsec_site_connection['peer_cidrs']:
|
||||
func(
|
||||
router_id,
|
||||
'POSTROUTING',
|
||||
'-s %s -d %s -m policy '
|
||||
'--dir out --pol ipsec '
|
||||
'-j ACCEPT ' % (local_cidr, peer_cidr),
|
||||
top=True)
|
||||
self.agent.iptables_apply(router_id)
|
||||
|
||||
def vpnservice_updated(self, context, **kwargs):
|
||||
"""Vpnservice updated rpc handler
|
||||
|
||||
VPN Service Driver will call this method
|
||||
when vpnservices updated.
|
||||
Then this method start sync with server.
|
||||
"""
|
||||
self.sync(context, [])
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_process(self, process_id, vpnservice, namespace):
|
||||
pass
|
||||
|
||||
def ensure_process(self, process_id, vpnservice=None):
|
||||
"""Ensuring process.
|
||||
|
||||
If the process doesn't exist, it will create process
|
||||
and store it in self.processs
|
||||
"""
|
||||
process = self.processes.get(process_id)
|
||||
if not process or not process.namespace:
|
||||
namespace = self.agent.get_namespace(process_id)
|
||||
process = self.create_process(
|
||||
process_id,
|
||||
vpnservice,
|
||||
namespace)
|
||||
self.processes[process_id] = process
|
||||
elif vpnservice:
|
||||
process.update_vpnservice(vpnservice)
|
||||
return process
|
||||
|
||||
def create_router(self, process_id):
|
||||
"""Handling create router event.
|
||||
|
||||
Agent calls this method, when the process namespace
|
||||
is ready.
|
||||
"""
|
||||
if process_id in self.processes:
|
||||
# In case of vpnservice is created
|
||||
# before router's namespace
|
||||
process = self.processes[process_id]
|
||||
self._update_nat(process.vpnservice, self.agent.add_nat_rule)
|
||||
process.enable()
|
||||
|
||||
def destroy_router(self, process_id):
|
||||
"""Handling destroy_router event.
|
||||
|
||||
Agent calls this method, when the process namespace
|
||||
is deleted.
|
||||
"""
|
||||
if process_id in self.processes:
|
||||
process = self.processes[process_id]
|
||||
process.disable()
|
||||
vpnservice = process.vpnservice
|
||||
if vpnservice:
|
||||
self._update_nat(vpnservice, self.agent.remove_nat_rule)
|
||||
del self.processes[process_id]
|
||||
|
||||
def get_process_status_cache(self, process):
|
||||
if not self.process_status_cache.get(process.id):
|
||||
self.process_status_cache[process.id] = {
|
||||
'status': None,
|
||||
'id': process.vpnservice['id'],
|
||||
'updated_pending_status': False,
|
||||
'ipsec_site_connections': {}}
|
||||
return self.process_status_cache[process.id]
|
||||
|
||||
def is_status_updated(self, process, previous_status):
|
||||
if process.updated_pending_status:
|
||||
return True
|
||||
if process.status != previous_status['status']:
|
||||
return True
|
||||
if (process.connection_status !=
|
||||
previous_status['ipsec_site_connections']):
|
||||
return True
|
||||
|
||||
def unset_updated_pending_status(self, process):
|
||||
process.updated_pending_status = False
|
||||
for connection_status in process.connection_status.values():
|
||||
connection_status['updated_pending_status'] = False
|
||||
|
||||
def copy_process_status(self, process):
|
||||
return {
|
||||
'id': process.vpnservice['id'],
|
||||
'status': process.status,
|
||||
'updated_pending_status': process.updated_pending_status,
|
||||
'ipsec_site_connections': copy.deepcopy(process.connection_status)
|
||||
}
|
||||
|
||||
def update_downed_connections(self, process_id, new_status):
|
||||
"""Update info to be reported, if connections just went down.
|
||||
|
||||
If there is no longer any information for a connection, because it
|
||||
has been removed (e.g. due to an admin down of VPN service or IPSec
|
||||
connection), but there was previous status information for the
|
||||
connection, mark the connection as down for reporting purposes.
|
||||
"""
|
||||
if process_id in self.process_status_cache:
|
||||
for conn in self.process_status_cache[process_id][IPSEC_CONNS]:
|
||||
if conn not in new_status[IPSEC_CONNS]:
|
||||
new_status[IPSEC_CONNS][conn] = {
|
||||
'status': constants.DOWN,
|
||||
'updated_pending_status': True
|
||||
}
|
||||
|
||||
def report_status(self, context):
|
||||
status_changed_vpn_services = []
|
||||
for process in self.processes.values():
|
||||
previous_status = self.get_process_status_cache(process)
|
||||
if self.is_status_updated(process, previous_status):
|
||||
new_status = self.copy_process_status(process)
|
||||
self.update_downed_connections(process.id, new_status)
|
||||
status_changed_vpn_services.append(new_status)
|
||||
self.process_status_cache[process.id] = (
|
||||
self.copy_process_status(process))
|
||||
# We need unset updated_pending status after it
|
||||
# is reported to the server side
|
||||
self.unset_updated_pending_status(process)
|
||||
|
||||
if status_changed_vpn_services:
|
||||
self.agent_rpc.update_status(
|
||||
context,
|
||||
status_changed_vpn_services)
|
||||
|
||||
@lockutils.synchronized('vpn-agent', 'neutron-')
|
||||
def sync(self, context, routers):
|
||||
"""Sync status with server side.
|
||||
|
||||
:param context: context object for RPC call
|
||||
:param routers: Router objects which is created in this sync event
|
||||
|
||||
There could be many failure cases should be
|
||||
considered including the followings.
|
||||
1) Agent class restarted
|
||||
2) Failure on process creation
|
||||
3) VpnService is deleted during agent down
|
||||
4) RPC failure
|
||||
|
||||
In order to handle, these failure cases,
|
||||
This driver takes simple sync strategies.
|
||||
"""
|
||||
vpnservices = self.agent_rpc.get_vpn_services_on_host(
|
||||
context, self.host)
|
||||
router_ids = [vpnservice['router_id'] for vpnservice in vpnservices]
|
||||
# Ensure the ipsec process is enabled
|
||||
for vpnservice in vpnservices:
|
||||
process = self.ensure_process(vpnservice['router_id'],
|
||||
vpnservice=vpnservice)
|
||||
self._update_nat(vpnservice, self.agent.add_nat_rule)
|
||||
process.update()
|
||||
|
||||
# Delete any IPSec processes that are
|
||||
# associated with routers, but are not running the VPN service.
|
||||
for router in routers:
|
||||
#We are using router id as process_id
|
||||
process_id = router['id']
|
||||
if process_id not in router_ids:
|
||||
process = self.ensure_process(process_id)
|
||||
self.destroy_router(process_id)
|
||||
|
||||
# Delete any IPSec processes running
|
||||
# VPN that do not have an associated router.
|
||||
process_ids = [pid for pid in self.processes if pid not in router_ids]
|
||||
for process_id in process_ids:
|
||||
self.destroy_router(process_id)
|
||||
self.report_status(context)
|
||||
|
||||
|
||||
class OpenSwanDriver(IPsecDriver):
|
||||
def create_process(self, process_id, vpnservice, namespace):
|
||||
return OpenSwanProcess(
|
||||
self.conf,
|
||||
self.root_helper,
|
||||
process_id,
|
||||
vpnservice,
|
||||
namespace)
|
|
@ -1,64 +0,0 @@
|
|||
# Configuration for {{vpnservice.name}}
|
||||
config setup
|
||||
nat_traversal=yes
|
||||
listen={{vpnservice.external_ip}}
|
||||
conn %default
|
||||
ikelifetime=480m
|
||||
keylife=60m
|
||||
keyingtries=%forever
|
||||
{% for ipsec_site_connection in vpnservice.ipsec_site_connections if ipsec_site_connection.admin_state_up
|
||||
%}conn {{ipsec_site_connection.id}}
|
||||
# NOTE: a default route is required for %defaultroute to work...
|
||||
left={{vpnservice.external_ip}}
|
||||
leftid={{vpnservice.external_ip}}
|
||||
auto={{ipsec_site_connection.initiator}}
|
||||
# NOTE:REQUIRED
|
||||
# [subnet]
|
||||
leftsubnet={{vpnservice.subnet.cidr}}
|
||||
# leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
|
||||
leftnexthop=%defaultroute
|
||||
######################
|
||||
# ipsec_site_connections
|
||||
######################
|
||||
# [peer_address]
|
||||
right={{ipsec_site_connection.peer_address}}
|
||||
# [peer_id]
|
||||
rightid={{ipsec_site_connection.peer_id}}
|
||||
# [peer_cidrs]
|
||||
rightsubnets={ {{ipsec_site_connection['peer_cidrs']|join(' ')}} }
|
||||
# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
|
||||
rightnexthop=%defaultroute
|
||||
# [mtu]
|
||||
# Note It looks like not supported in the strongswan driver
|
||||
# ignore it now
|
||||
# [dpd_action]
|
||||
dpdaction={{ipsec_site_connection.dpd_action}}
|
||||
# [dpd_interval]
|
||||
dpddelay={{ipsec_site_connection.dpd_interval}}
|
||||
# [dpd_timeout]
|
||||
dpdtimeout={{ipsec_site_connection.dpd_timeout}}
|
||||
# [auth_mode]
|
||||
authby=secret
|
||||
######################
|
||||
# IKEPolicy params
|
||||
######################
|
||||
#ike version
|
||||
ikev2={{ipsec_site_connection.ikepolicy.ike_version}}
|
||||
# [encryption_algorithm]-[auth_algorithm]-[pfs]
|
||||
ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}};{{ipsec_site_connection.ikepolicy.pfs}}
|
||||
# [lifetime_value]
|
||||
ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}s
|
||||
# NOTE: it looks lifetime_units=kilobytes can't be enforced (could be seconds, hours, days...)
|
||||
##########################
|
||||
# IPsecPolicys params
|
||||
##########################
|
||||
# [transform_protocol]
|
||||
auth={{ipsec_site_connection.ipsecpolicy.transform_protocol}}
|
||||
# [encryption_algorithm]-[auth_algorithm]-[pfs]
|
||||
phase2alg={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}}
|
||||
# [encapsulation_mode]
|
||||
type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}}
|
||||
# [lifetime_value]
|
||||
lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s
|
||||
# lifebytes=100000 if lifetime_units=kilobytes (IKEv2 only)
|
||||
{% endfor %}
|
|
@ -1,3 +0,0 @@
|
|||
# Configuration for {{vpnservice.name}} {% for ipsec_site_connection in vpnservice.ipsec_site_connections %}
|
||||
{{vpnservice.external_ip}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}"
|
||||
{% endfor %}
|
|
@ -1,107 +0,0 @@
|
|||
|
||||
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.db.vpn import vpn_db
|
||||
from neutron.i18n import _LI
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services import service_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VPNPlugin(vpn_db.VPNPluginDb):
|
||||
|
||||
"""Implementation of the VPN Service Plugin.
|
||||
|
||||
This class manages the workflow of VPNaaS request/response.
|
||||
Most DB related works are implemented in class
|
||||
vpn_db.VPNPluginDb.
|
||||
"""
|
||||
supported_extension_aliases = ["vpnaas", "service-type"]
|
||||
|
||||
|
||||
class VPNDriverPlugin(VPNPlugin, vpn_db.VPNPluginRpcDbMixin):
|
||||
"""VpnPlugin which supports VPN Service Drivers."""
|
||||
#TODO(nati) handle ikepolicy and ipsecpolicy update usecase
|
||||
def __init__(self):
|
||||
super(VPNDriverPlugin, self).__init__()
|
||||
# Load the service driver from neutron.conf.
|
||||
drivers, default_provider = service_base.load_drivers(
|
||||
constants.VPN, self)
|
||||
LOG.info(_LI("VPN plugin using service driver: %s"), default_provider)
|
||||
self.ipsec_driver = drivers[default_provider]
|
||||
|
||||
def _get_driver_for_vpnservice(self, vpnservice):
|
||||
return self.ipsec_driver
|
||||
|
||||
def _get_driver_for_ipsec_site_connection(self, context,
|
||||
ipsec_site_connection):
|
||||
#TODO(nati) get vpnservice when we support service type framework
|
||||
vpnservice = None
|
||||
return self._get_driver_for_vpnservice(vpnservice)
|
||||
|
||||
def _get_validator(self):
|
||||
return self.ipsec_driver.validator
|
||||
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
ipsec_site_connection = super(
|
||||
VPNDriverPlugin, self).create_ipsec_site_connection(
|
||||
context, ipsec_site_connection)
|
||||
driver = self._get_driver_for_ipsec_site_connection(
|
||||
context, ipsec_site_connection)
|
||||
driver.create_ipsec_site_connection(context, ipsec_site_connection)
|
||||
return ipsec_site_connection
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_conn_id):
|
||||
ipsec_site_connection = self.get_ipsec_site_connection(
|
||||
context, ipsec_conn_id)
|
||||
super(VPNDriverPlugin, self).delete_ipsec_site_connection(
|
||||
context, ipsec_conn_id)
|
||||
driver = self._get_driver_for_ipsec_site_connection(
|
||||
context, ipsec_site_connection)
|
||||
driver.delete_ipsec_site_connection(context, ipsec_site_connection)
|
||||
|
||||
def update_ipsec_site_connection(
|
||||
self, context,
|
||||
ipsec_conn_id, ipsec_site_connection):
|
||||
old_ipsec_site_connection = self.get_ipsec_site_connection(
|
||||
context, ipsec_conn_id)
|
||||
ipsec_site_connection = super(
|
||||
VPNDriverPlugin, self).update_ipsec_site_connection(
|
||||
context,
|
||||
ipsec_conn_id,
|
||||
ipsec_site_connection)
|
||||
driver = self._get_driver_for_ipsec_site_connection(
|
||||
context, ipsec_site_connection)
|
||||
driver.update_ipsec_site_connection(
|
||||
context, old_ipsec_site_connection, ipsec_site_connection)
|
||||
return ipsec_site_connection
|
||||
|
||||
def update_vpnservice(self, context, vpnservice_id, vpnservice):
|
||||
old_vpn_service = self.get_vpnservice(context, vpnservice_id)
|
||||
new_vpn_service = super(
|
||||
VPNDriverPlugin, self).update_vpnservice(context, vpnservice_id,
|
||||
vpnservice)
|
||||
driver = self._get_driver_for_vpnservice(old_vpn_service)
|
||||
driver.update_vpnservice(context, old_vpn_service, new_vpn_service)
|
||||
return new_vpn_service
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice_id):
|
||||
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
||||
super(VPNDriverPlugin, self).delete_vpnservice(context, vpnservice_id)
|
||||
driver = self._get_driver_for_vpnservice(vpnservice)
|
||||
driver.delete_vpnservice(context, vpnservice)
|
|
@ -1,111 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
from oslo import messaging
|
||||
import six
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.db.vpn import vpn_validator
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class VpnDriver(object):
|
||||
|
||||
def __init__(self, service_plugin, validator=None):
|
||||
self.service_plugin = service_plugin
|
||||
if validator is None:
|
||||
validator = vpn_validator.VpnReferenceValidator()
|
||||
self.validator = validator
|
||||
|
||||
@property
|
||||
def l3_plugin(self):
|
||||
return manager.NeutronManager.get_service_plugins().get(
|
||||
constants.L3_ROUTER_NAT)
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_vpnservice(
|
||||
self, context, old_vpnservice, vpnservice):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_ipsec_site_connection(self, context, old_ipsec_site_connection,
|
||||
ipsec_site_connection):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
pass
|
||||
|
||||
|
||||
class BaseIPsecVpnAgentApi(object):
|
||||
"""Base class for IPSec API to agent."""
|
||||
|
||||
def __init__(self, topic, default_version, driver):
|
||||
self.topic = topic
|
||||
self.driver = driver
|
||||
target = messaging.Target(topic=topic, version=default_version)
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def _agent_notification(self, context, method, router_id,
|
||||
version=None, **kwargs):
|
||||
"""Notify update for the agent.
|
||||
|
||||
This method will find where is the router, and
|
||||
dispatch notification for the agent.
|
||||
"""
|
||||
admin_context = context if context.is_admin else context.elevated()
|
||||
if not version:
|
||||
version = self.target.version
|
||||
l3_agents = self.driver.l3_plugin.get_l3_agents_hosting_routers(
|
||||
admin_context, [router_id],
|
||||
admin_state_up=True,
|
||||
active=True)
|
||||
for l3_agent in l3_agents:
|
||||
LOG.debug('Notify agent at %(topic)s.%(host)s the message '
|
||||
'%(method)s %(args)s',
|
||||
{'topic': self.topic,
|
||||
'host': l3_agent.host,
|
||||
'method': method,
|
||||
'args': kwargs})
|
||||
cctxt = self.client.prepare(server=l3_agent.host, version=version)
|
||||
cctxt.cast(context, method, **kwargs)
|
||||
|
||||
def vpnservice_updated(self, context, router_id, **kwargs):
|
||||
"""Send update event of vpnservices."""
|
||||
self._agent_notification(context, 'vpnservice_updated', router_id,
|
||||
**kwargs)
|
|
@ -1,231 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# 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 oslo import messaging
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.db.vpn import vpn_db
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.cisco.l3.plugging_drivers import (
|
||||
n1kv_plugging_constants as n1kv_constants)
|
||||
from neutron.services.vpn.common import topics
|
||||
from neutron.services.vpn import service_drivers
|
||||
from neutron.services.vpn.service_drivers import cisco_csr_db as csr_id_map
|
||||
from neutron.services.vpn.service_drivers import cisco_validator
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
IPSEC = 'ipsec'
|
||||
BASE_IPSEC_VERSION = '1.0'
|
||||
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
|
||||
'IPSec Policy': {'min': 120, 'max': 2592000}}
|
||||
MIN_CSR_MTU = 1500
|
||||
MAX_CSR_MTU = 9192
|
||||
VRF_SUFFIX_LEN = 6
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnDriverCallBack(object):
|
||||
|
||||
"""Handler for agent to plugin RPC messaging."""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
|
||||
target = messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, driver):
|
||||
super(CiscoCsrIPsecVpnDriverCallBack, self).__init__()
|
||||
self.driver = driver
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
return n_rpc.PluginRpcDispatcher([self])
|
||||
|
||||
def get_vpn_services_using(self, context, router_id):
|
||||
query = context.session.query(vpn_db.VPNService)
|
||||
query = query.join(vpn_db.IPsecSiteConnection)
|
||||
query = query.join(vpn_db.IKEPolicy)
|
||||
query = query.join(vpn_db.IPsecPolicy)
|
||||
query = query.join(vpn_db.IPsecPeerCidr)
|
||||
query = query.filter(vpn_db.VPNService.router_id == router_id)
|
||||
return query.all()
|
||||
|
||||
def get_vpn_services_on_host(self, context, host=None):
|
||||
"""Returns info on the VPN services on the host."""
|
||||
routers = self.driver.l3_plugin.get_active_routers_for_host(context,
|
||||
host)
|
||||
host_vpn_services = []
|
||||
for router in routers:
|
||||
vpn_services = self.get_vpn_services_using(context, router['id'])
|
||||
for vpn_service in vpn_services:
|
||||
host_vpn_services.append(
|
||||
self.driver._make_vpnservice_dict(context, vpn_service,
|
||||
router))
|
||||
return host_vpn_services
|
||||
|
||||
def update_status(self, context, status):
|
||||
"""Update status of all vpnservices."""
|
||||
plugin = self.driver.service_plugin
|
||||
plugin.update_status_by_agent(context, status)
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi):
|
||||
|
||||
"""API and handler for Cisco IPSec plugin to agent RPC messaging."""
|
||||
|
||||
target = messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, topic, default_version, driver):
|
||||
super(CiscoCsrIPsecVpnAgentApi, self).__init__(
|
||||
topic, default_version, driver)
|
||||
|
||||
def _agent_notification(self, context, method, router_id,
|
||||
version=None, **kwargs):
|
||||
"""Notify update for the agent.
|
||||
|
||||
Find the host for the router being notified and then
|
||||
dispatches a notification for the VPN device driver.
|
||||
"""
|
||||
admin_context = context if context.is_admin else context.elevated()
|
||||
if not version:
|
||||
version = self.target.version
|
||||
host = self.driver.l3_plugin.get_host_for_router(admin_context,
|
||||
router_id)
|
||||
LOG.debug('Notify agent at %(topic)s.%(host)s the message '
|
||||
'%(method)s %(args)s for router %(router)s',
|
||||
{'topic': self.topic,
|
||||
'host': host,
|
||||
'method': method,
|
||||
'args': kwargs,
|
||||
'router': router_id})
|
||||
cctxt = self.client.prepare(server=host, version=version)
|
||||
cctxt.cast(context, method, **kwargs)
|
||||
|
||||
|
||||
class CiscoCsrIPsecVPNDriver(service_drivers.VpnDriver):
|
||||
|
||||
"""Cisco CSR VPN Service Driver class for IPsec."""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(CiscoCsrIPsecVPNDriver, self).__init__(
|
||||
service_plugin,
|
||||
cisco_validator.CiscoCsrVpnValidator(service_plugin))
|
||||
self.endpoints = [CiscoCsrIPsecVpnDriverCallBack(self)]
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
topics.CISCO_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
self.agent_rpc = CiscoCsrIPsecVpnAgentApi(
|
||||
topics.CISCO_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self)
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return IPSEC
|
||||
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
csr_id_map.create_tunnel_mapping(context, ipsec_site_connection)
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
|
||||
reason='ipsec-conn-create')
|
||||
|
||||
def update_ipsec_site_connection(
|
||||
self, context, old_ipsec_site_connection, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(
|
||||
context, vpnservice['router_id'],
|
||||
reason='ipsec-conn-update')
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
|
||||
reason='ipsec-conn-delete')
|
||||
|
||||
def create_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def delete_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def update_ikepolicy(self, context, old_ikepolicy, ikepolicy):
|
||||
pass
|
||||
|
||||
def create_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def delete_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def update_ipsecpolicy(self, context, old_ipsec_policy, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
def update_vpnservice(self, context, old_vpnservice, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
|
||||
reason='vpn-service-update')
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
|
||||
reason='vpn-service-delete')
|
||||
|
||||
def get_cisco_connection_mappings(self, conn_id, context):
|
||||
"""Obtain persisted mappings for IDs related to connection."""
|
||||
tunnel_id, ike_id, ipsec_id = csr_id_map.get_tunnel_mapping_for(
|
||||
conn_id, context.session)
|
||||
return {'site_conn_id': u'Tunnel%d' % tunnel_id,
|
||||
'ike_policy_id': u'%d' % ike_id,
|
||||
'ipsec_policy_id': u'%s' % ipsec_id}
|
||||
|
||||
def _create_interface(self, interface_info):
|
||||
hosting_info = interface_info['hosting_info']
|
||||
vlan = hosting_info['segmentation_id']
|
||||
# Port name "currently" is t{1,2}_p:1, as only one router per CSR,
|
||||
# but will keep a semi-generic algorithm
|
||||
port_name = hosting_info['hosting_port_name']
|
||||
name, sep, num = port_name.partition(':')
|
||||
offset = 1 if name in n1kv_constants.T2_PORT_NAME else 0
|
||||
if_num = int(num) * 2 + offset
|
||||
return 'GigabitEthernet%d.%d' % (if_num, vlan)
|
||||
|
||||
def _get_router_info(self, router_info):
|
||||
hosting_device = router_info['hosting_device']
|
||||
return {'rest_mgmt_ip': hosting_device['management_ip_address'],
|
||||
'username': hosting_device['credentials']['username'],
|
||||
'password': hosting_device['credentials']['password'],
|
||||
'inner_if_name': self._create_interface(
|
||||
router_info['_interfaces'][0]),
|
||||
'outer_if_name': self._create_interface(
|
||||
router_info['gw_port']),
|
||||
'vrf': 'nrouter-' + router_info['id'][:VRF_SUFFIX_LEN],
|
||||
'timeout': 30} # Hard-coded for now
|
||||
|
||||
def _make_vpnservice_dict(self, context, vpnservice, router_info):
|
||||
"""Collect all service info, including Cisco info for IPSec conn."""
|
||||
vpnservice_dict = dict(vpnservice)
|
||||
vpnservice_dict['ipsec_conns'] = []
|
||||
vpnservice_dict['subnet'] = dict(vpnservice.subnet)
|
||||
vpnservice_dict['router_info'] = self._get_router_info(router_info)
|
||||
for ipsec_conn in vpnservice.ipsec_site_connections:
|
||||
ipsec_conn_dict = dict(ipsec_conn)
|
||||
ipsec_conn_dict['ike_policy'] = dict(ipsec_conn.ikepolicy)
|
||||
ipsec_conn_dict['ipsec_policy'] = dict(ipsec_conn.ipsecpolicy)
|
||||
ipsec_conn_dict['peer_cidrs'] = [
|
||||
peer_cidr.cidr for peer_cidr in ipsec_conn.peer_cidrs]
|
||||
ipsec_conn_dict['cisco'] = self.get_cisco_connection_mappings(
|
||||
ipsec_conn['id'], context)
|
||||
vpnservice_dict['ipsec_conns'].append(ipsec_conn_dict)
|
||||
return vpnservice_dict
|
|
@ -1,114 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# 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 netaddr
|
||||
from netaddr import core as net_exc
|
||||
|
||||
from neutron.common import exceptions
|
||||
from neutron.db.vpn import vpn_validator
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
|
||||
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
|
||||
'IPSec Policy': {'min': 120, 'max': 2592000}}
|
||||
MIN_CSR_MTU = 1500
|
||||
MAX_CSR_MTU = 9192
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CsrValidationFailure(exceptions.BadRequest):
|
||||
message = _("Cisco CSR does not support %(resource)s attribute %(key)s "
|
||||
"with value '%(value)s'")
|
||||
|
||||
|
||||
class CiscoCsrVpnValidator(vpn_validator.VpnReferenceValidator):
|
||||
|
||||
"""Validator methods for the Cisco CSR."""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
self.service_plugin = service_plugin
|
||||
super(CiscoCsrVpnValidator, self).__init__()
|
||||
|
||||
def validate_lifetime(self, for_policy, policy_info):
|
||||
"""Ensure lifetime in secs and value is supported, based on policy."""
|
||||
units = policy_info['lifetime']['units']
|
||||
if units != 'seconds':
|
||||
raise CsrValidationFailure(resource=for_policy,
|
||||
key='lifetime:units',
|
||||
value=units)
|
||||
value = policy_info['lifetime']['value']
|
||||
if (value < LIFETIME_LIMITS[for_policy]['min'] or
|
||||
value > LIFETIME_LIMITS[for_policy]['max']):
|
||||
raise CsrValidationFailure(resource=for_policy,
|
||||
key='lifetime:value',
|
||||
value=value)
|
||||
|
||||
def validate_ike_version(self, policy_info):
|
||||
"""Ensure IKE policy is v1 for current REST API."""
|
||||
version = policy_info['ike_version']
|
||||
if version != 'v1':
|
||||
raise CsrValidationFailure(resource='IKE Policy',
|
||||
key='ike_version',
|
||||
value=version)
|
||||
|
||||
def validate_mtu(self, conn_info):
|
||||
"""Ensure the MTU value is supported."""
|
||||
mtu = conn_info['mtu']
|
||||
if mtu < MIN_CSR_MTU or mtu > MAX_CSR_MTU:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='mtu',
|
||||
value=mtu)
|
||||
|
||||
def validate_public_ip_present(self, router):
|
||||
"""Ensure there is one gateway IP specified for the router used."""
|
||||
gw_port = router.gw_port
|
||||
if not gw_port or len(gw_port.fixed_ips) != 1:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='router:gw_port:ip_address',
|
||||
value='missing')
|
||||
|
||||
def validate_peer_id(self, ipsec_conn):
|
||||
"""Ensure that an IP address is specified for peer ID."""
|
||||
# TODO(pcm) Should we check peer_address too?
|
||||
peer_id = ipsec_conn['peer_id']
|
||||
try:
|
||||
netaddr.IPAddress(peer_id)
|
||||
except net_exc.AddrFormatError:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='peer_id', value=peer_id)
|
||||
|
||||
def validate_ipsec_site_connection(self, context, ipsec_sitecon,
|
||||
ip_version):
|
||||
"""Validate IPSec site connection for Cisco CSR.
|
||||
|
||||
After doing reference validation, do additional checks that relate
|
||||
to the Cisco CSR.
|
||||
"""
|
||||
super(CiscoCsrVpnValidator, self)._check_dpd(ipsec_sitecon)
|
||||
|
||||
ike_policy = self.service_plugin.get_ikepolicy(
|
||||
context, ipsec_sitecon['ikepolicy_id'])
|
||||
ipsec_policy = self.service_plugin.get_ipsecpolicy(
|
||||
context, ipsec_sitecon['ipsecpolicy_id'])
|
||||
vpn_service = self.service_plugin.get_vpnservice(
|
||||
context, ipsec_sitecon['vpnservice_id'])
|
||||
router = self.l3_plugin._get_router(context, vpn_service['router_id'])
|
||||
self.validate_lifetime('IKE Policy', ike_policy)
|
||||
self.validate_lifetime('IPSec Policy', ipsec_policy)
|
||||
self.validate_ike_version(ike_policy)
|
||||
self.validate_mtu(ipsec_sitecon)
|
||||
self.validate_public_ip_present(router)
|
||||
self.validate_peer_id(ipsec_sitecon)
|
||||
LOG.debug("IPSec connection validated for Cisco CSR")
|
|
@ -1,155 +0,0 @@
|
|||
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 netaddr
|
||||
|
||||
from oslo import messaging
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.vpn.common import topics
|
||||
from neutron.services.vpn import service_drivers
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
IPSEC = 'ipsec'
|
||||
BASE_IPSEC_VERSION = '1.0'
|
||||
|
||||
|
||||
class IPsecVpnDriverCallBack(object):
|
||||
"""Callback for IPSecVpnDriver rpc."""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
|
||||
target = messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, driver):
|
||||
super(IPsecVpnDriverCallBack, self).__init__()
|
||||
self.driver = driver
|
||||
|
||||
def get_vpn_services_on_host(self, context, host=None):
|
||||
"""Returns the vpnservices on the host."""
|
||||
plugin = self.driver.service_plugin
|
||||
vpnservices = plugin._get_agent_hosting_vpn_services(
|
||||
context, host)
|
||||
return [self.driver._make_vpnservice_dict(vpnservice)
|
||||
for vpnservice in vpnservices]
|
||||
|
||||
def update_status(self, context, status):
|
||||
"""Update status of vpnservices."""
|
||||
plugin = self.driver.service_plugin
|
||||
plugin.update_status_by_agent(context, status)
|
||||
|
||||
|
||||
class IPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi):
|
||||
"""Agent RPC API for IPsecVPNAgent."""
|
||||
|
||||
target = messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, topic, default_version, driver):
|
||||
super(IPsecVpnAgentApi, self).__init__(
|
||||
topic, default_version, driver)
|
||||
|
||||
|
||||
class IPsecVPNDriver(service_drivers.VpnDriver):
|
||||
"""VPN Service Driver class for IPsec."""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(IPsecVPNDriver, self).__init__(service_plugin)
|
||||
self.endpoints = [IPsecVpnDriverCallBack(self)]
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
topics.IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
self.agent_rpc = IPsecVpnAgentApi(
|
||||
topics.IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self)
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return IPSEC
|
||||
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def update_ipsec_site_connection(
|
||||
self, context, old_ipsec_site_connection, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def create_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def delete_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def update_ikepolicy(self, context, old_ikepolicy, ikepolicy):
|
||||
pass
|
||||
|
||||
def create_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def delete_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def update_ipsecpolicy(self, context, old_ipsec_policy, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
def update_vpnservice(self, context, old_vpnservice, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def _make_vpnservice_dict(self, vpnservice):
|
||||
"""Convert vpnservice information for vpn agent.
|
||||
|
||||
also converting parameter name for vpn agent driver
|
||||
"""
|
||||
vpnservice_dict = dict(vpnservice)
|
||||
vpnservice_dict['ipsec_site_connections'] = []
|
||||
vpnservice_dict['subnet'] = dict(
|
||||
vpnservice.subnet)
|
||||
vpnservice_dict['external_ip'] = vpnservice.router.gw_port[
|
||||
'fixed_ips'][0]['ip_address']
|
||||
for ipsec_site_connection in vpnservice.ipsec_site_connections:
|
||||
ipsec_site_connection_dict = dict(ipsec_site_connection)
|
||||
try:
|
||||
netaddr.IPAddress(ipsec_site_connection['peer_id'])
|
||||
except netaddr.core.AddrFormatError:
|
||||
ipsec_site_connection['peer_id'] = (
|
||||
'@' + ipsec_site_connection['peer_id'])
|
||||
ipsec_site_connection_dict['ikepolicy'] = dict(
|
||||
ipsec_site_connection.ikepolicy)
|
||||
ipsec_site_connection_dict['ipsecpolicy'] = dict(
|
||||
ipsec_site_connection.ipsecpolicy)
|
||||
vpnservice_dict['ipsec_site_connections'].append(
|
||||
ipsec_site_connection_dict)
|
||||
peer_cidrs = [
|
||||
peer_cidr.cidr
|
||||
for peer_cidr in ipsec_site_connection.peer_cidrs]
|
||||
ipsec_site_connection_dict['peer_cidrs'] = peer_cidrs
|
||||
return vpnservice_dict
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,393 +0,0 @@
|
|||
# Copyright (c) 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# 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 contextlib
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config as agent_config
|
||||
from neutron.agent import l3_agent
|
||||
from neutron.agent import l3_ha_agent
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import config as base_config
|
||||
from neutron import context
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.firewall.agents import firewall_agent_api
|
||||
from neutron.services.firewall.agents.l3reference import firewall_l3_agent
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit.services.firewall.agents import test_firewall_agent_api
|
||||
|
||||
|
||||
class FWaasHelper(object):
|
||||
def __init__(self, host):
|
||||
pass
|
||||
|
||||
|
||||
class FWaasAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, FWaasHelper):
|
||||
neutron_service_plugins = []
|
||||
|
||||
|
||||
def _setup_test_agent_class(service_plugins):
|
||||
class FWaasTestAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
|
||||
FWaasHelper):
|
||||
neutron_service_plugins = service_plugins
|
||||
|
||||
return FWaasTestAgent
|
||||
|
||||
|
||||
class TestFwaasL3AgentRpcCallback(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFwaasL3AgentRpcCallback, self).setUp()
|
||||
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.conf.register_opts(base_config.core_opts)
|
||||
self.conf.register_opts(l3_agent.L3NATAgent.OPTS)
|
||||
self.conf.register_opts(l3_ha_agent.OPTS)
|
||||
agent_config.register_use_namespaces_opts_helper(self.conf)
|
||||
agent_config.register_root_helper(self.conf)
|
||||
self.conf.root_helper = 'sudo'
|
||||
self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas')
|
||||
self.api = FWaasAgent(self.conf)
|
||||
self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriver()
|
||||
|
||||
def test_fw_config_match(self):
|
||||
test_agent_class = _setup_test_agent_class([constants.FIREWALL])
|
||||
cfg.CONF.set_override('enabled', True, 'fwaas')
|
||||
with mock.patch('oslo.utils.importutils.import_object'):
|
||||
test_agent_class(cfg.CONF)
|
||||
|
||||
def test_fw_config_mismatch_plugin_enabled_agent_disabled(self):
|
||||
test_agent_class = _setup_test_agent_class([constants.FIREWALL])
|
||||
cfg.CONF.set_override('enabled', False, 'fwaas')
|
||||
self.assertRaises(SystemExit, test_agent_class, cfg.CONF)
|
||||
|
||||
def test_fw_plugin_list_unavailable(self):
|
||||
test_agent_class = _setup_test_agent_class(None)
|
||||
cfg.CONF.set_override('enabled', False, 'fwaas')
|
||||
with mock.patch('oslo.utils.importutils.import_object'):
|
||||
test_agent_class(cfg.CONF)
|
||||
|
||||
def test_create_firewall(self):
|
||||
fake_firewall = {'id': 0}
|
||||
with mock.patch.object(
|
||||
self.api,
|
||||
'_invoke_driver_for_plugin_api'
|
||||
) as mock_driver:
|
||||
self.assertEqual(
|
||||
self.api.create_firewall(
|
||||
mock.sentinel.context,
|
||||
fake_firewall,
|
||||
'host'),
|
||||
mock_driver.return_value)
|
||||
|
||||
def test_update_firewall(self):
|
||||
fake_firewall = {'id': 0}
|
||||
with mock.patch.object(
|
||||
self.api,
|
||||
'_invoke_driver_for_plugin_api'
|
||||
) as mock_driver:
|
||||
self.assertEqual(
|
||||
self.api.update_firewall(
|
||||
mock.sentinel.context,
|
||||
fake_firewall,
|
||||
'host'),
|
||||
mock_driver.return_value)
|
||||
|
||||
def test_delete_firewall(self):
|
||||
fake_firewall = {'id': 0}
|
||||
with mock.patch.object(
|
||||
self.api,
|
||||
'_invoke_driver_for_plugin_api'
|
||||
) as mock_driver:
|
||||
self.assertEqual(
|
||||
self.api.delete_firewall(
|
||||
mock.sentinel.context,
|
||||
fake_firewall,
|
||||
'host'),
|
||||
mock_driver.return_value)
|
||||
|
||||
def test_invoke_driver_for_plugin_api(self):
|
||||
fake_firewall = {'id': 0, 'tenant_id': 1,
|
||||
'admin_state_up': True}
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwaas_driver, 'create_firewall'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'set_firewall_status')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_driver_create_firewall,
|
||||
mock_set_firewall_status):
|
||||
|
||||
mock_driver_create_firewall.return_value = True
|
||||
self.api.create_firewall(
|
||||
context=mock.sentinel.context,
|
||||
firewall=fake_firewall, host='host')
|
||||
|
||||
mock_get_routers.assert_called_once_with(
|
||||
mock.sentinel.context)
|
||||
|
||||
mock_get_router_info_list_for_tenant.assert_called_once_with(
|
||||
mock_get_routers.return_value, fake_firewall['tenant_id'])
|
||||
|
||||
mock_set_firewall_status.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
fake_firewall['id'],
|
||||
'ACTIVE')
|
||||
|
||||
def test_invoke_driver_for_plugin_api_admin_state_down(self):
|
||||
fake_firewall = {'id': 0, 'tenant_id': 1,
|
||||
'admin_state_up': False}
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwaas_driver, 'update_firewall'),
|
||||
mock.patch.object(self.api.fwplugin_rpc,
|
||||
'get_firewalls_for_tenant'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'set_firewall_status')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_driver_update_firewall,
|
||||
mock_get_firewalls_for_tenant,
|
||||
mock_set_firewall_status):
|
||||
|
||||
mock_driver_update_firewall.return_value = True
|
||||
self.api.update_firewall(
|
||||
context=mock.sentinel.context,
|
||||
firewall=fake_firewall, host='host')
|
||||
|
||||
mock_get_routers.assert_called_once_with(
|
||||
mock.sentinel.context)
|
||||
|
||||
mock_get_router_info_list_for_tenant.assert_called_once_with(
|
||||
mock_get_routers.return_value, fake_firewall['tenant_id'])
|
||||
|
||||
mock_set_firewall_status.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
fake_firewall['id'],
|
||||
'DOWN')
|
||||
|
||||
def test_invoke_driver_for_plugin_api_delete(self):
|
||||
fake_firewall = {'id': 0, 'tenant_id': 1,
|
||||
'admin_state_up': True}
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwaas_driver, 'delete_firewall'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'firewall_deleted')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_driver_delete_firewall,
|
||||
mock_firewall_deleted):
|
||||
|
||||
mock_driver_delete_firewall.return_value = True
|
||||
self.api.delete_firewall(
|
||||
context=mock.sentinel.context,
|
||||
firewall=fake_firewall, host='host')
|
||||
|
||||
mock_get_routers.assert_called_once_with(
|
||||
mock.sentinel.context)
|
||||
|
||||
mock_get_router_info_list_for_tenant.assert_called_once_with(
|
||||
mock_get_routers.return_value, fake_firewall['tenant_id'])
|
||||
|
||||
mock_firewall_deleted.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
fake_firewall['id'])
|
||||
|
||||
def test_delete_firewall_no_router(self):
|
||||
fake_firewall = {'id': 0, 'tenant_id': 1}
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'firewall_deleted')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_firewall_deleted):
|
||||
|
||||
mock_get_router_info_list_for_tenant.return_value = []
|
||||
self.api.delete_firewall(
|
||||
context=mock.sentinel.context,
|
||||
firewall=fake_firewall, host='host')
|
||||
|
||||
mock_get_routers.assert_called_once_with(
|
||||
mock.sentinel.context)
|
||||
|
||||
mock_get_router_info_list_for_tenant.assert_called_once_with(
|
||||
mock_get_routers.return_value, fake_firewall['tenant_id'])
|
||||
|
||||
mock_firewall_deleted.assert_called_once_with(
|
||||
mock.sentinel.context,
|
||||
fake_firewall['id'])
|
||||
|
||||
def test_process_router_add_fw_update(self):
|
||||
fake_firewall_list = [{'id': 0, 'tenant_id': 1,
|
||||
'status': constants.PENDING_UPDATE,
|
||||
'admin_state_up': True}]
|
||||
fake_router = {'id': 1111, 'tenant_id': 2}
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
agent_mode = 'legacy'
|
||||
ri = mock.Mock()
|
||||
ri.router = fake_router
|
||||
routers = [ri.router]
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwaas_driver, 'update_firewall'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'set_firewall_status'),
|
||||
mock.patch.object(self.api.fwplugin_rpc,
|
||||
'get_firewalls_for_tenant'),
|
||||
mock.patch.object(context, 'Context')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_driver_update_firewall,
|
||||
mock_set_firewall_status,
|
||||
mock_get_firewalls_for_tenant,
|
||||
mock_Context):
|
||||
|
||||
mock_driver_update_firewall.return_value = True
|
||||
ctx = mock.sentinel.context
|
||||
mock_Context.return_value = ctx
|
||||
mock_get_router_info_list_for_tenant.return_value = routers
|
||||
mock_get_firewalls_for_tenant.return_value = fake_firewall_list
|
||||
|
||||
self.api._process_router_add(ri)
|
||||
mock_get_router_info_list_for_tenant.assert_called_with(
|
||||
routers,
|
||||
ri.router['tenant_id'])
|
||||
mock_get_firewalls_for_tenant.assert_called_once_with(ctx)
|
||||
mock_driver_update_firewall.assert_called_once_with(
|
||||
agent_mode,
|
||||
routers,
|
||||
fake_firewall_list[0])
|
||||
|
||||
mock_set_firewall_status.assert_called_once_with(
|
||||
ctx,
|
||||
fake_firewall_list[0]['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_process_router_add_fw_delete(self):
|
||||
fake_firewall_list = [{'id': 0, 'tenant_id': 1,
|
||||
'status': constants.PENDING_DELETE}]
|
||||
fake_router = {'id': 1111, 'tenant_id': 2}
|
||||
agent_mode = 'legacy'
|
||||
self.api.plugin_rpc = mock.Mock()
|
||||
ri = mock.Mock()
|
||||
ri.router = fake_router
|
||||
routers = [ri.router]
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.plugin_rpc, 'get_routers'),
|
||||
mock.patch.object(self.api, '_get_router_info_list_for_tenant'),
|
||||
mock.patch.object(self.api.fwaas_driver, 'delete_firewall'),
|
||||
mock.patch.object(self.api.fwplugin_rpc, 'firewall_deleted'),
|
||||
mock.patch.object(self.api.fwplugin_rpc,
|
||||
'get_firewalls_for_tenant'),
|
||||
mock.patch.object(context, 'Context')
|
||||
) as (
|
||||
mock_get_routers,
|
||||
mock_get_router_info_list_for_tenant,
|
||||
mock_driver_delete_firewall,
|
||||
mock_firewall_deleted,
|
||||
mock_get_firewalls_for_tenant,
|
||||
mock_Context):
|
||||
|
||||
mock_driver_delete_firewall.return_value = True
|
||||
ctx = mock.sentinel.context
|
||||
mock_Context.return_value = ctx
|
||||
mock_get_router_info_list_for_tenant.return_value = routers
|
||||
mock_get_firewalls_for_tenant.return_value = fake_firewall_list
|
||||
|
||||
self.api._process_router_add(ri)
|
||||
mock_get_router_info_list_for_tenant.assert_called_with(
|
||||
routers,
|
||||
ri.router['tenant_id'])
|
||||
mock_get_firewalls_for_tenant.assert_called_once_with(ctx)
|
||||
mock_driver_delete_firewall.assert_called_once_with(
|
||||
agent_mode,
|
||||
routers,
|
||||
fake_firewall_list[0])
|
||||
|
||||
mock_firewall_deleted.assert_called_once_with(
|
||||
ctx,
|
||||
fake_firewall_list[0]['id'])
|
||||
|
||||
def _prepare_router_data(self):
|
||||
router = {'id': str(uuid.uuid4()), 'tenant_id': str(uuid.uuid4())}
|
||||
ns = "ns-" + router['id']
|
||||
return l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
||||
router=router, ns_name=ns)
|
||||
|
||||
def _get_router_info_list_helper(self, use_namespaces):
|
||||
self.conf.set_override('use_namespaces', use_namespaces)
|
||||
ri = self._prepare_router_data()
|
||||
routers = [ri.router]
|
||||
self.api.router_info = {ri.router_id: ri}
|
||||
with mock.patch.object(ip_lib.IPWrapper,
|
||||
'get_namespaces') as mock_get_namespaces:
|
||||
mock_get_namespaces.return_value = []
|
||||
router_info_list = self.api._get_router_info_list_for_tenant(
|
||||
routers,
|
||||
ri.router['tenant_id'])
|
||||
if use_namespaces:
|
||||
mock_get_namespaces.assert_called_once_with(self.conf.root_helper)
|
||||
self.assertFalse(router_info_list)
|
||||
else:
|
||||
self.assertEqual([ri], router_info_list)
|
||||
|
||||
def test_get_router_info_list_for_tenant_for_namespaces_disabled(self):
|
||||
self._get_router_info_list_helper(use_namespaces=False)
|
||||
|
||||
def test_get_router_info_list_for_tenant(self):
|
||||
self._get_router_info_list_helper(use_namespaces=True)
|
||||
|
||||
def _get_router_info_list_router_without_router_info_helper(self,
|
||||
rtr_with_ri):
|
||||
self.conf.set_override('use_namespaces', True)
|
||||
# ri.router with associated router_info (ri)
|
||||
# rtr2 has no router_info
|
||||
ri = self._prepare_router_data()
|
||||
rtr2 = {'id': str(uuid.uuid4()), 'tenant_id': ri.router['tenant_id']}
|
||||
routers = [rtr2]
|
||||
self.api.router_info = {}
|
||||
ri_expected = []
|
||||
if rtr_with_ri:
|
||||
self.api.router_info[ri.router_id] = ri
|
||||
routers.append(ri.router)
|
||||
ri_expected.append(ri)
|
||||
with mock.patch.object(ip_lib.IPWrapper,
|
||||
'get_namespaces') as mock_get_namespaces:
|
||||
mock_get_namespaces.return_value = ri.ns_name
|
||||
router_info_list = self.api._get_router_info_list_for_tenant(
|
||||
routers,
|
||||
ri.router['tenant_id'])
|
||||
self.assertEqual(ri_expected, router_info_list)
|
||||
|
||||
def test_get_router_info_list_router_without_router_info(self):
|
||||
self._get_router_info_list_router_without_router_info_helper(
|
||||
rtr_with_ri=False)
|
||||
|
||||
def test_get_router_info_list_two_routers_one_without_router_info(self):
|
||||
self._get_router_info_list_router_without_router_info_helper(
|
||||
rtr_with_ri=True)
|
|
@ -1,77 +0,0 @@
|
|||
# Copyright (c) 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 contextlib
|
||||
import mock
|
||||
|
||||
from neutron.services.firewall.agents import firewall_agent_api as api
|
||||
from neutron.services.firewall.drivers import fwaas_base as base_driver
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class NoopFwaasDriver(base_driver.FwaasDriverBase):
|
||||
"""Noop Fwaas Driver.
|
||||
|
||||
Firewall driver which does nothing.
|
||||
This driver is for disabling Fwaas functionality.
|
||||
"""
|
||||
|
||||
def create_firewall(self, apply_list, firewall):
|
||||
pass
|
||||
|
||||
def delete_firewall(self, apply_list, firewall):
|
||||
pass
|
||||
|
||||
def update_firewall(self, apply_list, firewall):
|
||||
pass
|
||||
|
||||
def apply_default_policy(self, apply_list, firewall):
|
||||
pass
|
||||
|
||||
|
||||
class TestFWaaSAgentApi(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFWaaSAgentApi, self).setUp()
|
||||
|
||||
self.api = api.FWaaSPluginApiMixin(
|
||||
'topic',
|
||||
'host')
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.api.host, 'host')
|
||||
|
||||
def _test_firewall_method(self, method_name, **kwargs):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.client, 'call'),
|
||||
mock.patch.object(self.api.client, 'prepare'),
|
||||
) as (
|
||||
rpc_mock, prepare_mock
|
||||
):
|
||||
prepare_mock.return_value = self.api.client
|
||||
getattr(self.api, method_name)(mock.sentinel.context, 'test',
|
||||
**kwargs)
|
||||
|
||||
prepare_args = {}
|
||||
prepare_mock.assert_called_once_with(**prepare_args)
|
||||
|
||||
rpc_mock.assert_called_once_with(mock.sentinel.context, method_name,
|
||||
firewall_id='test', host='host',
|
||||
**kwargs)
|
||||
|
||||
def test_set_firewall_status(self):
|
||||
self._test_firewall_method('set_firewall_status', status='fake_status')
|
||||
|
||||
def test_firewall_deleted(self):
|
||||
self._test_firewall_method('firewall_deleted')
|
|
@ -1,324 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
from neutron.agent.common import config as agent_config
|
||||
from neutron.agent import l3_agent
|
||||
from neutron.agent import l3_ha_agent
|
||||
from neutron.agent.linux import interface
|
||||
from neutron.common import config as base_config
|
||||
from neutron.common import constants as l3_constants
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.services.firewall.agents.varmour import varmour_router
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils
|
||||
from neutron.tests import base
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
HOSTNAME = 'myhost'
|
||||
FAKE_DIRECTOR = '1.1.1.1'
|
||||
|
||||
|
||||
class TestVarmourRouter(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVarmourRouter, self).setUp()
|
||||
self.conf = agent_config.setup_conf()
|
||||
self.conf.register_opts(base_config.core_opts)
|
||||
self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS)
|
||||
self.conf.register_opts(l3_ha_agent.OPTS)
|
||||
agent_config.register_interface_driver_opts_helper(self.conf)
|
||||
agent_config.register_use_namespaces_opts_helper(self.conf)
|
||||
agent_config.register_root_helper(self.conf)
|
||||
self.conf.register_opts(interface.OPTS)
|
||||
self.conf.set_override('interface_driver',
|
||||
'neutron.agent.linux.interface.NullDriver')
|
||||
self.conf.root_helper = 'sudo'
|
||||
self.conf.state_path = ''
|
||||
|
||||
self.device_exists_p = mock.patch(
|
||||
'neutron.agent.linux.ip_lib.device_exists')
|
||||
self.device_exists = self.device_exists_p.start()
|
||||
|
||||
self.utils_exec_p = mock.patch(
|
||||
'neutron.agent.linux.utils.execute')
|
||||
self.utils_exec = self.utils_exec_p.start()
|
||||
|
||||
self.external_process_p = mock.patch(
|
||||
'neutron.agent.linux.external_process.ProcessManager')
|
||||
self.external_process = self.external_process_p.start()
|
||||
|
||||
self.makedirs_p = mock.patch('os.makedirs')
|
||||
self.makedirs = self.makedirs_p.start()
|
||||
|
||||
self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
|
||||
driver_cls = self.dvr_cls_p.start()
|
||||
self.mock_driver = mock.MagicMock()
|
||||
self.mock_driver.DEV_NAME_LEN = (
|
||||
interface.LinuxInterfaceDriver.DEV_NAME_LEN)
|
||||
driver_cls.return_value = self.mock_driver
|
||||
|
||||
self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
|
||||
ip_cls = self.ip_cls_p.start()
|
||||
self.mock_ip = mock.MagicMock()
|
||||
ip_cls.return_value = self.mock_ip
|
||||
|
||||
mock.patch('neutron.agent.l3_agent.L3PluginApi').start()
|
||||
|
||||
self.looping_call_p = mock.patch(
|
||||
'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
|
||||
self.looping_call_p.start()
|
||||
|
||||
def _create_router(self):
|
||||
router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf)
|
||||
router.rest.server = FAKE_DIRECTOR
|
||||
router.rest.user = 'varmour'
|
||||
router.rest.passwd = 'varmour'
|
||||
return router
|
||||
|
||||
def _del_all_internal_ports(self, router):
|
||||
router[l3_constants.INTERFACE_KEY] = []
|
||||
|
||||
def _del_internal_ports(self, router, port_idx):
|
||||
del router[l3_constants.INTERFACE_KEY][port_idx]
|
||||
|
||||
def _add_internal_ports(self, router, port_count=1):
|
||||
self._del_all_internal_ports(router)
|
||||
for i in range(port_count):
|
||||
port = {'id': _uuid(),
|
||||
'network_id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'fixed_ips': [{'ip_address': '10.0.%s.4' % i,
|
||||
'subnet_id': _uuid()}],
|
||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||
'subnet': {'cidr': '10.0.%s.0/24' % i,
|
||||
'gateway_ip': '10.0.%s.1' % i}}
|
||||
router[l3_constants.INTERFACE_KEY].append(port)
|
||||
|
||||
def _del_all_floating_ips(self, router):
|
||||
router[l3_constants.FLOATINGIP_KEY] = []
|
||||
|
||||
def _del_floating_ips(self, router, port_idx):
|
||||
del router[l3_constants.FLOATINGIP_KEY][port_idx]
|
||||
|
||||
def _add_floating_ips(self, router, port_count=1):
|
||||
self._del_all_floating_ips(router)
|
||||
for i in range(port_count):
|
||||
fip = {'id': _uuid(),
|
||||
'port_id': router['gw_port']['id'],
|
||||
'floating_ip_address': '172.24.4.%s' % (100 + i),
|
||||
'fixed_ip_address': '10.0.0.%s' % (100 + i)}
|
||||
router[l3_constants.FLOATINGIP_KEY].append(fip)
|
||||
|
||||
def _prepare_router_data(self, enable_snat=None):
|
||||
router_id = _uuid()
|
||||
ex_gw_port = {'id': _uuid(),
|
||||
'network_id': _uuid(),
|
||||
'fixed_ips': [{'ip_address': '172.24.4.2',
|
||||
'subnet_id': _uuid()}],
|
||||
'subnet': {'cidr': '172.24.4.0/24',
|
||||
'gateway_ip': '172.24.4.1'},
|
||||
'ip_cidr': '172.24.4.226/28'}
|
||||
int_ports = []
|
||||
|
||||
router = {
|
||||
'id': router_id,
|
||||
l3_constants.INTERFACE_KEY: int_ports,
|
||||
'routes': [],
|
||||
'gw_port': ex_gw_port}
|
||||
if enable_snat is not None:
|
||||
router['enable_snat'] = enable_snat
|
||||
|
||||
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
||||
router=router)
|
||||
return ri
|
||||
|
||||
def test_agent_add_internal_network(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_NAT_RULE
|
||||
prefix = varmour_utils.get_snat_rule_name(ri)
|
||||
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
def test_agent_remove_internal_network(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_NAT_RULE
|
||||
prefix = varmour_utils.get_snat_rule_name(ri)
|
||||
|
||||
self._add_internal_ports(ri.router, port_count=2)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 2, 'prefix %s' % prefix)
|
||||
|
||||
self._del_internal_ports(ri.router, 0)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
self._del_all_internal_ports(ri.router)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
def test_agent_add_floating_ips(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_NAT_RULE
|
||||
prefix = varmour_utils.get_dnat_rule_name(ri)
|
||||
|
||||
self._add_floating_ips(ri.router, port_count=1)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
self._add_floating_ips(ri.router, port_count=2)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 2, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
def test_agent_remove_floating_ips(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
self._add_floating_ips(ri.router, port_count=2)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_NAT_RULE
|
||||
prefix = varmour_utils.get_dnat_rule_name(ri)
|
||||
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 2, 'prefix %s' % prefix)
|
||||
|
||||
self._del_floating_ips(ri.router, 0)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
self._del_all_floating_ips(ri.router)
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
def test_agent_external_gateway(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_ZONE
|
||||
prefix = varmour_utils.get_untrusted_zone_name(ri)
|
||||
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
del ri.router['gw_port']
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 1, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
def test_agent_snat_enable(self):
|
||||
router = self._create_router()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_NAT_RULE
|
||||
prefix = varmour_utils.get_snat_rule_name(ri)
|
||||
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
ri.router['enable_snat'] = False
|
||||
router.process_router(ri)
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
n = router.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0, 'prefix %s' % prefix)
|
|
@ -1,250 +0,0 @@
|
|||
# Copyright 2013 Dell Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config as a_cfg
|
||||
import neutron.services.firewall.drivers.linux.iptables_fwaas as fwaas
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit import test_api_v2
|
||||
|
||||
|
||||
_uuid = test_api_v2._uuid
|
||||
FAKE_SRC_PREFIX = '10.0.0.0/24'
|
||||
FAKE_DST_PREFIX = '20.0.0.0/24'
|
||||
FAKE_PROTOCOL = 'tcp'
|
||||
FAKE_SRC_PORT = 5000
|
||||
FAKE_DST_PORT = 22
|
||||
FAKE_FW_ID = 'fake-fw-uuid'
|
||||
|
||||
|
||||
class IptablesFwaasTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(IptablesFwaasTestCase, self).setUp()
|
||||
cfg.CONF.register_opts(a_cfg.ROOT_HELPER_OPTS, 'AGENT')
|
||||
self.utils_exec_p = mock.patch(
|
||||
'neutron.agent.linux.utils.execute')
|
||||
self.utils_exec = self.utils_exec_p.start()
|
||||
self.iptables_cls_p = mock.patch(
|
||||
'neutron.agent.linux.iptables_manager.IptablesManager')
|
||||
self.iptables_cls_p.start()
|
||||
self.firewall = fwaas.IptablesFwaasDriver()
|
||||
|
||||
def _fake_rules_v4(self, fwid, apply_list):
|
||||
rule_list = []
|
||||
rule1 = {'enabled': True,
|
||||
'action': 'allow',
|
||||
'ip_version': 4,
|
||||
'protocol': 'tcp',
|
||||
'destination_port': '80',
|
||||
'source_ip_address': '10.24.4.2'}
|
||||
rule2 = {'enabled': True,
|
||||
'action': 'deny',
|
||||
'ip_version': 4,
|
||||
'protocol': 'tcp',
|
||||
'destination_port': '22'}
|
||||
ingress_chain = ('iv4%s' % fwid)[:11]
|
||||
egress_chain = ('ov4%s' % fwid)[:11]
|
||||
for router_info_inst in apply_list:
|
||||
v4filter_inst = router_info_inst.iptables_manager.ipv4['filter']
|
||||
v4filter_inst.chains.append(ingress_chain)
|
||||
v4filter_inst.chains.append(egress_chain)
|
||||
rule_list.append(rule1)
|
||||
rule_list.append(rule2)
|
||||
return rule_list
|
||||
|
||||
def _fake_firewall_no_rule(self):
|
||||
rule_list = []
|
||||
fw_inst = {'id': FAKE_FW_ID,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'tenant-uuid',
|
||||
'firewall_rule_list': rule_list}
|
||||
return fw_inst
|
||||
|
||||
def _fake_firewall(self, rule_list):
|
||||
fw_inst = {'id': FAKE_FW_ID,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'tenant-uuid',
|
||||
'firewall_rule_list': rule_list}
|
||||
return fw_inst
|
||||
|
||||
def _fake_firewall_with_admin_down(self, rule_list):
|
||||
fw_inst = {'id': FAKE_FW_ID,
|
||||
'admin_state_up': False,
|
||||
'tenant_id': 'tenant-uuid',
|
||||
'firewall_rule_list': rule_list}
|
||||
return fw_inst
|
||||
|
||||
def _fake_apply_list(self, router_count=1, distributed=False,
|
||||
distributed_mode=None):
|
||||
apply_list = []
|
||||
while router_count > 0:
|
||||
iptables_inst = mock.Mock()
|
||||
router_inst = {'distributed': distributed}
|
||||
v4filter_inst = mock.Mock()
|
||||
v6filter_inst = mock.Mock()
|
||||
v4filter_inst.chains = []
|
||||
v6filter_inst.chains = []
|
||||
iptables_inst.ipv4 = {'filter': v4filter_inst}
|
||||
iptables_inst.ipv6 = {'filter': v6filter_inst}
|
||||
router_info_inst = mock.Mock()
|
||||
router_info_inst.iptables_manager = iptables_inst
|
||||
router_info_inst.snat_iptables_manager = iptables_inst
|
||||
if distributed_mode == 'dvr':
|
||||
router_info_inst.dist_fip_count = 1
|
||||
router_info_inst.router = router_inst
|
||||
apply_list.append(router_info_inst)
|
||||
router_count -= 1
|
||||
return apply_list
|
||||
|
||||
def _setup_firewall_with_rules(self, func, router_count=1,
|
||||
distributed=False, distributed_mode=None):
|
||||
apply_list = self._fake_apply_list(router_count=router_count,
|
||||
distributed=distributed, distributed_mode=distributed_mode)
|
||||
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
|
||||
firewall = self._fake_firewall(rule_list)
|
||||
if distributed:
|
||||
if distributed_mode == 'dvr_snat':
|
||||
if_prefix = 'sg-+'
|
||||
if distributed_mode == 'dvr':
|
||||
if_prefix = 'rfp-+'
|
||||
else:
|
||||
if_prefix = 'qr-+'
|
||||
distributed_mode = 'legacy'
|
||||
func(distributed_mode, apply_list, firewall)
|
||||
invalid_rule = '-m state --state INVALID -j DROP'
|
||||
est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
|
||||
rule1 = '-p tcp --dport 80 -s 10.24.4.2 -j ACCEPT'
|
||||
rule2 = '-p tcp --dport 22 -j DROP'
|
||||
ingress_chain = 'iv4%s' % firewall['id']
|
||||
egress_chain = 'ov4%s' % firewall['id']
|
||||
bname = fwaas.iptables_manager.binary_name
|
||||
ipt_mgr_ichain = '%s-%s' % (bname, ingress_chain[:11])
|
||||
ipt_mgr_echain = '%s-%s' % (bname, egress_chain[:11])
|
||||
for router_info_inst in apply_list:
|
||||
v4filter_inst = router_info_inst.iptables_manager.ipv4['filter']
|
||||
calls = [mock.call.remove_chain('iv4fake-fw-uuid'),
|
||||
mock.call.remove_chain('ov4fake-fw-uuid'),
|
||||
mock.call.remove_chain('fwaas-default-policy'),
|
||||
mock.call.add_chain('fwaas-default-policy'),
|
||||
mock.call.add_rule('fwaas-default-policy', '-j DROP'),
|
||||
mock.call.add_chain(ingress_chain),
|
||||
mock.call.add_rule(ingress_chain, invalid_rule),
|
||||
mock.call.add_rule(ingress_chain, est_rule),
|
||||
mock.call.add_chain(egress_chain),
|
||||
mock.call.add_rule(egress_chain, invalid_rule),
|
||||
mock.call.add_rule(egress_chain, est_rule),
|
||||
mock.call.add_rule(ingress_chain, rule1),
|
||||
mock.call.add_rule(egress_chain, rule1),
|
||||
mock.call.add_rule(ingress_chain, rule2),
|
||||
mock.call.add_rule(egress_chain, rule2),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-o %s -j %s' % (if_prefix,
|
||||
ipt_mgr_ichain)),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-i %s -j %s' % (if_prefix,
|
||||
ipt_mgr_echain)),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-o %s -j %s-fwaas-defau' % (if_prefix,
|
||||
bname)),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-i %s -j %s-fwaas-defau' % (if_prefix,
|
||||
bname))]
|
||||
v4filter_inst.assert_has_calls(calls)
|
||||
|
||||
def test_create_firewall_no_rules(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
firewall = self._fake_firewall_no_rule()
|
||||
self.firewall.create_firewall('legacy', apply_list, firewall)
|
||||
invalid_rule = '-m state --state INVALID -j DROP'
|
||||
est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
|
||||
bname = fwaas.iptables_manager.binary_name
|
||||
|
||||
for ip_version in (4, 6):
|
||||
ingress_chain = ('iv%s%s' % (ip_version, firewall['id']))
|
||||
egress_chain = ('ov%s%s' % (ip_version, firewall['id']))
|
||||
calls = [mock.call.remove_chain(
|
||||
'iv%sfake-fw-uuid' % ip_version),
|
||||
mock.call.remove_chain(
|
||||
'ov%sfake-fw-uuid' % ip_version),
|
||||
mock.call.remove_chain('fwaas-default-policy'),
|
||||
mock.call.add_chain('fwaas-default-policy'),
|
||||
mock.call.add_rule('fwaas-default-policy', '-j DROP'),
|
||||
mock.call.add_chain(ingress_chain),
|
||||
mock.call.add_rule(ingress_chain, invalid_rule),
|
||||
mock.call.add_rule(ingress_chain, est_rule),
|
||||
mock.call.add_chain(egress_chain),
|
||||
mock.call.add_rule(egress_chain, invalid_rule),
|
||||
mock.call.add_rule(egress_chain, est_rule),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-o qr-+ -j %s-fwaas-defau' % bname),
|
||||
mock.call.add_rule('FORWARD',
|
||||
'-i qr-+ -j %s-fwaas-defau' % bname)]
|
||||
if ip_version == 4:
|
||||
v4filter_inst = apply_list[0].iptables_manager.ipv4['filter']
|
||||
v4filter_inst.assert_has_calls(calls)
|
||||
else:
|
||||
v6filter_inst = apply_list[0].iptables_manager.ipv6['filter']
|
||||
v6filter_inst.assert_has_calls(calls)
|
||||
|
||||
def test_create_firewall_with_rules(self):
|
||||
self._setup_firewall_with_rules(self.firewall.create_firewall)
|
||||
|
||||
def test_create_firewall_with_rules_two_routers(self):
|
||||
self._setup_firewall_with_rules(self.firewall.create_firewall,
|
||||
router_count=2)
|
||||
|
||||
def test_update_firewall_with_rules(self):
|
||||
self._setup_firewall_with_rules(self.firewall.update_firewall)
|
||||
|
||||
def test_delete_firewall(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
firewall = self._fake_firewall_no_rule()
|
||||
self.firewall.delete_firewall('legacy', apply_list, firewall)
|
||||
ingress_chain = 'iv4%s' % firewall['id']
|
||||
egress_chain = 'ov4%s' % firewall['id']
|
||||
calls = [mock.call.remove_chain(ingress_chain),
|
||||
mock.call.remove_chain(egress_chain),
|
||||
mock.call.remove_chain('fwaas-default-policy')]
|
||||
apply_list[0].iptables_manager.ipv4['filter'].assert_has_calls(calls)
|
||||
|
||||
def test_create_firewall_with_admin_down(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
|
||||
firewall = self._fake_firewall_with_admin_down(rule_list)
|
||||
self.firewall.create_firewall('legacy', apply_list, firewall)
|
||||
calls = [mock.call.remove_chain('iv4fake-fw-uuid'),
|
||||
mock.call.remove_chain('ov4fake-fw-uuid'),
|
||||
mock.call.remove_chain('fwaas-default-policy'),
|
||||
mock.call.add_chain('fwaas-default-policy'),
|
||||
mock.call.add_rule('fwaas-default-policy', '-j DROP')]
|
||||
apply_list[0].iptables_manager.ipv4['filter'].assert_has_calls(calls)
|
||||
|
||||
def test_create_firewall_with_rules_dvr_snat(self):
|
||||
self._setup_firewall_with_rules(self.firewall.create_firewall,
|
||||
distributed=True, distributed_mode='dvr_snat')
|
||||
|
||||
def test_update_firewall_with_rules_dvr_snat(self):
|
||||
self._setup_firewall_with_rules(self.firewall.update_firewall,
|
||||
distributed=True, distributed_mode='dvr_snat')
|
||||
|
||||
def test_create_firewall_with_rules_dvr(self):
|
||||
self._setup_firewall_with_rules(self.firewall.create_firewall,
|
||||
distributed=True, distributed_mode='dvr')
|
||||
|
||||
def test_update_firewall_with_rules_dvr(self):
|
||||
self._setup_firewall_with_rules(self.firewall.update_firewall,
|
||||
distributed=True, distributed_mode='dvr')
|
|
@ -1,292 +0,0 @@
|
|||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
from neutron.agent.common import config as agent_config
|
||||
from neutron.agent import l3_agent
|
||||
from neutron.agent import l3_ha_agent
|
||||
from neutron.agent.linux import interface
|
||||
from neutron.common import config as base_config
|
||||
from neutron.common import constants as l3_constants
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.services.firewall.agents.varmour import varmour_router
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils
|
||||
from neutron.services.firewall.drivers.varmour import varmour_fwaas
|
||||
from neutron.tests import base
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
HOSTNAME = 'myhost'
|
||||
FAKE_DIRECTOR = '1.1.1.1'
|
||||
|
||||
|
||||
class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBasicRouterOperations, self).setUp()
|
||||
self.conf = agent_config.setup_conf()
|
||||
self.conf.register_opts(base_config.core_opts)
|
||||
self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS)
|
||||
self.conf.register_opts(l3_ha_agent.OPTS)
|
||||
agent_config.register_interface_driver_opts_helper(self.conf)
|
||||
agent_config.register_use_namespaces_opts_helper(self.conf)
|
||||
agent_config.register_root_helper(self.conf)
|
||||
self.conf.register_opts(interface.OPTS)
|
||||
self.conf.set_override('interface_driver',
|
||||
'neutron.agent.linux.interface.NullDriver')
|
||||
self.conf.root_helper = 'sudo'
|
||||
self.conf.state_path = ''
|
||||
|
||||
self.device_exists_p = mock.patch(
|
||||
'neutron.agent.linux.ip_lib.device_exists')
|
||||
self.device_exists = self.device_exists_p.start()
|
||||
|
||||
self.utils_exec_p = mock.patch(
|
||||
'neutron.agent.linux.utils.execute')
|
||||
self.utils_exec = self.utils_exec_p.start()
|
||||
|
||||
self.external_process_p = mock.patch(
|
||||
'neutron.agent.linux.external_process.ProcessManager')
|
||||
self.external_process = self.external_process_p.start()
|
||||
|
||||
self.makedirs_p = mock.patch('os.makedirs')
|
||||
self.makedirs = self.makedirs_p.start()
|
||||
|
||||
self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
|
||||
driver_cls = self.dvr_cls_p.start()
|
||||
self.mock_driver = mock.MagicMock()
|
||||
self.mock_driver.DEV_NAME_LEN = (
|
||||
interface.LinuxInterfaceDriver.DEV_NAME_LEN)
|
||||
driver_cls.return_value = self.mock_driver
|
||||
|
||||
self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
|
||||
ip_cls = self.ip_cls_p.start()
|
||||
self.mock_ip = mock.MagicMock()
|
||||
ip_cls.return_value = self.mock_ip
|
||||
|
||||
mock.patch('neutron.agent.l3_agent.L3PluginApi').start()
|
||||
|
||||
self.looping_call_p = mock.patch(
|
||||
'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
|
||||
self.looping_call_p.start()
|
||||
|
||||
def _create_router(self):
|
||||
router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf)
|
||||
router.rest.server = FAKE_DIRECTOR
|
||||
router.rest.user = 'varmour'
|
||||
router.rest.passwd = 'varmour'
|
||||
return router
|
||||
|
||||
def _create_fwaas(self):
|
||||
fwaas = varmour_fwaas.vArmourFwaasDriver()
|
||||
fwaas.rest.server = FAKE_DIRECTOR
|
||||
fwaas.rest.user = 'varmour'
|
||||
fwaas.rest.passwd = 'varmour'
|
||||
return fwaas
|
||||
|
||||
def _del_all_internal_ports(self, router):
|
||||
router[l3_constants.INTERFACE_KEY] = []
|
||||
|
||||
def _del_internal_ports(self, router, port_idx):
|
||||
del router[l3_constants.INTERFACE_KEY][port_idx]
|
||||
|
||||
def _add_internal_ports(self, router, port_count=1):
|
||||
self._del_all_internal_ports(router)
|
||||
for i in range(port_count):
|
||||
port = {'id': _uuid(),
|
||||
'network_id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'fixed_ips': [{'ip_address': '10.0.%s.4' % i,
|
||||
'subnet_id': _uuid()}],
|
||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||
'subnet': {'cidr': '10.0.%s.0/24' % i,
|
||||
'gateway_ip': '10.0.%s.1' % i}}
|
||||
router[l3_constants.INTERFACE_KEY].append(port)
|
||||
|
||||
def _del_all_floating_ips(self, router):
|
||||
router[l3_constants.FLOATINGIP_KEY] = []
|
||||
|
||||
def _del_floating_ips(self, router, port_idx):
|
||||
del router[l3_constants.FLOATINGIP_KEY][port_idx]
|
||||
|
||||
def _add_floating_ips(self, router, port_count=1):
|
||||
self._del_all_floating_ips(router)
|
||||
for i in range(port_count):
|
||||
fip = {'id': _uuid(),
|
||||
'port_id': router['gw_port']['id'],
|
||||
'floating_ip_address': '172.24.4.%s' % (100 + i),
|
||||
'fixed_ip_address': '10.0.0.%s' % (100 + i)}
|
||||
router[l3_constants.FLOATINGIP_KEY].append(fip)
|
||||
|
||||
def _prepare_router_data(self, enable_snat=None):
|
||||
router_id = _uuid()
|
||||
ex_gw_port = {'id': _uuid(),
|
||||
'network_id': _uuid(),
|
||||
'fixed_ips': [{'ip_address': '172.24.4.2',
|
||||
'subnet_id': _uuid()}],
|
||||
'subnet': {'cidr': '172.24.4.0/24',
|
||||
'gateway_ip': '172.24.4.1'},
|
||||
'ip_cidr': '172.24.4.226/28'}
|
||||
int_ports = []
|
||||
|
||||
router = {
|
||||
'id': router_id,
|
||||
l3_constants.INTERFACE_KEY: int_ports,
|
||||
'routes': [],
|
||||
'gw_port': ex_gw_port}
|
||||
if enable_snat is not None:
|
||||
router['enable_snat'] = enable_snat
|
||||
|
||||
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
||||
router=router)
|
||||
return ri
|
||||
|
||||
def _add_firewall_rules(self, fw, rule_count=1):
|
||||
rules = []
|
||||
for i in range(rule_count):
|
||||
rule = {'id': _uuid(),
|
||||
'enabled': True,
|
||||
'action': 'deny' if (i % 2 == 0) else 'allow',
|
||||
'ip_version': 4,
|
||||
'protocol': 'tcp',
|
||||
'source_ip_address': '10.0.0.%s/24' % (100 + i),
|
||||
'destination_port': '%s' % (100 + i)}
|
||||
rules.append(rule)
|
||||
fw['firewall_rule_list'] = rules
|
||||
|
||||
def _prepare_firewall_data(self):
|
||||
fw = {'id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'firewall_rule_list': []}
|
||||
return fw
|
||||
|
||||
def test_firewall_without_rule(self):
|
||||
router = self._create_router()
|
||||
fwaas = self._create_fwaas()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
self._add_floating_ips(ri.router, port_count=1)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
rl = [ri]
|
||||
|
||||
fw = self._prepare_firewall_data()
|
||||
fwaas.create_firewall(rl, fw)
|
||||
|
||||
url = varmour_utils.REST_URL_CONF_POLICY
|
||||
prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
|
||||
|
||||
n = fwaas.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0)
|
||||
|
||||
fwaas.delete_firewall(rl, fw)
|
||||
n = fwaas.rest.count_cfg_objs(url, prefix)
|
||||
self.assertEqual(n, 0)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
|
||||
def test_firewall_with_rules(self):
|
||||
router = self._create_router()
|
||||
fwaas = self._create_fwaas()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
self._add_floating_ips(ri.router, port_count=1)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
rl = [ri]
|
||||
|
||||
fw = self._prepare_firewall_data()
|
||||
self._add_firewall_rules(fw, 2)
|
||||
fwaas.create_firewall(rl, fw)
|
||||
|
||||
prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
|
||||
pol_url = varmour_utils.REST_URL_CONF_POLICY
|
||||
serv_url = varmour_utils.REST_URL_CONF_SERVICE
|
||||
addr_url = varmour_utils.REST_URL_CONF_ADDR
|
||||
|
||||
# 3x number of policies
|
||||
n = fwaas.rest.count_cfg_objs(pol_url, prefix)
|
||||
self.assertEqual(n, 6)
|
||||
n = fwaas.rest.count_cfg_objs(addr_url, prefix)
|
||||
self.assertEqual(n, 2)
|
||||
n = fwaas.rest.count_cfg_objs(serv_url, prefix)
|
||||
self.assertEqual(n, 2)
|
||||
|
||||
fwaas.delete_firewall(rl, fw)
|
||||
n = fwaas.rest.count_cfg_objs(pol_url, prefix)
|
||||
self.assertEqual(n, 0)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
||||
|
||||
def test_firewall_add_remove_rules(self):
|
||||
router = self._create_router()
|
||||
fwaas = self._create_fwaas()
|
||||
try:
|
||||
router.rest.auth()
|
||||
except Exception:
|
||||
# skip the test, firewall is not deployed
|
||||
return
|
||||
|
||||
ri = self._prepare_router_data(enable_snat=True)
|
||||
self._add_internal_ports(ri.router, port_count=1)
|
||||
self._add_floating_ips(ri.router, port_count=1)
|
||||
router._router_added(ri.router['id'], ri.router)
|
||||
|
||||
rl = [ri]
|
||||
|
||||
fw = self._prepare_firewall_data()
|
||||
self._add_firewall_rules(fw, 2)
|
||||
fwaas.create_firewall(rl, fw)
|
||||
|
||||
prefix = varmour_utils.get_firewall_object_prefix(ri, fw)
|
||||
pol_url = varmour_utils.REST_URL_CONF_POLICY
|
||||
serv_url = varmour_utils.REST_URL_CONF_SERVICE
|
||||
addr_url = varmour_utils.REST_URL_CONF_ADDR
|
||||
|
||||
# 3x number of policies
|
||||
n = fwaas.rest.count_cfg_objs(pol_url, prefix)
|
||||
self.assertEqual(n, 6)
|
||||
n = fwaas.rest.count_cfg_objs(addr_url, prefix)
|
||||
self.assertEqual(n, 2)
|
||||
n = fwaas.rest.count_cfg_objs(serv_url, prefix)
|
||||
self.assertEqual(n, 2)
|
||||
|
||||
self._add_firewall_rules(fw, 1)
|
||||
fwaas.create_firewall(rl, fw)
|
||||
n = fwaas.rest.count_cfg_objs(pol_url, prefix)
|
||||
self.assertEqual(n, 3)
|
||||
n = fwaas.rest.count_cfg_objs(addr_url, prefix)
|
||||
self.assertEqual(n, 1)
|
||||
n = fwaas.rest.count_cfg_objs(serv_url, prefix)
|
||||
self.assertEqual(n, 1)
|
||||
|
||||
fwaas.delete_firewall(rl, fw)
|
||||
n = fwaas.rest.count_cfg_objs(pol_url, prefix)
|
||||
self.assertEqual(n, 0)
|
||||
|
||||
router._router_removed(ri.router['id'])
|
|
@ -1,403 +0,0 @@
|
|||
# Copyright 2013 Big Switch Networks, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
from webob import exc
|
||||
|
||||
from neutron import context
|
||||
from neutron.extensions import firewall
|
||||
from neutron.plugins.common import constants as const
|
||||
from neutron.services.firewall import fwaas_plugin
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit.db.firewall import test_db_firewall
|
||||
|
||||
|
||||
FW_PLUGIN_KLASS = (
|
||||
"neutron.services.firewall.fwaas_plugin.FirewallPlugin"
|
||||
)
|
||||
|
||||
|
||||
class TestFirewallCallbacks(test_db_firewall.FirewallPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestFirewallCallbacks,
|
||||
self).setUp(fw_plugin=FW_PLUGIN_KLASS)
|
||||
self.callbacks = self.plugin.endpoints[0]
|
||||
|
||||
def test_set_firewall_status(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE,
|
||||
host='dummy')
|
||||
fw_db = self.plugin.get_firewall(ctx, fw_id)
|
||||
self.assertEqual(fw_db['status'], const.ACTIVE)
|
||||
self.assertTrue(res)
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ERROR)
|
||||
fw_db = self.plugin.get_firewall(ctx, fw_id)
|
||||
self.assertEqual(fw_db['status'], const.ERROR)
|
||||
self.assertFalse(res)
|
||||
|
||||
def test_set_firewall_status_pending_delete(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.PENDING_DELETE
|
||||
ctx.session.flush()
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE,
|
||||
host='dummy')
|
||||
fw_db = self.plugin.get_firewall(ctx, fw_id)
|
||||
self.assertEqual(fw_db['status'], const.PENDING_DELETE)
|
||||
self.assertFalse(res)
|
||||
|
||||
def test_firewall_deleted(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP,
|
||||
do_delete=False) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.PENDING_DELETE
|
||||
ctx.session.flush()
|
||||
res = self.callbacks.firewall_deleted(ctx, fw_id,
|
||||
host='dummy')
|
||||
self.assertTrue(res)
|
||||
self.assertRaises(firewall.FirewallNotFound,
|
||||
self.plugin.get_firewall,
|
||||
ctx, fw_id)
|
||||
|
||||
def test_firewall_deleted_error(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP,
|
||||
) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
res = self.callbacks.firewall_deleted(ctx, fw_id,
|
||||
host='dummy')
|
||||
self.assertFalse(res)
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
self.assertEqual(fw_db['status'], const.ERROR)
|
||||
|
||||
def test_get_firewall_for_tenant(self):
|
||||
tenant_id = 'test-tenant'
|
||||
ctx = context.Context('', tenant_id)
|
||||
with contextlib.nested(self.firewall_rule(name='fwr1',
|
||||
tenant_id=tenant_id),
|
||||
self.firewall_rule(name='fwr2',
|
||||
tenant_id=tenant_id),
|
||||
self.firewall_rule(name='fwr3',
|
||||
tenant_id=tenant_id)
|
||||
) as fr:
|
||||
with self.firewall_policy(tenant_id=tenant_id) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
fw_rule_ids = [r['firewall_rule']['id'] for r in fr]
|
||||
data = {'firewall_policy':
|
||||
{'firewall_rules': fw_rule_ids}}
|
||||
req = self.new_update_request('firewall_policies', data,
|
||||
fwp_id)
|
||||
res = req.get_response(self.ext_api)
|
||||
attrs = self._get_test_firewall_attrs()
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
tenant_id=tenant_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
res = self.callbacks.get_firewalls_for_tenant(ctx,
|
||||
host='dummy')
|
||||
fw_rules = (
|
||||
self.plugin._make_firewall_dict_with_rules(ctx,
|
||||
fw_id)
|
||||
)
|
||||
self.assertEqual(res[0], fw_rules)
|
||||
self._compare_firewall_rule_lists(
|
||||
fwp_id, fr, res[0]['firewall_rule_list'])
|
||||
|
||||
def test_get_firewall_for_tenant_without_rules(self):
|
||||
tenant_id = 'test-tenant'
|
||||
ctx = context.Context('', tenant_id)
|
||||
with self.firewall_policy(tenant_id=tenant_id) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
attrs = self._get_test_firewall_attrs()
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(firewall_policy_id=fwp_id, tenant_id=tenant_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as fw:
|
||||
fw_list = [fw['firewall']]
|
||||
f = self.callbacks.get_firewalls_for_tenant_without_rules
|
||||
res = f(ctx, host='dummy')
|
||||
for fw in res:
|
||||
del fw['shared']
|
||||
self.assertEqual(res, fw_list)
|
||||
|
||||
|
||||
class TestFirewallAgentApi(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFirewallAgentApi, self).setUp()
|
||||
|
||||
self.api = fwaas_plugin.FirewallAgentApi('topic', 'host')
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.api.client.target.topic, 'topic')
|
||||
self.assertEqual(self.api.host, 'host')
|
||||
|
||||
def _call_test_helper(self, method_name):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.client, 'cast'),
|
||||
mock.patch.object(self.api.client, 'prepare'),
|
||||
) as (
|
||||
rpc_mock, prepare_mock
|
||||
):
|
||||
prepare_mock.return_value = self.api.client
|
||||
getattr(self.api, method_name)(mock.sentinel.context, 'test')
|
||||
|
||||
prepare_args = {'fanout': True}
|
||||
prepare_mock.assert_called_once_with(**prepare_args)
|
||||
|
||||
rpc_mock.assert_called_once_with(mock.sentinel.context, method_name,
|
||||
firewall='test', host='host')
|
||||
|
||||
def test_create_firewall(self):
|
||||
self._call_test_helper('create_firewall')
|
||||
|
||||
def test_update_firewall(self):
|
||||
self._call_test_helper('update_firewall')
|
||||
|
||||
def test_delete_firewall(self):
|
||||
self._call_test_helper('delete_firewall')
|
||||
|
||||
|
||||
class TestFirewallPluginBase(test_db_firewall.TestFirewallDBPlugin):
|
||||
|
||||
def setUp(self):
|
||||
super(TestFirewallPluginBase, self).setUp(fw_plugin=FW_PLUGIN_KLASS)
|
||||
self.callbacks = self.plugin.endpoints[0]
|
||||
|
||||
def test_create_second_firewall_not_permitted(self):
|
||||
with self.firewall():
|
||||
res = self._create_firewall(
|
||||
None, 'firewall2', description='test',
|
||||
firewall_policy_id=None, admin_state_up=True)
|
||||
self.assertEqual(res.status_int, exc.HTTPConflict.code)
|
||||
|
||||
def test_create_firewall_admin_not_affected_by_other_tenant(self):
|
||||
# Create fw with admin after creating fw with other tenant
|
||||
with self.firewall(tenant_id='other-tenant') as fw1:
|
||||
with self.firewall() as fw2:
|
||||
self.assertEqual('other-tenant', fw1['firewall']['tenant_id'])
|
||||
self.assertEqual(self._tenant_id, fw2['firewall']['tenant_id'])
|
||||
|
||||
def test_update_firewall(self):
|
||||
ctx = context.get_admin_context()
|
||||
name = "new_firewall1"
|
||||
attrs = self._get_test_firewall_attrs(name)
|
||||
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as firewall:
|
||||
fw_id = firewall['firewall']['id']
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE)
|
||||
data = {'firewall': {'name': name}}
|
||||
req = self.new_update_request('firewalls', data, fw_id)
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
attrs = self._replace_firewall_status(attrs,
|
||||
const.PENDING_CREATE,
|
||||
const.PENDING_UPDATE)
|
||||
for k, v in attrs.iteritems():
|
||||
self.assertEqual(res['firewall'][k], v)
|
||||
|
||||
def test_update_firewall_fails_when_firewall_pending(self):
|
||||
name = "new_firewall1"
|
||||
attrs = self._get_test_firewall_attrs(name)
|
||||
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as firewall:
|
||||
fw_id = firewall['firewall']['id']
|
||||
data = {'firewall': {'name': name}}
|
||||
req = self.new_update_request('firewalls', data, fw_id)
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPConflict.code)
|
||||
|
||||
def test_update_firewall_shared_fails_for_non_admin(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP,
|
||||
tenant_id='noadmin'
|
||||
) as firewall:
|
||||
fw_id = firewall['firewall']['id']
|
||||
self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE)
|
||||
data = {'firewall': {'shared': True}}
|
||||
req = self.new_update_request(
|
||||
'firewalls', data, fw_id,
|
||||
context=context.Context('', 'noadmin'))
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPForbidden.code)
|
||||
|
||||
def test_update_firewall_policy_fails_when_firewall_pending(self):
|
||||
name = "new_firewall1"
|
||||
attrs = self._get_test_firewall_attrs(name)
|
||||
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
):
|
||||
data = {'firewall_policy': {'name': name}}
|
||||
req = self.new_update_request('firewall_policies',
|
||||
data, fwp_id)
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPConflict.code)
|
||||
|
||||
def test_update_firewall_rule_fails_when_firewall_pending(self):
|
||||
with self.firewall_rule(name='fwr1') as fr:
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
fr_id = fr['firewall_rule']['id']
|
||||
fw_rule_ids = [fr_id]
|
||||
data = {'firewall_policy':
|
||||
{'firewall_rules': fw_rule_ids}}
|
||||
req = self.new_update_request('firewall_policies', data,
|
||||
fwp_id)
|
||||
req.get_response(self.ext_api)
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
):
|
||||
data = {'firewall_rule': {'protocol': 'udp'}}
|
||||
req = self.new_update_request('firewall_rules',
|
||||
data, fr_id)
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPConflict.code)
|
||||
|
||||
def test_delete_firewall(self):
|
||||
ctx = context.get_admin_context()
|
||||
attrs = self._get_test_firewall_attrs()
|
||||
# stop the AgentRPC patch for this one to test pending states
|
||||
self.agentapi_delf_p.stop()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as firewall:
|
||||
fw_id = firewall['firewall']['id']
|
||||
attrs = self._replace_firewall_status(attrs,
|
||||
const.PENDING_CREATE,
|
||||
const.PENDING_DELETE)
|
||||
req = self.new_delete_request('firewalls', fw_id)
|
||||
req.get_response(self.ext_api)
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
for k, v in attrs.iteritems():
|
||||
self.assertEqual(fw_db[k], v)
|
||||
# cleanup the pending firewall
|
||||
self.plugin.endpoints[0].firewall_deleted(ctx, fw_id)
|
||||
|
||||
def test_delete_firewall_after_agent_delete(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(firewall_policy_id=fwp_id,
|
||||
do_delete=False) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
req = self.new_delete_request('firewalls', fw_id)
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
self.assertRaises(firewall.FirewallNotFound,
|
||||
self.plugin.get_firewall,
|
||||
ctx, fw_id)
|
||||
|
||||
def test_make_firewall_dict_with_in_place_rules(self):
|
||||
ctx = context.get_admin_context()
|
||||
with contextlib.nested(self.firewall_rule(name='fwr1'),
|
||||
self.firewall_rule(name='fwr2'),
|
||||
self.firewall_rule(name='fwr3')) as fr:
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
fw_rule_ids = [r['firewall_rule']['id'] for r in fr]
|
||||
data = {'firewall_policy':
|
||||
{'firewall_rules': fw_rule_ids}}
|
||||
req = self.new_update_request('firewall_policies', data,
|
||||
fwp_id)
|
||||
req.get_response(self.ext_api)
|
||||
attrs = self._get_test_firewall_attrs()
|
||||
attrs['firewall_policy_id'] = fwp_id
|
||||
with self.firewall(
|
||||
firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP
|
||||
) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
fw_rules = (
|
||||
self.plugin._make_firewall_dict_with_rules(ctx,
|
||||
fw_id)
|
||||
)
|
||||
self.assertEqual(fw_rules['id'], fw_id)
|
||||
self._compare_firewall_rule_lists(
|
||||
fwp_id, fr, fw_rules['firewall_rule_list'])
|
||||
|
||||
def test_make_firewall_dict_with_in_place_rules_no_policy(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall() as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
fw_rules = self.plugin._make_firewall_dict_with_rules(ctx, fw_id)
|
||||
self.assertEqual(fw_rules['firewall_rule_list'], [])
|
||||
|
||||
def test_list_firewalls(self):
|
||||
with self.firewall_policy() as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(name='fw1', firewall_policy_id=fwp_id,
|
||||
description='fw') as fwalls:
|
||||
self._test_list_resources('firewall', [fwalls],
|
||||
query_params='description=fw')
|
|
@ -1,47 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 contextlib
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.services.loadbalancer.agent import agent
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestLbaasService(base.BaseTestCase):
|
||||
def test_start(self):
|
||||
with mock.patch.object(
|
||||
agent.n_rpc.Service, 'start'
|
||||
) as mock_start:
|
||||
|
||||
mgr = mock.Mock()
|
||||
cfg.CONF.periodic_interval = mock.Mock(return_value=10)
|
||||
agent_service = agent.LbaasAgentService('host', 'topic', mgr)
|
||||
agent_service.start()
|
||||
|
||||
self.assertTrue(mock_start.called)
|
||||
|
||||
def test_main(self):
|
||||
logging_str = 'neutron.agent.common.config.setup_logging'
|
||||
with contextlib.nested(
|
||||
mock.patch(logging_str),
|
||||
mock.patch.object(agent.service, 'launch'),
|
||||
mock.patch('sys.argv'),
|
||||
mock.patch.object(agent.manager, 'LbaasAgentManager'),
|
||||
mock.patch.object(cfg.CONF, 'register_opts')
|
||||
) as (mock_logging, mock_launch, sys_argv, mgr_cls, ro):
|
||||
agent.main()
|
||||
|
||||
mock_launch.assert_called_once_with(mock.ANY)
|
|
@ -1,370 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.agent import agent_manager as manager
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestManager(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestManager, self).setUp()
|
||||
|
||||
mock_conf = mock.Mock()
|
||||
mock_conf.device_driver = ['devdriver']
|
||||
|
||||
self.mock_importer = mock.patch.object(manager, 'importutils').start()
|
||||
|
||||
rpc_mock_cls = mock.patch(
|
||||
'neutron.services.loadbalancer.agent.agent_api.LbaasAgentApi'
|
||||
).start()
|
||||
|
||||
# disable setting up periodic state reporting
|
||||
mock_conf.AGENT.report_interval = 0
|
||||
|
||||
self.mgr = manager.LbaasAgentManager(mock_conf)
|
||||
self.rpc_mock = rpc_mock_cls.return_value
|
||||
self.log = mock.patch.object(manager, 'LOG').start()
|
||||
self.driver_mock = mock.Mock()
|
||||
self.mgr.device_drivers = {'devdriver': self.driver_mock}
|
||||
self.mgr.instance_mapping = {'1': 'devdriver', '2': 'devdriver'}
|
||||
self.mgr.needs_resync = False
|
||||
|
||||
def test_initialize_service_hook(self):
|
||||
with mock.patch.object(self.mgr, 'sync_state') as sync:
|
||||
self.mgr.initialize_service_hook(mock.Mock())
|
||||
sync.assert_called_once_with()
|
||||
|
||||
def test_periodic_resync_needs_sync(self):
|
||||
with mock.patch.object(self.mgr, 'sync_state') as sync:
|
||||
self.mgr.needs_resync = True
|
||||
self.mgr.periodic_resync(mock.Mock())
|
||||
sync.assert_called_once_with()
|
||||
|
||||
def test_periodic_resync_no_sync(self):
|
||||
with mock.patch.object(self.mgr, 'sync_state') as sync:
|
||||
self.mgr.needs_resync = False
|
||||
self.mgr.periodic_resync(mock.Mock())
|
||||
self.assertFalse(sync.called)
|
||||
|
||||
def test_collect_stats(self):
|
||||
self.mgr.collect_stats(mock.Mock())
|
||||
self.rpc_mock.update_pool_stats.assert_has_calls([
|
||||
mock.call('1', mock.ANY),
|
||||
mock.call('2', mock.ANY)
|
||||
], any_order=True)
|
||||
|
||||
def test_collect_stats_exception(self):
|
||||
self.driver_mock.get_stats.side_effect = Exception
|
||||
|
||||
self.mgr.collect_stats(mock.Mock())
|
||||
|
||||
self.assertFalse(self.rpc_mock.called)
|
||||
self.assertTrue(self.mgr.needs_resync)
|
||||
self.assertTrue(self.log.exception.called)
|
||||
|
||||
def _sync_state_helper(self, ready, reloaded, destroyed):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.mgr, '_reload_pool'),
|
||||
mock.patch.object(self.mgr, '_destroy_pool')
|
||||
) as (reload, destroy):
|
||||
|
||||
self.rpc_mock.get_ready_devices.return_value = ready
|
||||
|
||||
self.mgr.sync_state()
|
||||
|
||||
self.assertEqual(len(reloaded), len(reload.mock_calls))
|
||||
self.assertEqual(len(destroyed), len(destroy.mock_calls))
|
||||
|
||||
reload.assert_has_calls([mock.call(i) for i in reloaded],
|
||||
any_order=True)
|
||||
destroy.assert_has_calls([mock.call(i) for i in destroyed],
|
||||
any_order=True)
|
||||
self.assertFalse(self.mgr.needs_resync)
|
||||
|
||||
def test_sync_state_all_known(self):
|
||||
self._sync_state_helper(['1', '2'], ['1', '2'], [])
|
||||
|
||||
def test_sync_state_all_unknown(self):
|
||||
self.mgr.instance_mapping = {}
|
||||
self._sync_state_helper(['1', '2'], ['1', '2'], [])
|
||||
|
||||
def test_sync_state_destroy_all(self):
|
||||
self._sync_state_helper([], [], ['1', '2'])
|
||||
|
||||
def test_sync_state_both(self):
|
||||
self.mgr.instance_mapping = {'1': 'devdriver'}
|
||||
self._sync_state_helper(['2'], ['2'], ['1'])
|
||||
|
||||
def test_sync_state_exception(self):
|
||||
self.rpc_mock.get_ready_devices.side_effect = Exception
|
||||
|
||||
self.mgr.sync_state()
|
||||
|
||||
self.assertTrue(self.log.exception.called)
|
||||
self.assertTrue(self.mgr.needs_resync)
|
||||
|
||||
def test_reload_pool(self):
|
||||
config = {'driver': 'devdriver'}
|
||||
self.rpc_mock.get_logical_device.return_value = config
|
||||
pool_id = 'new_id'
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
|
||||
self.mgr._reload_pool(pool_id)
|
||||
|
||||
self.driver_mock.deploy_instance.assert_called_once_with(config)
|
||||
self.assertIn(pool_id, self.mgr.instance_mapping)
|
||||
self.rpc_mock.pool_deployed.assert_called_once_with(pool_id)
|
||||
|
||||
def test_reload_pool_driver_not_found(self):
|
||||
config = {'driver': 'unknown_driver'}
|
||||
self.rpc_mock.get_logical_device.return_value = config
|
||||
pool_id = 'new_id'
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
|
||||
self.mgr._reload_pool(pool_id)
|
||||
|
||||
self.assertTrue(self.log.error.called)
|
||||
self.assertFalse(self.driver_mock.deploy_instance.called)
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
self.assertFalse(self.rpc_mock.pool_deployed.called)
|
||||
|
||||
def test_reload_pool_exception_on_driver(self):
|
||||
config = {'driver': 'devdriver'}
|
||||
self.rpc_mock.get_logical_device.return_value = config
|
||||
self.driver_mock.deploy_instance.side_effect = Exception
|
||||
pool_id = 'new_id'
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
|
||||
self.mgr._reload_pool(pool_id)
|
||||
|
||||
self.driver_mock.deploy_instance.assert_called_once_with(config)
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
self.assertFalse(self.rpc_mock.pool_deployed.called)
|
||||
self.assertTrue(self.log.exception.called)
|
||||
self.assertTrue(self.mgr.needs_resync)
|
||||
|
||||
def test_destroy_pool(self):
|
||||
pool_id = '1'
|
||||
self.assertIn(pool_id, self.mgr.instance_mapping)
|
||||
|
||||
self.mgr._destroy_pool(pool_id)
|
||||
|
||||
self.driver_mock.undeploy_instance.assert_called_once_with(pool_id)
|
||||
self.assertNotIn(pool_id, self.mgr.instance_mapping)
|
||||
self.rpc_mock.pool_destroyed.assert_called_once_with(pool_id)
|
||||
self.assertFalse(self.mgr.needs_resync)
|
||||
|
||||
def test_destroy_pool_exception_on_driver(self):
|
||||
pool_id = '1'
|
||||
self.assertIn(pool_id, self.mgr.instance_mapping)
|
||||
self.driver_mock.undeploy_instance.side_effect = Exception
|
||||
|
||||
self.mgr._destroy_pool(pool_id)
|
||||
|
||||
self.driver_mock.undeploy_instance.assert_called_once_with(pool_id)
|
||||
self.assertIn(pool_id, self.mgr.instance_mapping)
|
||||
self.assertFalse(self.rpc_mock.pool_destroyed.called)
|
||||
self.assertTrue(self.log.exception.called)
|
||||
self.assertTrue(self.mgr.needs_resync)
|
||||
|
||||
def test_get_driver_unknown_device(self):
|
||||
self.assertRaises(manager.DeviceNotFoundOnAgent,
|
||||
self.mgr._get_driver, 'unknown')
|
||||
|
||||
def test_remove_orphans(self):
|
||||
self.mgr.remove_orphans()
|
||||
orphans = {'1': "Fake", '2': "Fake"}
|
||||
self.driver_mock.remove_orphans.assert_called_once_with(orphans.keys())
|
||||
|
||||
def test_create_vip(self):
|
||||
vip = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.create_vip(mock.Mock(), vip)
|
||||
self.driver_mock.create_vip.assert_called_once_with(vip)
|
||||
self.rpc_mock.update_status.assert_called_once_with('vip', vip['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_create_vip_failed(self):
|
||||
vip = {'id': 'id1', 'pool_id': '1'}
|
||||
self.driver_mock.create_vip.side_effect = Exception
|
||||
self.mgr.create_vip(mock.Mock(), vip)
|
||||
self.driver_mock.create_vip.assert_called_once_with(vip)
|
||||
self.rpc_mock.update_status.assert_called_once_with('vip', vip['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_vip(self):
|
||||
old_vip = {'id': 'id1'}
|
||||
vip = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.update_vip(mock.Mock(), old_vip, vip)
|
||||
self.driver_mock.update_vip.assert_called_once_with(old_vip, vip)
|
||||
self.rpc_mock.update_status.assert_called_once_with('vip', vip['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_vip_failed(self):
|
||||
old_vip = {'id': 'id1'}
|
||||
vip = {'id': 'id1', 'pool_id': '1'}
|
||||
self.driver_mock.update_vip.side_effect = Exception
|
||||
self.mgr.update_vip(mock.Mock(), old_vip, vip)
|
||||
self.driver_mock.update_vip.assert_called_once_with(old_vip, vip)
|
||||
self.rpc_mock.update_status.assert_called_once_with('vip', vip['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_delete_vip(self):
|
||||
vip = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.delete_vip(mock.Mock(), vip)
|
||||
self.driver_mock.delete_vip.assert_called_once_with(vip)
|
||||
|
||||
def test_create_pool(self):
|
||||
pool = {'id': 'id1'}
|
||||
self.assertNotIn(pool['id'], self.mgr.instance_mapping)
|
||||
self.mgr.create_pool(mock.Mock(), pool, 'devdriver')
|
||||
self.driver_mock.create_pool.assert_called_once_with(pool)
|
||||
self.rpc_mock.update_status.assert_called_once_with('pool', pool['id'],
|
||||
constants.ACTIVE)
|
||||
self.assertIn(pool['id'], self.mgr.instance_mapping)
|
||||
|
||||
def test_create_pool_failed(self):
|
||||
pool = {'id': 'id1'}
|
||||
self.assertNotIn(pool['id'], self.mgr.instance_mapping)
|
||||
self.driver_mock.create_pool.side_effect = Exception
|
||||
self.mgr.create_pool(mock.Mock(), pool, 'devdriver')
|
||||
self.driver_mock.create_pool.assert_called_once_with(pool)
|
||||
self.rpc_mock.update_status.assert_called_once_with('pool', pool['id'],
|
||||
constants.ERROR)
|
||||
self.assertNotIn(pool['id'], self.mgr.instance_mapping)
|
||||
|
||||
def test_update_pool(self):
|
||||
old_pool = {'id': '1'}
|
||||
pool = {'id': '1'}
|
||||
self.mgr.update_pool(mock.Mock(), old_pool, pool)
|
||||
self.driver_mock.update_pool.assert_called_once_with(old_pool, pool)
|
||||
self.rpc_mock.update_status.assert_called_once_with('pool', pool['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_pool_failed(self):
|
||||
old_pool = {'id': '1'}
|
||||
pool = {'id': '1'}
|
||||
self.driver_mock.update_pool.side_effect = Exception
|
||||
self.mgr.update_pool(mock.Mock(), old_pool, pool)
|
||||
self.driver_mock.update_pool.assert_called_once_with(old_pool, pool)
|
||||
self.rpc_mock.update_status.assert_called_once_with('pool', pool['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_delete_pool(self):
|
||||
pool = {'id': '1'}
|
||||
self.assertIn(pool['id'], self.mgr.instance_mapping)
|
||||
self.mgr.delete_pool(mock.Mock(), pool)
|
||||
self.driver_mock.delete_pool.assert_called_once_with(pool)
|
||||
self.assertNotIn(pool['id'], self.mgr.instance_mapping)
|
||||
|
||||
def test_create_member(self):
|
||||
member = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.create_member(mock.Mock(), member)
|
||||
self.driver_mock.create_member.assert_called_once_with(member)
|
||||
self.rpc_mock.update_status.assert_called_once_with('member',
|
||||
member['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_create_member_failed(self):
|
||||
member = {'id': 'id1', 'pool_id': '1'}
|
||||
self.driver_mock.create_member.side_effect = Exception
|
||||
self.mgr.create_member(mock.Mock(), member)
|
||||
self.driver_mock.create_member.assert_called_once_with(member)
|
||||
self.rpc_mock.update_status.assert_called_once_with('member',
|
||||
member['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_member(self):
|
||||
old_member = {'id': 'id1'}
|
||||
member = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.update_member(mock.Mock(), old_member, member)
|
||||
self.driver_mock.update_member.assert_called_once_with(old_member,
|
||||
member)
|
||||
self.rpc_mock.update_status.assert_called_once_with('member',
|
||||
member['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_member_failed(self):
|
||||
old_member = {'id': 'id1'}
|
||||
member = {'id': 'id1', 'pool_id': '1'}
|
||||
self.driver_mock.update_member.side_effect = Exception
|
||||
self.mgr.update_member(mock.Mock(), old_member, member)
|
||||
self.driver_mock.update_member.assert_called_once_with(old_member,
|
||||
member)
|
||||
self.rpc_mock.update_status.assert_called_once_with('member',
|
||||
member['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_delete_member(self):
|
||||
member = {'id': 'id1', 'pool_id': '1'}
|
||||
self.mgr.delete_member(mock.Mock(), member)
|
||||
self.driver_mock.delete_member.assert_called_once_with(member)
|
||||
|
||||
def test_create_monitor(self):
|
||||
monitor = {'id': 'id1'}
|
||||
assoc_id = {'monitor_id': monitor['id'], 'pool_id': '1'}
|
||||
self.mgr.create_pool_health_monitor(mock.Mock(), monitor, '1')
|
||||
self.driver_mock.create_pool_health_monitor.assert_called_once_with(
|
||||
monitor, '1')
|
||||
self.rpc_mock.update_status.assert_called_once_with('health_monitor',
|
||||
assoc_id,
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_create_monitor_failed(self):
|
||||
monitor = {'id': 'id1'}
|
||||
assoc_id = {'monitor_id': monitor['id'], 'pool_id': '1'}
|
||||
self.driver_mock.create_pool_health_monitor.side_effect = Exception
|
||||
self.mgr.create_pool_health_monitor(mock.Mock(), monitor, '1')
|
||||
self.driver_mock.create_pool_health_monitor.assert_called_once_with(
|
||||
monitor, '1')
|
||||
self.rpc_mock.update_status.assert_called_once_with('health_monitor',
|
||||
assoc_id,
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_monitor(self):
|
||||
monitor = {'id': 'id1'}
|
||||
assoc_id = {'monitor_id': monitor['id'], 'pool_id': '1'}
|
||||
self.mgr.update_pool_health_monitor(mock.Mock(), monitor, monitor, '1')
|
||||
self.driver_mock.update_pool_health_monitor.assert_called_once_with(
|
||||
monitor, monitor, '1')
|
||||
self.rpc_mock.update_status.assert_called_once_with('health_monitor',
|
||||
assoc_id,
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_monitor_failed(self):
|
||||
monitor = {'id': 'id1'}
|
||||
assoc_id = {'monitor_id': monitor['id'], 'pool_id': '1'}
|
||||
self.driver_mock.update_pool_health_monitor.side_effect = Exception
|
||||
self.mgr.update_pool_health_monitor(mock.Mock(), monitor, monitor, '1')
|
||||
self.driver_mock.update_pool_health_monitor.assert_called_once_with(
|
||||
monitor, monitor, '1')
|
||||
self.rpc_mock.update_status.assert_called_once_with('health_monitor',
|
||||
assoc_id,
|
||||
constants.ERROR)
|
||||
|
||||
def test_delete_monitor(self):
|
||||
monitor = {'id': 'id1'}
|
||||
self.mgr.delete_pool_health_monitor(mock.Mock(), monitor, '1')
|
||||
self.driver_mock.delete_pool_health_monitor.assert_called_once_with(
|
||||
monitor, '1')
|
||||
|
||||
def test_agent_disabled(self):
|
||||
payload = {'admin_state_up': False}
|
||||
self.mgr.agent_updated(mock.Mock(), payload)
|
||||
self.driver_mock.undeploy_instance.assert_has_calls(
|
||||
[mock.call('1'), mock.call('2')], any_order=True)
|
|
@ -1,81 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 contextlib
|
||||
import copy
|
||||
import mock
|
||||
|
||||
from neutron.services.loadbalancer.agent import agent_api as api
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestApiCache(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestApiCache, self).setUp()
|
||||
|
||||
self.api = api.LbaasAgentApi('topic', mock.sentinel.context, 'host')
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.api.host, 'host')
|
||||
self.assertEqual(self.api.context, mock.sentinel.context)
|
||||
|
||||
def _test_method(self, method, **kwargs):
|
||||
add_host = ('get_ready_devices', 'plug_vip_port', 'unplug_vip_port',
|
||||
'update_pool_stats')
|
||||
expected_kwargs = copy.copy(kwargs)
|
||||
if method in add_host:
|
||||
expected_kwargs['host'] = self.api.host
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.client, 'call'),
|
||||
mock.patch.object(self.api.client, 'prepare'),
|
||||
) as (
|
||||
rpc_mock, prepare_mock
|
||||
):
|
||||
prepare_mock.return_value = self.api.client
|
||||
rpc_mock.return_value = 'foo'
|
||||
rv = getattr(self.api, method)(**kwargs)
|
||||
|
||||
self.assertEqual(rv, 'foo')
|
||||
|
||||
prepare_args = {}
|
||||
prepare_mock.assert_called_once_with(**prepare_args)
|
||||
|
||||
rpc_mock.assert_called_once_with(mock.sentinel.context, method,
|
||||
**expected_kwargs)
|
||||
|
||||
def test_get_ready_devices(self):
|
||||
self._test_method('get_ready_devices')
|
||||
|
||||
def test_get_logical_device(self):
|
||||
self._test_method('get_logical_device', pool_id='pool_id')
|
||||
|
||||
def test_pool_destroyed(self):
|
||||
self._test_method('pool_destroyed', pool_id='pool_id')
|
||||
|
||||
def test_pool_deployed(self):
|
||||
self._test_method('pool_deployed', pool_id='pool_id')
|
||||
|
||||
def test_update_status(self):
|
||||
self._test_method('update_status', obj_type='type', obj_id='id',
|
||||
status='status')
|
||||
|
||||
def test_plug_vip_port(self):
|
||||
self._test_method('plug_vip_port', port_id='port_id')
|
||||
|
||||
def test_unplug_vip_port(self):
|
||||
self._test_method('unplug_vip_port', port_id='port_id')
|
||||
|
||||
def test_update_pool_stats(self):
|
||||
self._test_method('update_pool_stats', pool_id='id', stats='stats')
|
|
@ -1,179 +0,0 @@
|
|||
# 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')
|
|
@ -1,26 +0,0 @@
|
|||
# Copyright 2013 Embrane, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
|
||||
from neutron.services.loadbalancer.drivers.embrane import config # noqa
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class ConfigurationTest(base.BaseTestCase):
|
||||
|
||||
def test_defaults(self):
|
||||
self.assertEqual('small', cfg.CONF.heleoslb.lb_flavor)
|
||||
self.assertEqual(60, cfg.CONF.heleoslb.sync_interval)
|
|
@ -1,89 +0,0 @@
|
|||
# Copyright 2013 Embrane, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
from oslo.db import exception as n_exc
|
||||
|
||||
from neutron import context
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
|
||||
HELEOSAPIMOCK = mock.Mock()
|
||||
sys.modules["heleosapi"] = HELEOSAPIMOCK
|
||||
from neutron.services.loadbalancer.drivers.embrane import config # noqa
|
||||
from neutron.services.loadbalancer.drivers.embrane import constants as h_con
|
||||
from neutron.services.loadbalancer.drivers.embrane import db as h_db
|
||||
# Stop the mock from persisting indefinitely in the global modules space
|
||||
del sys.modules["heleosapi"]
|
||||
|
||||
EMBRANE_PROVIDER = ('LOADBALANCER:lbaas:neutron.services.'
|
||||
'loadbalancer.drivers.embrane.driver.'
|
||||
'EmbraneLbaas:default')
|
||||
|
||||
|
||||
class TestLoadBalancerPluginBase(
|
||||
test_db_loadbalancer.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('admin_password', "admin123", 'heleoslb')
|
||||
cfg.CONF.set_override('sync_interval', 0, 'heleoslb')
|
||||
mock.patch.dict(sys.modules, {'heleosapi': HELEOSAPIMOCK}).start()
|
||||
super(TestLoadBalancerPluginBase, self).setUp(
|
||||
lbaas_provider=EMBRANE_PROVIDER)
|
||||
self.driver = self.plugin.drivers['lbaas']
|
||||
# prevent module mock from saving calls between tests
|
||||
self.addCleanup(HELEOSAPIMOCK.reset_mock)
|
||||
|
||||
|
||||
class TestLoadBalancerPlugin(test_db_loadbalancer.TestLoadBalancer,
|
||||
TestLoadBalancerPluginBase):
|
||||
|
||||
def test_create_vip_with_session_persistence_with_app_cookie(self):
|
||||
self.skip("App cookie persistence not supported.")
|
||||
|
||||
def test_pool_port(self):
|
||||
with self.port() as port:
|
||||
with self.pool() as pool:
|
||||
h_db.add_pool_port(context.get_admin_context(),
|
||||
pool['pool']['id'], port['port']['id'])
|
||||
pool_port = h_db.get_pool_port(context.get_admin_context(),
|
||||
pool['pool']['id'])
|
||||
self.assertIsNotNone(pool_port)
|
||||
pool_port = h_db.get_pool_port(context.get_admin_context(),
|
||||
pool['pool']['id'])
|
||||
self.assertIsNone(pool_port)
|
||||
|
||||
def test_create_pool_port_no_port(self):
|
||||
with self.pool() as pool:
|
||||
self.assertRaises(n_exc.DBError,
|
||||
h_db.add_pool_port,
|
||||
context.get_admin_context(),
|
||||
pool['pool']['id'], None)
|
||||
|
||||
def test_lb_operations_handlers(self):
|
||||
h = self.driver._dispatcher.handlers
|
||||
self.assertIsNotNone(h[h_con.Events.ADD_OR_UPDATE_MEMBER])
|
||||
self.assertIsNotNone(h[h_con.Events.CREATE_VIP])
|
||||
self.assertIsNotNone(h[h_con.Events.DELETE_MEMBER])
|
||||
self.assertIsNotNone(h[h_con.Events.DELETE_VIP])
|
||||
self.assertIsNotNone(h[h_con.Events.POLL_GRAPH])
|
||||
self.assertIsNotNone(h[h_con.Events.REMOVE_MEMBER])
|
||||
self.assertIsNotNone(h[h_con.Events.UPDATE_POOL])
|
||||
self.assertIsNotNone(h[h_con.Events.UPDATE_VIP])
|
||||
self.assertIsNotNone(h[h_con.Events.UPDATE_POOL_HM])
|
||||
self.assertIsNotNone(h[h_con.Events.DELETE_POOL_HM])
|
||||
self.assertIsNotNone(h[h_con.Events.ADD_POOL_HM])
|
|
@ -1,224 +0,0 @@
|
|||
# Copyright 2013 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
|
||||
from neutron.services.loadbalancer.drivers.haproxy import cfg
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestHaproxyCfg(base.BaseTestCase):
|
||||
def test_save_config(self):
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.services.loadbalancer.'
|
||||
'drivers.haproxy.cfg._build_global'),
|
||||
mock.patch('neutron.services.loadbalancer.'
|
||||
'drivers.haproxy.cfg._build_defaults'),
|
||||
mock.patch('neutron.services.loadbalancer.'
|
||||
'drivers.haproxy.cfg._build_frontend'),
|
||||
mock.patch('neutron.services.loadbalancer.'
|
||||
'drivers.haproxy.cfg._build_backend'),
|
||||
mock.patch('neutron.agent.linux.utils.replace_file')
|
||||
) as (b_g, b_d, b_f, b_b, replace):
|
||||
test_config = ['globals', 'defaults', 'frontend', 'backend']
|
||||
b_g.return_value = [test_config[0]]
|
||||
b_d.return_value = [test_config[1]]
|
||||
b_f.return_value = [test_config[2]]
|
||||
b_b.return_value = [test_config[3]]
|
||||
|
||||
cfg.save_config('test_path', mock.Mock())
|
||||
replace.assert_called_once_with('test_path',
|
||||
'\n'.join(test_config))
|
||||
|
||||
def test_build_global(self):
|
||||
expected_opts = ['global',
|
||||
'\tdaemon',
|
||||
'\tuser nobody',
|
||||
'\tgroup test_group',
|
||||
'\tlog /dev/log local0',
|
||||
'\tlog /dev/log local1 notice',
|
||||
'\tstats socket test_path mode 0666 level user']
|
||||
opts = cfg._build_global(mock.Mock(), 'test_path', 'test_group')
|
||||
self.assertEqual(expected_opts, list(opts))
|
||||
|
||||
def test_build_defaults(self):
|
||||
expected_opts = ['defaults',
|
||||
'\tlog global',
|
||||
'\tretries 3',
|
||||
'\toption redispatch',
|
||||
'\ttimeout connect 5000',
|
||||
'\ttimeout client 50000',
|
||||
'\ttimeout server 50000']
|
||||
opts = cfg._build_defaults(mock.Mock())
|
||||
self.assertEqual(expected_opts, list(opts))
|
||||
|
||||
def test_build_frontend(self):
|
||||
test_config = {'vip': {'id': 'vip_id',
|
||||
'protocol': 'HTTP',
|
||||
'port': {'fixed_ips': [
|
||||
{'ip_address': '10.0.0.2'}]
|
||||
},
|
||||
'protocol_port': 80,
|
||||
'connection_limit': 2000,
|
||||
},
|
||||
'pool': {'id': 'pool_id'}}
|
||||
expected_opts = ['frontend vip_id',
|
||||
'\toption tcplog',
|
||||
'\tbind 10.0.0.2:80',
|
||||
'\tmode http',
|
||||
'\tdefault_backend pool_id',
|
||||
'\tmaxconn 2000',
|
||||
'\toption forwardfor']
|
||||
opts = cfg._build_frontend(test_config)
|
||||
self.assertEqual(expected_opts, list(opts))
|
||||
|
||||
test_config['vip']['connection_limit'] = -1
|
||||
expected_opts.remove('\tmaxconn 2000')
|
||||
opts = cfg._build_frontend(test_config)
|
||||
self.assertEqual(expected_opts, list(opts))
|
||||
|
||||
def test_build_backend(self):
|
||||
test_config = {'pool': {'id': 'pool_id',
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN'},
|
||||
'members': [{'status': 'ACTIVE',
|
||||
'admin_state_up': True,
|
||||
'id': 'member1_id',
|
||||
'address': '10.0.0.3',
|
||||
'protocol_port': 80,
|
||||
'weight': 1},
|
||||
{'status': 'INACTIVE',
|
||||
'admin_state_up': True,
|
||||
'id': 'member2_id',
|
||||
'address': '10.0.0.4',
|
||||
'protocol_port': 80,
|
||||
'weight': 1},
|
||||
{'status': 'PENDING_CREATE',
|
||||
'admin_state_up': True,
|
||||
'id': 'member3_id',
|
||||
'address': '10.0.0.5',
|
||||
'protocol_port': 80,
|
||||
'weight': 1}],
|
||||
'healthmonitors': [{'admin_state_up': True,
|
||||
'delay': 3,
|
||||
'max_retries': 4,
|
||||
'timeout': 2,
|
||||
'type': 'TCP'}],
|
||||
'vip': {'session_persistence': {'type': 'HTTP_COOKIE'}}}
|
||||
expected_opts = ['backend pool_id',
|
||||
'\tmode http',
|
||||
'\tbalance roundrobin',
|
||||
'\toption forwardfor',
|
||||
'\ttimeout check 2s',
|
||||
'\tcookie SRV insert indirect nocache',
|
||||
'\tserver member1_id 10.0.0.3:80 weight 1 '
|
||||
'check inter 3s fall 4 cookie 0',
|
||||
'\tserver member2_id 10.0.0.4:80 weight 1 '
|
||||
'check inter 3s fall 4 cookie 1',
|
||||
'\tserver member3_id 10.0.0.5:80 weight 1 '
|
||||
'check inter 3s fall 4 cookie 2']
|
||||
opts = cfg._build_backend(test_config)
|
||||
self.assertEqual(expected_opts, list(opts))
|
||||
|
||||
def test_get_server_health_option(self):
|
||||
test_config = {'healthmonitors': [{'admin_state_up': False,
|
||||
'delay': 3,
|
||||
'max_retries': 4,
|
||||
'timeout': 2,
|
||||
'type': 'TCP',
|
||||
'http_method': 'GET',
|
||||
'url_path': '/',
|
||||
'expected_codes': '200'}]}
|
||||
self.assertEqual(('', []), cfg._get_server_health_option(test_config))
|
||||
|
||||
self.assertEqual(('', []), cfg._get_server_health_option(test_config))
|
||||
|
||||
test_config['healthmonitors'][0]['admin_state_up'] = True
|
||||
expected = (' check inter 3s fall 4', ['timeout check 2s'])
|
||||
self.assertEqual(expected, cfg._get_server_health_option(test_config))
|
||||
|
||||
test_config['healthmonitors'][0]['type'] = 'HTTPS'
|
||||
expected = (' check inter 3s fall 4',
|
||||
['timeout check 2s',
|
||||
'option httpchk GET /',
|
||||
'http-check expect rstatus 200',
|
||||
'option ssl-hello-chk'])
|
||||
self.assertEqual(expected, cfg._get_server_health_option(test_config))
|
||||
|
||||
def test_has_http_cookie_persistence(self):
|
||||
config = {'vip': {'session_persistence': {'type': 'HTTP_COOKIE'}}}
|
||||
self.assertTrue(cfg._has_http_cookie_persistence(config))
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'SOURCE_IP'}}}
|
||||
self.assertFalse(cfg._has_http_cookie_persistence(config))
|
||||
|
||||
config = {'vip': {'session_persistence': {}}}
|
||||
self.assertFalse(cfg._has_http_cookie_persistence(config))
|
||||
|
||||
def test_get_session_persistence(self):
|
||||
config = {'vip': {'session_persistence': {'type': 'SOURCE_IP'}}}
|
||||
self.assertEqual(cfg._get_session_persistence(config),
|
||||
['stick-table type ip size 10k', 'stick on src'])
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'HTTP_COOKIE'}},
|
||||
'members': []}
|
||||
self.assertEqual([], cfg._get_session_persistence(config))
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'HTTP_COOKIE'}}}
|
||||
self.assertEqual([], cfg._get_session_persistence(config))
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'HTTP_COOKIE'}},
|
||||
'members': [{'id': 'member1_id'}]}
|
||||
self.assertEqual(cfg._get_session_persistence(config),
|
||||
['cookie SRV insert indirect nocache'])
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'APP_COOKIE',
|
||||
'cookie_name': 'test'}}}
|
||||
self.assertEqual(cfg._get_session_persistence(config),
|
||||
['appsession test len 56 timeout 3h'])
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'APP_COOKIE'}}}
|
||||
self.assertEqual(cfg._get_session_persistence(config), [])
|
||||
|
||||
config = {'vip': {'session_persistence': {'type': 'UNSUPPORTED'}}}
|
||||
self.assertEqual(cfg._get_session_persistence(config), [])
|
||||
|
||||
def test_expand_expected_codes(self):
|
||||
exp_codes = ''
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes), set([]))
|
||||
exp_codes = '200'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes), set(['200']))
|
||||
exp_codes = '200, 201'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201']))
|
||||
exp_codes = '200, 201,202'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201', '202']))
|
||||
exp_codes = '200-202'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201', '202']))
|
||||
exp_codes = '200-202, 205'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201', '202', '205']))
|
||||
exp_codes = '200, 201-203'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201', '202', '203']))
|
||||
exp_codes = '200, 201-203, 205'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes),
|
||||
set(['200', '201', '202', '203', '205']))
|
||||
exp_codes = '201-200, 205'
|
||||
self.assertEqual(cfg._expand_expected_codes(exp_codes), set(['205']))
|
|
@ -1,549 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
|
||||
from neutron.common import exceptions
|
||||
from neutron.services.loadbalancer.drivers.haproxy import namespace_driver
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestHaproxyNSDriver(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestHaproxyNSDriver, self).setUp()
|
||||
|
||||
conf = mock.Mock()
|
||||
conf.haproxy.loadbalancer_state_path = '/the/path'
|
||||
conf.interface_driver = 'intdriver'
|
||||
conf.haproxy.user_group = 'test_group'
|
||||
conf.haproxy.send_gratuitous_arp = 3
|
||||
conf.AGENT.root_helper = 'sudo_test'
|
||||
self.conf = conf
|
||||
self.mock_importer = mock.patch.object(namespace_driver,
|
||||
'importutils').start()
|
||||
|
||||
self.rpc_mock = mock.Mock()
|
||||
self.driver = namespace_driver.HaproxyNSDriver(
|
||||
conf,
|
||||
self.rpc_mock
|
||||
)
|
||||
self.vif_driver = mock.Mock()
|
||||
self.driver.vif_driver = self.vif_driver
|
||||
|
||||
self.fake_config = {
|
||||
'pool': {'id': 'pool_id', 'status': 'ACTIVE',
|
||||
'admin_state_up': True},
|
||||
'vip': {'id': 'vip_id', 'port': {'id': 'port_id'},
|
||||
'status': 'ACTIVE', 'admin_state_up': True}
|
||||
}
|
||||
|
||||
def test_get_name(self):
|
||||
self.assertEqual(self.driver.get_name(), namespace_driver.DRIVER_NAME)
|
||||
|
||||
def test_create(self):
|
||||
with mock.patch.object(self.driver, '_plug') as plug:
|
||||
with mock.patch.object(self.driver, '_spawn') as spawn:
|
||||
self.driver.create(self.fake_config)
|
||||
|
||||
plug.assert_called_once_with(
|
||||
'qlbaas-pool_id', {'id': 'port_id'}
|
||||
)
|
||||
spawn.assert_called_once_with(self.fake_config)
|
||||
|
||||
def test_update(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch.object(self.driver, '_spawn'),
|
||||
mock.patch('__builtin__.open')
|
||||
) as (gsp, spawn, mock_open):
|
||||
mock_open.return_value = ['5']
|
||||
|
||||
self.driver.update(self.fake_config)
|
||||
|
||||
mock_open.assert_called_once_with(gsp.return_value, 'r')
|
||||
spawn.assert_called_once_with(self.fake_config, ['-sf', '5'])
|
||||
|
||||
def test_spawn(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(namespace_driver.hacfg, 'save_config'),
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
|
||||
) as (mock_save, gsp, ip_wrap):
|
||||
gsp.side_effect = lambda x, y: y
|
||||
|
||||
self.driver._spawn(self.fake_config)
|
||||
|
||||
mock_save.assert_called_once_with('conf', self.fake_config,
|
||||
'sock', 'test_group')
|
||||
cmd = ['haproxy', '-f', 'conf', '-p', 'pid']
|
||||
ip_wrap.assert_has_calls([
|
||||
mock.call('sudo_test', 'qlbaas-pool_id'),
|
||||
mock.call().netns.execute(cmd)
|
||||
])
|
||||
|
||||
def test_undeploy_instance(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch.object(namespace_driver, 'kill_pids_in_file'),
|
||||
mock.patch.object(self.driver, '_unplug'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
mock.patch('os.path.isdir'),
|
||||
mock.patch('shutil.rmtree')
|
||||
) as (gsp, kill, unplug, ip_wrap, isdir, rmtree):
|
||||
gsp.side_effect = lambda x, y: '/pool/' + y
|
||||
|
||||
self.driver.pool_to_port_id['pool_id'] = 'port_id'
|
||||
isdir.return_value = True
|
||||
|
||||
self.driver.undeploy_instance('pool_id')
|
||||
|
||||
kill.assert_called_once_with('sudo_test', '/pool/pid')
|
||||
unplug.assert_called_once_with('qlbaas-pool_id', 'port_id')
|
||||
isdir.assert_called_once_with('/pool')
|
||||
rmtree.assert_called_once_with('/pool')
|
||||
ip_wrap.assert_has_calls([
|
||||
mock.call('sudo_test', 'qlbaas-pool_id'),
|
||||
mock.call().garbage_collect_namespace()
|
||||
])
|
||||
|
||||
def test_undeploy_instance_with_ns_cleanup(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch.object(self.driver, 'vif_driver'),
|
||||
mock.patch.object(namespace_driver, 'kill_pids_in_file'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
mock.patch('os.path.isdir'),
|
||||
mock.patch('shutil.rmtree')
|
||||
) as (gsp, vif, kill, ip_wrap, isdir, rmtree):
|
||||
device = mock.Mock()
|
||||
device_name = 'port_device'
|
||||
device.name = device_name
|
||||
ip_wrap.return_value.get_devices.return_value = [device]
|
||||
|
||||
self.driver.undeploy_instance('pool_id', cleanup_namespace=True)
|
||||
vif.unplug.assert_called_once_with(device_name,
|
||||
namespace='qlbaas-pool_id')
|
||||
|
||||
def test_remove_orphans(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, 'exists'),
|
||||
mock.patch.object(self.driver, 'undeploy_instance'),
|
||||
mock.patch('os.listdir'),
|
||||
mock.patch('os.path.exists')
|
||||
) as (exists, undeploy, listdir, path_exists):
|
||||
known = ['known1', 'known2']
|
||||
unknown = ['unknown1', 'unknown2']
|
||||
listdir.return_value = known + unknown
|
||||
exists.side_effect = lambda x: x == 'unknown2'
|
||||
|
||||
self.driver.remove_orphans(known)
|
||||
|
||||
undeploy.assert_called_once_with('unknown2',
|
||||
cleanup_namespace=True)
|
||||
|
||||
def test_exists(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
mock.patch('socket.socket'),
|
||||
mock.patch('os.path.exists'),
|
||||
) as (gsp, ip_wrap, socket, path_exists):
|
||||
gsp.side_effect = lambda x, y, z: '/pool/' + y
|
||||
|
||||
ip_wrap.return_value.netns.exists.return_value = True
|
||||
path_exists.return_value = True
|
||||
|
||||
self.driver.exists('pool_id')
|
||||
|
||||
ip_wrap.assert_has_calls([
|
||||
mock.call('sudo_test'),
|
||||
mock.call().netns.exists('qlbaas-pool_id')
|
||||
])
|
||||
|
||||
self.assertTrue(self.driver.exists('pool_id'))
|
||||
|
||||
def test_get_stats(self):
|
||||
raw_stats = ('# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,'
|
||||
'dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,'
|
||||
'act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,'
|
||||
'sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,'
|
||||
'check_status,check_code,check_duration,hrsp_1xx,'
|
||||
'hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,'
|
||||
'req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,\n'
|
||||
'8e271901-69ed-403e-a59b-f53cf77ef208,BACKEND,1,2,3,4,0,'
|
||||
'10,7764,2365,0,0,,0,0,0,0,UP,1,1,0,,0,103780,0,,1,2,0,,0'
|
||||
',,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,\n\n'
|
||||
'a557019b-dc07-4688-9af4-f5cf02bb6d4b,'
|
||||
'32a6c2a3-420a-44c3-955d-86bd2fc6871e,0,0,0,1,,7,1120,'
|
||||
'224,,0,,0,0,0,0,UP,1,1,0,0,1,2623,303,,1,2,1,,7,,2,0,,'
|
||||
'1,L7OK,200,98,0,7,0,0,0,0,0,,,,0,0,\n'
|
||||
'a557019b-dc07-4688-9af4-f5cf02bb6d4b,'
|
||||
'd9aea044-8867-4e80-9875-16fb808fa0f9,0,0,0,2,,12,0,0,,'
|
||||
'0,,0,0,8,4,DOWN,1,1,0,9,2,308,675,,1,2,2,,4,,2,0,,2,'
|
||||
'L4CON,,2999,0,0,0,0,0,0,0,,,,0,0,\n')
|
||||
raw_stats_empty = ('# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,'
|
||||
'bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,'
|
||||
'status,weight,act,bck,chkfail,chkdown,lastchg,'
|
||||
'downtime,qlimit,pid,iid,sid,throttle,lbtot,'
|
||||
'tracked,type,rate,rate_lim,rate_max,check_status,'
|
||||
'check_code,check_duration,hrsp_1xx,hrsp_2xx,'
|
||||
'hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,'
|
||||
'req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,'
|
||||
'\n')
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_get_state_file_path'),
|
||||
mock.patch('socket.socket'),
|
||||
mock.patch('os.path.exists'),
|
||||
) as (gsp, socket, path_exists):
|
||||
gsp.side_effect = lambda x, y, z: '/pool/' + y
|
||||
path_exists.return_value = True
|
||||
socket.return_value = socket
|
||||
socket.recv.return_value = raw_stats
|
||||
|
||||
exp_stats = {'connection_errors': '0',
|
||||
'active_connections': '3',
|
||||
'current_sessions': '3',
|
||||
'bytes_in': '7764',
|
||||
'max_connections': '4',
|
||||
'max_sessions': '4',
|
||||
'bytes_out': '2365',
|
||||
'response_errors': '0',
|
||||
'total_sessions': '10',
|
||||
'total_connections': '10',
|
||||
'members': {
|
||||
'32a6c2a3-420a-44c3-955d-86bd2fc6871e': {
|
||||
'status': 'ACTIVE',
|
||||
'health': 'L7OK',
|
||||
'failed_checks': '0'
|
||||
},
|
||||
'd9aea044-8867-4e80-9875-16fb808fa0f9': {
|
||||
'status': 'INACTIVE',
|
||||
'health': 'L4CON',
|
||||
'failed_checks': '9'
|
||||
}
|
||||
}
|
||||
}
|
||||
stats = self.driver.get_stats('pool_id')
|
||||
self.assertEqual(exp_stats, stats)
|
||||
|
||||
socket.recv.return_value = raw_stats_empty
|
||||
self.assertEqual({'members': {}}, self.driver.get_stats('pool_id'))
|
||||
|
||||
path_exists.return_value = False
|
||||
socket.reset_mock()
|
||||
self.assertEqual({}, self.driver.get_stats('pool_id'))
|
||||
self.assertFalse(socket.called)
|
||||
|
||||
def test_plug(self):
|
||||
test_port = {'id': 'port_id',
|
||||
'network_id': 'net_id',
|
||||
'mac_address': 'mac_addr',
|
||||
'fixed_ips': [{'ip_address': '10.0.0.2',
|
||||
'subnet': {'cidr': '10.0.0.0/24',
|
||||
'gateway_ip': '10.0.0.1'}}]}
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.agent.linux.ip_lib.device_exists'),
|
||||
mock.patch('netaddr.IPNetwork'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
) as (dev_exists, ip_net, ip_wrap):
|
||||
self.vif_driver.get_device_name.return_value = 'test_interface'
|
||||
dev_exists.return_value = False
|
||||
ip_net.return_value = ip_net
|
||||
ip_net.prefixlen = 24
|
||||
|
||||
self.driver._plug('test_ns', test_port)
|
||||
self.rpc_mock.plug_vip_port.assert_called_once_with(
|
||||
test_port['id'])
|
||||
self.assertTrue(dev_exists.called)
|
||||
self.vif_driver.plug.assert_called_once_with('net_id', 'port_id',
|
||||
'test_interface',
|
||||
'mac_addr',
|
||||
namespace='test_ns')
|
||||
self.vif_driver.init_l3.assert_called_once_with(
|
||||
'test_interface',
|
||||
['10.0.0.2/24'],
|
||||
namespace='test_ns'
|
||||
)
|
||||
cmd = ['route', 'add', 'default', 'gw', '10.0.0.1']
|
||||
cmd_arping = ['arping', '-U', '-I',
|
||||
'test_interface', '-c',
|
||||
self.conf.haproxy.send_gratuitous_arp, '10.0.0.2']
|
||||
ip_wrap.assert_has_calls([
|
||||
mock.call('sudo_test', namespace='test_ns'),
|
||||
mock.call().netns.execute(cmd, check_exit_code=False),
|
||||
mock.call().netns.execute(cmd_arping, check_exit_code=False),
|
||||
])
|
||||
|
||||
dev_exists.return_value = True
|
||||
self.assertRaises(exceptions.PreexistingDeviceFailure,
|
||||
self.driver._plug, 'test_ns', test_port, False)
|
||||
|
||||
def test_plug_not_send_gratuitous_arp(self):
|
||||
self.conf.haproxy.send_gratuitous_arp = 0
|
||||
test_port = {'id': 'port_id',
|
||||
'network_id': 'net_id',
|
||||
'mac_address': 'mac_addr',
|
||||
'fixed_ips': [{'ip_address': '10.0.0.2',
|
||||
'subnet': {'cidr': '10.0.0.0/24',
|
||||
'gateway_ip': '10.0.0.1'}}]}
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.agent.linux.ip_lib.device_exists'),
|
||||
mock.patch('netaddr.IPNetwork'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
) as (dev_exists, ip_net, ip_wrap):
|
||||
self.vif_driver.get_device_name.return_value = 'test_interface'
|
||||
dev_exists.return_value = False
|
||||
ip_net.return_value = ip_net
|
||||
ip_net.prefixlen = 24
|
||||
|
||||
self.driver._plug('test_ns', test_port)
|
||||
cmd = ['route', 'add', 'default', 'gw', '10.0.0.1']
|
||||
expected = [
|
||||
mock.call('sudo_test', namespace='test_ns'),
|
||||
mock.call().netns.execute(cmd, check_exit_code=False)]
|
||||
self.assertEqual(expected, ip_wrap.mock_calls)
|
||||
|
||||
def test_plug_no_gw(self):
|
||||
test_port = {'id': 'port_id',
|
||||
'network_id': 'net_id',
|
||||
'mac_address': 'mac_addr',
|
||||
'fixed_ips': [{'ip_address': '10.0.0.2',
|
||||
'subnet': {'cidr': '10.0.0.0/24'}}]}
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.agent.linux.ip_lib.device_exists'),
|
||||
mock.patch('netaddr.IPNetwork'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
) as (dev_exists, ip_net, ip_wrap):
|
||||
self.vif_driver.get_device_name.return_value = 'test_interface'
|
||||
dev_exists.return_value = False
|
||||
ip_net.return_value = ip_net
|
||||
ip_net.prefixlen = 24
|
||||
|
||||
self.driver._plug('test_ns', test_port)
|
||||
self.rpc_mock.plug_vip_port.assert_called_once_with(
|
||||
test_port['id'])
|
||||
self.assertTrue(dev_exists.called)
|
||||
self.vif_driver.plug.assert_called_once_with('net_id', 'port_id',
|
||||
'test_interface',
|
||||
'mac_addr',
|
||||
namespace='test_ns')
|
||||
self.vif_driver.init_l3.assert_called_once_with(
|
||||
'test_interface',
|
||||
['10.0.0.2/24'],
|
||||
namespace='test_ns'
|
||||
)
|
||||
self.assertFalse(ip_wrap.called)
|
||||
dev_exists.return_value = True
|
||||
self.assertRaises(exceptions.PreexistingDeviceFailure,
|
||||
self.driver._plug, 'test_ns', test_port, False)
|
||||
|
||||
def test_plug_gw_in_host_routes(self):
|
||||
test_port = {'id': 'port_id',
|
||||
'network_id': 'net_id',
|
||||
'mac_address': 'mac_addr',
|
||||
'fixed_ips': [{'ip_address': '10.0.0.2',
|
||||
'subnet': {'cidr': '10.0.0.0/24',
|
||||
'host_routes':
|
||||
[{'destination': '0.0.0.0/0',
|
||||
'nexthop': '10.0.0.1'}]}}]}
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.agent.linux.ip_lib.device_exists'),
|
||||
mock.patch('netaddr.IPNetwork'),
|
||||
mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
|
||||
) as (dev_exists, ip_net, ip_wrap):
|
||||
self.vif_driver.get_device_name.return_value = 'test_interface'
|
||||
dev_exists.return_value = False
|
||||
ip_net.return_value = ip_net
|
||||
ip_net.prefixlen = 24
|
||||
|
||||
self.driver._plug('test_ns', test_port)
|
||||
self.rpc_mock.plug_vip_port.assert_called_once_with(
|
||||
test_port['id'])
|
||||
self.assertTrue(dev_exists.called)
|
||||
self.vif_driver.plug.assert_called_once_with('net_id', 'port_id',
|
||||
'test_interface',
|
||||
'mac_addr',
|
||||
namespace='test_ns')
|
||||
self.vif_driver.init_l3.assert_called_once_with(
|
||||
'test_interface',
|
||||
['10.0.0.2/24'],
|
||||
namespace='test_ns'
|
||||
)
|
||||
cmd = ['route', 'add', 'default', 'gw', '10.0.0.1']
|
||||
ip_wrap.assert_has_calls([
|
||||
mock.call('sudo_test', namespace='test_ns'),
|
||||
mock.call().netns.execute(cmd, check_exit_code=False),
|
||||
])
|
||||
|
||||
def test_unplug(self):
|
||||
self.vif_driver.get_device_name.return_value = 'test_interface'
|
||||
|
||||
self.driver._unplug('test_ns', 'port_id')
|
||||
self.rpc_mock.unplug_vip_port.assert_called_once_with('port_id')
|
||||
self.vif_driver.unplug('test_interface', namespace='test_ns')
|
||||
|
||||
def test_kill_pids_in_file(self):
|
||||
with contextlib.nested(
|
||||
mock.patch('os.path.exists'),
|
||||
mock.patch('__builtin__.open'),
|
||||
mock.patch('neutron.agent.linux.utils.execute'),
|
||||
mock.patch.object(namespace_driver.LOG, 'exception'),
|
||||
) as (path_exists, mock_open, mock_execute, mock_log):
|
||||
file_mock = mock.MagicMock()
|
||||
mock_open.return_value = file_mock
|
||||
file_mock.__enter__.return_value = file_mock
|
||||
file_mock.__iter__.return_value = iter(['123'])
|
||||
|
||||
path_exists.return_value = False
|
||||
namespace_driver.kill_pids_in_file('sudo_test', 'test_path')
|
||||
path_exists.assert_called_once_with('test_path')
|
||||
self.assertFalse(mock_open.called)
|
||||
self.assertFalse(mock_execute.called)
|
||||
|
||||
path_exists.return_value = True
|
||||
mock_execute.side_effect = RuntimeError
|
||||
namespace_driver.kill_pids_in_file('sudo_test', 'test_path')
|
||||
self.assertTrue(mock_log.called)
|
||||
mock_execute.assert_called_once_with(
|
||||
['kill', '-9', '123'], 'sudo_test')
|
||||
|
||||
def test_get_state_file_path(self):
|
||||
with mock.patch('os.makedirs') as mkdir:
|
||||
path = self.driver._get_state_file_path('pool_id', 'conf')
|
||||
self.assertEqual('/the/path/pool_id/conf', path)
|
||||
mkdir.assert_called_once_with('/the/path/pool_id', 0o755)
|
||||
|
||||
def test_deploy_instance(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
with mock.patch.object(self.driver, 'update') as update:
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
exists.assert_called_once_with(self.fake_config['pool']['id'])
|
||||
update.assert_called_once_with(self.fake_config)
|
||||
|
||||
def test_deploy_instance_non_existing(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
with mock.patch.object(self.driver, 'create') as create:
|
||||
exists.return_value = False
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
exists.assert_called_once_with(self.fake_config['pool']['id'])
|
||||
create.assert_called_once_with(self.fake_config)
|
||||
|
||||
def test_deploy_instance_vip_status_non_active(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
self.fake_config['vip']['status'] = 'NON_ACTIVE'
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
self.assertFalse(exists.called)
|
||||
|
||||
def test_deploy_instance_vip_admin_state_down(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
self.fake_config['vip']['admin_state_up'] = False
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
self.assertFalse(exists.called)
|
||||
|
||||
def test_deploy_instance_no_vip(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
del self.fake_config['vip']
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
self.assertFalse(exists.called)
|
||||
|
||||
def test_deploy_instance_pool_status_non_active(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
self.fake_config['pool']['status'] = 'NON_ACTIVE'
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
self.assertFalse(exists.called)
|
||||
|
||||
def test_deploy_instance_pool_admin_state_down(self):
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
self.fake_config['pool']['admin_state_up'] = False
|
||||
self.driver.deploy_instance(self.fake_config)
|
||||
self.assertFalse(exists.called)
|
||||
|
||||
def test_refresh_device(self):
|
||||
with mock.patch.object(self.driver, 'deploy_instance') as deploy:
|
||||
pool_id = 'pool_id1'
|
||||
self.driver._refresh_device(pool_id)
|
||||
self.rpc_mock.get_logical_device.assert_called_once_with(pool_id)
|
||||
deploy.assert_called_once_with(
|
||||
self.rpc_mock.get_logical_device.return_value)
|
||||
|
||||
def test_create_vip(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.create_vip({'pool_id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_update_vip(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.update_vip({}, {'pool_id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_delete_vip(self):
|
||||
with mock.patch.object(self.driver, 'undeploy_instance') as undeploy:
|
||||
self.driver.delete_vip({'pool_id': '1'})
|
||||
undeploy.assert_called_once_with('1')
|
||||
|
||||
def test_create_pool(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.create_pool({'id': '1'})
|
||||
self.assertFalse(refresh.called)
|
||||
|
||||
def test_update_pool(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.update_pool({}, {'id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_delete_pool_existing(self):
|
||||
with mock.patch.object(self.driver, 'undeploy_instance') as undeploy:
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
exists.return_value = True
|
||||
self.driver.delete_pool({'id': '1'})
|
||||
undeploy.assert_called_once_with('1')
|
||||
|
||||
def test_delete_pool_non_existing(self):
|
||||
with mock.patch.object(self.driver, 'undeploy_instance') as undeploy:
|
||||
with mock.patch.object(self.driver, 'exists') as exists:
|
||||
exists.return_value = False
|
||||
self.driver.delete_pool({'id': '1'})
|
||||
self.assertFalse(undeploy.called)
|
||||
|
||||
def test_create_member(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.create_member({'pool_id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_update_member(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.update_member({}, {'pool_id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_delete_member(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.delete_member({'pool_id': '1'})
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_create_pool_health_monitor(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.create_pool_health_monitor('', '1')
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_update_pool_health_monitor(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.update_pool_health_monitor('', '', '1')
|
||||
refresh.assert_called_once_with('1')
|
||||
|
||||
def test_delete_pool_health_monitor(self):
|
||||
with mock.patch.object(self.driver, '_refresh_device') as refresh:
|
||||
self.driver.delete_pool_health_monitor('', '1')
|
||||
refresh.assert_called_once_with('1')
|
|
@ -1,148 +0,0 @@
|
|||
# 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 mock
|
||||
|
||||
from neutron import context
|
||||
from neutron.services.loadbalancer.drivers.logging_noop import driver
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
|
||||
log_path = 'neutron.services.loadbalancer.drivers.logging_noop.driver.LOG'
|
||||
|
||||
|
||||
class FakeModel(object):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
|
||||
def patch_manager(func):
|
||||
@mock.patch(log_path)
|
||||
def wrapper(*args):
|
||||
log_mock = args[-1]
|
||||
manager_test = args[0]
|
||||
model = args[1]
|
||||
parent = manager_test.parent
|
||||
driver = parent.driver
|
||||
driver.plugin.reset_mock()
|
||||
|
||||
func(*args[:-1])
|
||||
|
||||
s = str(log_mock.mock_calls[0])
|
||||
parent.assertEqual(s[:11], "call.debug(")
|
||||
parent.assertTrue(s.index(model.id) != -1,
|
||||
msg="Model ID not found in log")
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class ManagerTest(object):
|
||||
def __init__(self, parent, manager, model):
|
||||
self.parent = parent
|
||||
self.manager = manager
|
||||
|
||||
self.create(model)
|
||||
self.update(model, model)
|
||||
self.delete(model)
|
||||
|
||||
@patch_manager
|
||||
def create(self, model):
|
||||
self.manager.create(self.parent.context, model)
|
||||
|
||||
@patch_manager
|
||||
def update(self, old_model, model):
|
||||
self.manager.update(self.parent.context, old_model, model)
|
||||
|
||||
@patch_manager
|
||||
def delete(self, model):
|
||||
self.manager.delete(self.parent.context, model)
|
||||
|
||||
|
||||
class ManagerTestWithUpdates(ManagerTest):
|
||||
def __init__(self, parent, manager, model):
|
||||
self.parent = parent
|
||||
self.manager = manager
|
||||
|
||||
self.create(model)
|
||||
self.update(model, model)
|
||||
self.delete(model)
|
||||
|
||||
@patch_manager
|
||||
def create(self, model):
|
||||
self.manager.create(self.parent.context, model)
|
||||
if self.manager.model_class is not None:
|
||||
self.parent.assertEqual(
|
||||
str(self.parent.driver.plugin.mock_calls[0])[:18],
|
||||
"call.update_status")
|
||||
|
||||
@patch_manager
|
||||
def update(self, old_model, model):
|
||||
self.manager.update(self.parent.context, old_model, model)
|
||||
if self.manager.model_class is not None:
|
||||
self.parent.assertEqual(
|
||||
str(self.parent.driver.plugin.mock_calls[0])[:18],
|
||||
"call.update_status")
|
||||
|
||||
@patch_manager
|
||||
def delete(self, model):
|
||||
self.manager.delete(self.parent.context, model)
|
||||
|
||||
|
||||
class LoadBalancerManagerTest(ManagerTestWithUpdates):
|
||||
def __init__(self, parent, manager, model):
|
||||
super(LoadBalancerManagerTest, self).__init__(parent, manager, model)
|
||||
|
||||
self.refresh(model)
|
||||
self.stats(model)
|
||||
|
||||
@patch_manager
|
||||
def refresh(self, model):
|
||||
self.manager.refresh(self.parent.context, model)
|
||||
|
||||
@patch_manager
|
||||
def stats(self, model):
|
||||
dummy_stats = {
|
||||
"bytes_in": 0,
|
||||
"bytes_out": 0,
|
||||
"active_connections": 0,
|
||||
"total_connections": 0
|
||||
}
|
||||
h = self.manager.stats(self.parent.context, model)
|
||||
self.parent.assertEqual(h, dummy_stats)
|
||||
|
||||
|
||||
class TestLoggingNoopLoadBalancerDriver(
|
||||
test_db_loadbalancer.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoggingNoopLoadBalancerDriver, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.plugin = mock.Mock()
|
||||
self.driver = driver.LoggingNoopLoadBalancerDriver(self.plugin)
|
||||
|
||||
def test_load_balancer_ops(self):
|
||||
LoadBalancerManagerTest(self, self.driver.load_balancer,
|
||||
FakeModel("loadbalancer-001"))
|
||||
|
||||
def test_listener_ops(self):
|
||||
ManagerTest(self, self.driver.listener, FakeModel("listener-001"))
|
||||
|
||||
def test_pool_ops(self):
|
||||
ManagerTestWithUpdates(self, self.driver.pool, FakeModel("pool-001"))
|
||||
|
||||
def test_member_ops(self):
|
||||
ManagerTestWithUpdates(self, self.driver.member,
|
||||
FakeModel("member-001"))
|
||||
|
||||
def test_health_monitor_ops(self):
|
||||
ManagerTest(self, self.driver.health_monitor, FakeModel("hm-001"))
|
|
@ -1,204 +0,0 @@
|
|||
# Copyright 2014 Citrix Systems
|
||||
#
|
||||
# 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 requests
|
||||
|
||||
from neutron.services.loadbalancer.drivers.netscaler import ncc_client
|
||||
from neutron.services.loadbalancer.drivers.netscaler import netscaler_driver
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
NCC_CLIENT_CLASS = ('neutron.services.loadbalancer.drivers'
|
||||
'.netscaler.ncc_client.NSClient')
|
||||
|
||||
TESTURI_SCHEME = 'http'
|
||||
TESTURI_HOSTNAME = '1.1.1.1'
|
||||
TESTURI_PORT = 4433
|
||||
TESTURI_PATH = '/ncc_service/1.0'
|
||||
TESTURI = '%s://%s:%s%s' % (TESTURI_SCHEME, TESTURI_HOSTNAME,
|
||||
TESTURI_PORT, TESTURI_PATH)
|
||||
TEST_USERNAME = 'user211'
|
||||
TEST_PASSWORD = '@30xHl5cT'
|
||||
TEST_TENANT_ID = '9c5245a2-0432-9d4c-4829-9bd7028603a1'
|
||||
TESTVIP_ID = '52ab5d71-6bb2-457f-8414-22a4ba55efec'
|
||||
|
||||
|
||||
class TestNSClient(testlib_api.WebTestCase):
|
||||
|
||||
"""A Unit test for the NetScaler NCC client module."""
|
||||
|
||||
def setUp(self):
|
||||
self.log = mock.patch.object(ncc_client, 'LOG').start()
|
||||
super(TestNSClient, self).setUp()
|
||||
# mock the requests.request function call
|
||||
self.request_method_mock = mock.Mock()
|
||||
requests.request = self.request_method_mock
|
||||
self.testclient = self._get_nsclient()
|
||||
|
||||
def test_instantiate_nsclient_with_empty_uri(self):
|
||||
"""Asserts that a call with empty URI will raise an exception."""
|
||||
self.assertRaises(ncc_client.NCCException, ncc_client.NSClient,
|
||||
'', TEST_USERNAME, TEST_PASSWORD)
|
||||
|
||||
def test_create_resource_with_no_connection(self):
|
||||
"""Asserts that a call with no connection will raise an exception."""
|
||||
# mock a connection object that fails to establish a connection
|
||||
self.request_method_mock.side_effect = (
|
||||
requests.exceptions.ConnectionError())
|
||||
resource_path = netscaler_driver.VIPS_RESOURCE
|
||||
resource_name = netscaler_driver.VIP_RESOURCE
|
||||
resource_body = self._get_testvip_httpbody_for_create()
|
||||
# call method under test: create_resource() and assert that
|
||||
# it raises an exception
|
||||
self.assertRaises(ncc_client.NCCException,
|
||||
self.testclient.create_resource,
|
||||
TEST_TENANT_ID, resource_path,
|
||||
resource_name, resource_body)
|
||||
|
||||
def test_create_resource_with_error(self):
|
||||
"""Asserts that a failed create call raises an exception."""
|
||||
# create a mock object to represent a valid http response
|
||||
# with a failure status code.
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.unauthorized
|
||||
fake_response.headers = []
|
||||
requests.request.return_value = fake_response
|
||||
resource_path = netscaler_driver.VIPS_RESOURCE
|
||||
resource_name = netscaler_driver.VIP_RESOURCE
|
||||
resource_body = self._get_testvip_httpbody_for_create()
|
||||
# call method under test: create_resource
|
||||
# and assert that it raises the expected exception.
|
||||
self.assertRaises(ncc_client.NCCException,
|
||||
self.testclient.create_resource,
|
||||
TEST_TENANT_ID, resource_path,
|
||||
resource_name, resource_body)
|
||||
|
||||
def test_create_resource(self):
|
||||
"""Asserts that a correct call will succeed."""
|
||||
# obtain the mock object that corresponds to the call of request()
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.created
|
||||
fake_response.headers = []
|
||||
self.request_method_mock.return_value = fake_response
|
||||
resource_path = netscaler_driver.VIPS_RESOURCE
|
||||
resource_name = netscaler_driver.VIP_RESOURCE
|
||||
resource_body = self._get_testvip_httpbody_for_create()
|
||||
# call method under test: create_resource()
|
||||
self.testclient.create_resource(TEST_TENANT_ID, resource_path,
|
||||
resource_name, resource_body)
|
||||
# assert that request() was called
|
||||
# with the expected params.
|
||||
resource_url = "%s/%s" % (self.testclient.service_uri, resource_path)
|
||||
self.request_method_mock.assert_called_once_with(
|
||||
'POST',
|
||||
url=resource_url,
|
||||
headers=mock.ANY,
|
||||
data=mock.ANY)
|
||||
|
||||
def test_update_resource_with_error(self):
|
||||
"""Asserts that a failed update call raises an exception."""
|
||||
# create a valid http response with a failure status code.
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.unauthorized
|
||||
fake_response.headers = []
|
||||
# obtain the mock object that corresponds to the call of request()
|
||||
self.request_method_mock.return_value = fake_response
|
||||
resource_path = "%s/%s" % (netscaler_driver.VIPS_RESOURCE,
|
||||
TESTVIP_ID)
|
||||
resource_name = netscaler_driver.VIP_RESOURCE
|
||||
resource_body = self._get_testvip_httpbody_for_update()
|
||||
# call method under test: update_resource() and
|
||||
# assert that it raises the expected exception.
|
||||
self.assertRaises(ncc_client.NCCException,
|
||||
self.testclient.update_resource,
|
||||
TEST_TENANT_ID, resource_path,
|
||||
resource_name, resource_body)
|
||||
|
||||
def test_update_resource(self):
|
||||
"""Asserts that a correct update call will succeed."""
|
||||
# create a valid http response with a successful status code.
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.ok
|
||||
fake_response.headers = []
|
||||
# obtain the mock object that corresponds to the call of request()
|
||||
self.request_method_mock.return_value = fake_response
|
||||
resource_path = "%s/%s" % (netscaler_driver.VIPS_RESOURCE,
|
||||
TESTVIP_ID)
|
||||
resource_name = netscaler_driver.VIP_RESOURCE
|
||||
resource_body = self._get_testvip_httpbody_for_update()
|
||||
# call method under test: update_resource.
|
||||
self.testclient.update_resource(TEST_TENANT_ID, resource_path,
|
||||
resource_name, resource_body)
|
||||
resource_url = "%s/%s" % (self.testclient.service_uri, resource_path)
|
||||
# assert that requests.request() was called with the
|
||||
# expected params.
|
||||
self.request_method_mock.assert_called_once_with(
|
||||
'PUT',
|
||||
url=resource_url,
|
||||
headers=mock.ANY,
|
||||
data=mock.ANY)
|
||||
|
||||
def test_delete_resource_with_error(self):
|
||||
"""Asserts that a failed delete call raises an exception."""
|
||||
# create a valid http response with a failure status code.
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.unauthorized
|
||||
fake_response.headers = []
|
||||
resource_path = "%s/%s" % (netscaler_driver.VIPS_RESOURCE,
|
||||
TESTVIP_ID)
|
||||
# call method under test: create_resource
|
||||
self.assertRaises(ncc_client.NCCException,
|
||||
self.testclient.remove_resource,
|
||||
TEST_TENANT_ID, resource_path)
|
||||
|
||||
def test_delete_resource(self):
|
||||
"""Asserts that a correct delete call will succeed."""
|
||||
# create a valid http response with a failure status code.
|
||||
fake_response = requests.Response()
|
||||
fake_response.status_code = requests.codes.ok
|
||||
fake_response.headers = []
|
||||
# obtain the mock object that corresponds to the call of request()
|
||||
self.request_method_mock.return_value = fake_response
|
||||
resource_path = "%s/%s" % (netscaler_driver.VIPS_RESOURCE,
|
||||
TESTVIP_ID)
|
||||
resource_url = "%s/%s" % (self.testclient.service_uri, resource_path)
|
||||
# call method under test: create_resource
|
||||
self.testclient.remove_resource(TEST_TENANT_ID, resource_path)
|
||||
# assert that httplib.HTTPConnection request() was called with the
|
||||
# expected params
|
||||
self.request_method_mock.assert_called_once_with(
|
||||
'DELETE',
|
||||
url=resource_url,
|
||||
headers=mock.ANY,
|
||||
data=mock.ANY)
|
||||
|
||||
def _get_nsclient(self):
|
||||
return ncc_client.NSClient(TESTURI, TEST_USERNAME, TEST_PASSWORD)
|
||||
|
||||
def _get_testvip_httpbody_for_create(self):
|
||||
body = {
|
||||
'name': 'vip1',
|
||||
'address': '10.0.0.3',
|
||||
'pool_id': 'da477c13-24cd-4c9f-8c19-757a61ef3b9d',
|
||||
'protocol': 'HTTP',
|
||||
'protocol_port': 80,
|
||||
'admin_state_up': True,
|
||||
}
|
||||
return body
|
||||
|
||||
def _get_testvip_httpbody_for_update(self):
|
||||
body = {}
|
||||
body['name'] = 'updated vip1'
|
||||
body['admin_state_up'] = False
|
||||
return body
|
|
@ -1,802 +0,0 @@
|
|||
# Copyright 2014 Citrix Systems
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
|
||||
from neutron.common import exceptions
|
||||
from neutron import context
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.drivers.netscaler import ncc_client
|
||||
from neutron.services.loadbalancer.drivers.netscaler import netscaler_driver
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
|
||||
|
||||
LBAAS_DRIVER_CLASS = ('neutron.services.loadbalancer.drivers'
|
||||
'.netscaler.netscaler_driver'
|
||||
'.NetScalerPluginDriver')
|
||||
|
||||
NCC_CLIENT_CLASS = ('neutron.services.loadbalancer.drivers'
|
||||
'.netscaler.ncc_client'
|
||||
'.NSClient')
|
||||
|
||||
LBAAS_PROVIDER_NAME = 'netscaler'
|
||||
LBAAS_PROVIDER = ('LOADBALANCER:%s:%s:default' %
|
||||
(LBAAS_PROVIDER_NAME, LBAAS_DRIVER_CLASS))
|
||||
|
||||
#Test data
|
||||
TESTVIP_ID = '52ab5d71-6bb2-457f-8414-22a4ba55efec'
|
||||
TESTPOOL_ID = 'da477c13-24cd-4c9f-8c19-757a61ef3b9d'
|
||||
TESTMEMBER_ID = '84dea8bc-3416-4fb0-83f9-2ca6e7173bee'
|
||||
TESTMONITOR_ID = '9b9245a2-0413-4f15-87ef-9a41ef66048c'
|
||||
|
||||
TESTVIP_PORT_ID = '327d9662-ade9-4c74-aaf6-c76f145c1180'
|
||||
TESTPOOL_PORT_ID = '132c1dbb-d3d8-45aa-96e3-71f2ea51651e'
|
||||
TESTPOOL_SNATIP_ADDRESS = '10.0.0.50'
|
||||
TESTPOOL_SNAT_PORT = {
|
||||
'id': TESTPOOL_PORT_ID,
|
||||
'fixed_ips': [{'ip_address': TESTPOOL_SNATIP_ADDRESS}]
|
||||
}
|
||||
TESTVIP_IP = '10.0.1.100'
|
||||
TESTMEMBER_IP = '10.0.0.5'
|
||||
|
||||
|
||||
class TestLoadBalancerPluginBase(test_db_loadbalancer
|
||||
.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerPluginBase, self).setUp(
|
||||
lbaas_provider=LBAAS_PROVIDER)
|
||||
loaded_plugins = manager.NeutronManager().get_service_plugins()
|
||||
self.plugin_instance = loaded_plugins[constants.LOADBALANCER]
|
||||
|
||||
|
||||
class TestNetScalerPluginDriver(TestLoadBalancerPluginBase):
|
||||
|
||||
"""Unit tests for the NetScaler LBaaS driver module."""
|
||||
|
||||
def setUp(self):
|
||||
mock.patch.object(netscaler_driver, 'LOG').start()
|
||||
|
||||
# mock the NSClient class (REST client)
|
||||
client_mock_cls = mock.patch(NCC_CLIENT_CLASS).start()
|
||||
|
||||
#mock the REST methods of the NSClient class
|
||||
self.client_mock_instance = client_mock_cls.return_value
|
||||
self.create_resource_mock = self.client_mock_instance.create_resource
|
||||
self.create_resource_mock.side_effect = mock_create_resource_func
|
||||
self.update_resource_mock = self.client_mock_instance.update_resource
|
||||
self.update_resource_mock.side_effect = mock_update_resource_func
|
||||
self.retrieve_resource_mock = (self.client_mock_instance
|
||||
.retrieve_resource)
|
||||
self.retrieve_resource_mock.side_effect = mock_retrieve_resource_func
|
||||
self.remove_resource_mock = self.client_mock_instance.remove_resource
|
||||
self.remove_resource_mock.side_effect = mock_remove_resource_func
|
||||
super(TestNetScalerPluginDriver, self).setUp()
|
||||
self.plugin_instance.drivers[LBAAS_PROVIDER_NAME] = (
|
||||
netscaler_driver.NetScalerPluginDriver(self.plugin_instance))
|
||||
self.driver = self.plugin_instance.drivers[LBAAS_PROVIDER_NAME]
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def test_create_vip(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
testvip = self._build_testvip_contents(subnet['subnet'],
|
||||
pool['pool'])
|
||||
expectedvip = self._build_expectedvip_contents(
|
||||
testvip,
|
||||
subnet['subnet'])
|
||||
# mock the LBaaS plugin update_status().
|
||||
self._mock_update_status()
|
||||
# reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test
|
||||
self.driver.create_vip(self.context, testvip)
|
||||
# First, assert that create_resource was called once
|
||||
# with expected params.
|
||||
self.create_resource_mock.assert_called_once_with(
|
||||
None,
|
||||
netscaler_driver.VIPS_RESOURCE,
|
||||
netscaler_driver.VIP_RESOURCE,
|
||||
expectedvip)
|
||||
#Finally, assert that the vip object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Vip,
|
||||
expectedvip['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_create_vip_without_connection(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
testvip = self._build_testvip_contents(subnet['subnet'],
|
||||
pool['pool'])
|
||||
expectedvip = self._build_expectedvip_contents(
|
||||
testvip,
|
||||
subnet['subnet'])
|
||||
errorcode = ncc_client.NCCException.CONNECTION_ERROR
|
||||
self.create_resource_mock.side_effect = (
|
||||
ncc_client.NCCException(errorcode))
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
# reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.create_vip(self.context, testvip)
|
||||
# First, assert that update_resource was called once
|
||||
# with expected params.
|
||||
self.create_resource_mock.assert_called_once_with(
|
||||
None,
|
||||
netscaler_driver.VIPS_RESOURCE,
|
||||
netscaler_driver.VIP_RESOURCE,
|
||||
expectedvip)
|
||||
#Finally, assert that the vip object is in ERROR state
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Vip,
|
||||
testvip['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_vip(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet) as vip:
|
||||
updated_vip = self._build_updated_testvip_contents(
|
||||
vip['vip'],
|
||||
subnet['subnet'],
|
||||
pool['pool'])
|
||||
expectedvip = self._build_updated_expectedvip_contents(
|
||||
updated_vip,
|
||||
subnet['subnet'],
|
||||
pool['pool'])
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
# reset the update_resource() mock
|
||||
self.update_resource_mock.reset_mock()
|
||||
# execute the method under test
|
||||
self.driver.update_vip(self.context, updated_vip,
|
||||
updated_vip)
|
||||
vip_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.VIPS_RESOURCE,
|
||||
vip['vip']['id']))
|
||||
# First, assert that update_resource was called once
|
||||
# with expected params.
|
||||
(self.update_resource_mock
|
||||
.assert_called_once_with(
|
||||
None,
|
||||
vip_resource_path,
|
||||
netscaler_driver.VIP_RESOURCE,
|
||||
expectedvip))
|
||||
#Finally, assert that the vip object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Vip,
|
||||
vip['vip']['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_delete_vip(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with contextlib.nested(
|
||||
self.vip(pool=pool, subnet=subnet),
|
||||
mock.patch.object(self.driver.plugin, '_delete_db_vip')
|
||||
) as (vip, mock_delete_db_vip):
|
||||
mock_delete_db_vip.return_value = None
|
||||
#reset the remove_resource() mock
|
||||
self.remove_resource_mock.reset_mock()
|
||||
# execute the method under test
|
||||
self.driver.delete_vip(self.context, vip['vip'])
|
||||
vip_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.VIPS_RESOURCE,
|
||||
vip['vip']['id']))
|
||||
# Assert that remove_resource() was called once
|
||||
# with expected params.
|
||||
(self.remove_resource_mock
|
||||
.assert_called_once_with(None, vip_resource_path))
|
||||
|
||||
def test_create_pool(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_ports'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'create_port')
|
||||
) as (subnet, mock_get_subnet, mock_get_ports, mock_create_port):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
mock_get_ports.return_value = None
|
||||
mock_create_port.return_value = TESTPOOL_SNAT_PORT
|
||||
testpool = self._build_testpool_contents(subnet['subnet'])
|
||||
expectedpool = self._build_expectedpool_contents(testpool,
|
||||
subnet['subnet'])
|
||||
#reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
# execute the method under test
|
||||
self.driver.create_pool(self.context, testpool)
|
||||
# First, assert that create_resource was called once
|
||||
# with expected params.
|
||||
(self.create_resource_mock
|
||||
.assert_called_once_with(None,
|
||||
netscaler_driver.POOLS_RESOURCE,
|
||||
netscaler_driver.POOL_RESOURCE,
|
||||
expectedpool))
|
||||
#Finally, assert that the pool object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Pool,
|
||||
expectedpool['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_create_pool_with_error(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_ports'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'create_port')
|
||||
) as (subnet, mock_get_subnet, mock_get_ports, mock_create_port):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
mock_get_ports.return_value = None
|
||||
mock_create_port.return_value = TESTPOOL_SNAT_PORT
|
||||
errorcode = ncc_client.NCCException.CONNECTION_ERROR
|
||||
self.create_resource_mock.side_effect = (ncc_client
|
||||
.NCCException(errorcode))
|
||||
testpool = self._build_testpool_contents(subnet['subnet'])
|
||||
expectedpool = self._build_expectedpool_contents(testpool,
|
||||
subnet['subnet'])
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
#reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.create_pool(self.context, testpool)
|
||||
# Also assert that create_resource was called once
|
||||
# with expected params.
|
||||
(self.create_resource_mock
|
||||
.assert_called_once_with(None,
|
||||
netscaler_driver.POOLS_RESOURCE,
|
||||
netscaler_driver.POOL_RESOURCE,
|
||||
expectedpool))
|
||||
#Finally, assert that the pool object is in ERROR state
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Pool,
|
||||
expectedpool['id'],
|
||||
constants.ERROR)
|
||||
|
||||
def test_create_pool_with_snatportcreate_failure(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_ports'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'create_port')
|
||||
) as (subnet, mock_get_subnet, mock_get_ports, mock_create_port):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
mock_get_ports.return_value = None
|
||||
mock_create_port.side_effect = exceptions.NeutronException()
|
||||
testpool = self._build_testpool_contents(subnet['subnet'])
|
||||
#reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.assertRaises(exceptions.NeutronException,
|
||||
self.driver.create_pool,
|
||||
self.context, testpool)
|
||||
|
||||
def test_update_pool(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
updated_pool = self._build_updated_testpool_contents(
|
||||
pool['pool'],
|
||||
subnet['subnet'])
|
||||
expectedpool = self._build_updated_expectedpool_contents(
|
||||
updated_pool,
|
||||
subnet['subnet'])
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
# reset the update_resource() mock
|
||||
self.update_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.update_pool(self.context, pool['pool'],
|
||||
updated_pool)
|
||||
pool_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.POOLS_RESOURCE,
|
||||
pool['pool']['id']))
|
||||
# First, assert that update_resource was called once
|
||||
# with expected params.
|
||||
(self.update_resource_mock
|
||||
.assert_called_once_with(None,
|
||||
pool_resource_path,
|
||||
netscaler_driver.POOL_RESOURCE,
|
||||
expectedpool))
|
||||
#Finally, assert that the pool object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Pool,
|
||||
pool['pool']['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_delete_pool(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with contextlib.nested(
|
||||
self.pool(provider=LBAAS_PROVIDER_NAME),
|
||||
mock.patch.object(self.driver.plugin._core_plugin,
|
||||
'delete_port'),
|
||||
mock.patch.object(self.driver.plugin._core_plugin,
|
||||
'get_ports'),
|
||||
mock.patch.object(self.driver.plugin,
|
||||
'get_pools'),
|
||||
mock.patch.object(self.driver.plugin,
|
||||
'_delete_db_pool')
|
||||
) as (pool, mock_delete_port, mock_get_ports, mock_get_pools,
|
||||
mock_delete_db_pool):
|
||||
mock_delete_port.return_value = None
|
||||
mock_get_ports.return_value = [{'id': TESTPOOL_PORT_ID}]
|
||||
mock_get_pools.return_value = []
|
||||
mock_delete_db_pool.return_value = None
|
||||
#reset the remove_resource() mock
|
||||
self.remove_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.delete_pool(self.context, pool['pool'])
|
||||
pool_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.POOLS_RESOURCE,
|
||||
pool['pool']['id']))
|
||||
# Assert that delete_resource was called
|
||||
# once with expected params.
|
||||
(self.remove_resource_mock
|
||||
.assert_called_once_with(None, pool_resource_path))
|
||||
|
||||
def test_create_member(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin,
|
||||
'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
testmember = self._build_testmember_contents(pool['pool'])
|
||||
expectedmember = self._build_expectedmember_contents(
|
||||
testmember)
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
#reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.create_member(self.context, testmember)
|
||||
# First, assert that create_resource was called once
|
||||
# with expected params.
|
||||
(self.create_resource_mock
|
||||
.assert_called_once_with(
|
||||
None,
|
||||
netscaler_driver.POOLMEMBERS_RESOURCE,
|
||||
netscaler_driver.POOLMEMBER_RESOURCE,
|
||||
expectedmember))
|
||||
#Finally, assert that the member object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Member,
|
||||
expectedmember['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_member(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with self.member(pool_id=pool['pool']['id']) as member:
|
||||
updatedmember = (self._build_updated_testmember_contents(
|
||||
member['member']))
|
||||
expectedmember = (self
|
||||
._build_updated_expectedmember_contents(
|
||||
updatedmember))
|
||||
# mock the plugin's update_status()
|
||||
self._mock_update_status()
|
||||
# reset the update_resource() mock
|
||||
self.update_resource_mock.reset_mock()
|
||||
# execute the method under test
|
||||
self.driver.update_member(self.context,
|
||||
member['member'],
|
||||
updatedmember)
|
||||
member_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.POOLMEMBERS_RESOURCE,
|
||||
member['member']['id']))
|
||||
# First, assert that update_resource was called once
|
||||
# with expected params.
|
||||
(self.update_resource_mock
|
||||
.assert_called_once_with(
|
||||
None,
|
||||
member_resource_path,
|
||||
netscaler_driver.POOLMEMBER_RESOURCE,
|
||||
expectedmember))
|
||||
#Finally, assert that the member object is now ACTIVE
|
||||
self.mock_update_status_obj.assert_called_once_with(
|
||||
mock.ANY,
|
||||
loadbalancer_db.Member,
|
||||
member['member']['id'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_delete_member(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with contextlib.nested(
|
||||
self.member(pool_id=pool['pool']['id']),
|
||||
mock.patch.object(self.driver.plugin, '_delete_db_member')
|
||||
) as (member, mock_delete_db_member):
|
||||
mock_delete_db_member.return_value = None
|
||||
# reset the remove_resource() mock
|
||||
self.remove_resource_mock.reset_mock()
|
||||
# execute the method under test
|
||||
self.driver.delete_member(self.context,
|
||||
member['member'])
|
||||
member_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.POOLMEMBERS_RESOURCE,
|
||||
member['member']['id']))
|
||||
# Assert that delete_resource was called once
|
||||
# with expected params.
|
||||
(self.remove_resource_mock
|
||||
.assert_called_once_with(None, member_resource_path))
|
||||
|
||||
def test_create_pool_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
testhealthmonitor = self._build_testhealthmonitor_contents(
|
||||
pool['pool'])
|
||||
expectedhealthmonitor = (
|
||||
self._build_expectedhealthmonitor_contents(
|
||||
testhealthmonitor))
|
||||
with mock.patch.object(self.driver.plugin,
|
||||
'update_pool_health_monitor') as mhm:
|
||||
# reset the create_resource() mock
|
||||
self.create_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.create_pool_health_monitor(self.context,
|
||||
testhealthmonitor,
|
||||
pool['pool']['id'])
|
||||
# First, assert that create_resource was called once
|
||||
# with expected params.
|
||||
resource_path = "%s/%s/%s" % (
|
||||
netscaler_driver.POOLS_RESOURCE,
|
||||
pool['pool']['id'],
|
||||
netscaler_driver.MONITORS_RESOURCE)
|
||||
(self.create_resource_mock
|
||||
.assert_called_once_with(
|
||||
None,
|
||||
resource_path,
|
||||
netscaler_driver.MONITOR_RESOURCE,
|
||||
expectedhealthmonitor))
|
||||
# Finally, assert that the healthmonitor object is
|
||||
# now ACTIVE.
|
||||
(mhm.assert_called_once_with(
|
||||
mock.ANY,
|
||||
expectedhealthmonitor['id'],
|
||||
pool['pool']['id'],
|
||||
constants.ACTIVE, ""))
|
||||
|
||||
def test_update_pool_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with self.health_monitor(
|
||||
pool_id=pool['pool']['id']
|
||||
) as (health_monitor):
|
||||
updatedhealthmonitor = (
|
||||
self._build_updated_testhealthmonitor_contents(
|
||||
health_monitor['health_monitor']))
|
||||
expectedhealthmonitor = (
|
||||
self._build_updated_expectedhealthmonitor_contents(
|
||||
updatedhealthmonitor))
|
||||
with mock.patch.object(self.driver.plugin,
|
||||
'update_pool_health_monitor')as mhm:
|
||||
# reset the update_resource() mock
|
||||
self.update_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.update_pool_health_monitor(
|
||||
self.context,
|
||||
health_monitor['health_monitor'],
|
||||
updatedhealthmonitor,
|
||||
pool['pool']['id'])
|
||||
monitor_resource_path = "%s/%s" % (
|
||||
(netscaler_driver.MONITORS_RESOURCE,
|
||||
health_monitor['health_monitor']['id']))
|
||||
# First, assert that update_resource was called once
|
||||
# with expected params.
|
||||
self.update_resource_mock.assert_called_once_with(
|
||||
None,
|
||||
monitor_resource_path,
|
||||
netscaler_driver.MONITOR_RESOURCE,
|
||||
expectedhealthmonitor)
|
||||
#Finally, assert that the member object is now ACTIVE
|
||||
(mhm.assert_called_once_with(
|
||||
mock.ANY,
|
||||
health_monitor['health_monitor']['id'],
|
||||
pool['pool']['id'],
|
||||
constants.ACTIVE, ""))
|
||||
|
||||
def test_delete_pool_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
|
||||
) as (subnet, mock_get_subnet):
|
||||
mock_get_subnet.return_value = subnet['subnet']
|
||||
with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
|
||||
with contextlib.nested(
|
||||
self.health_monitor(pool_id=pool['pool']['id']),
|
||||
mock.patch.object(self.driver.plugin,
|
||||
'_delete_db_pool_health_monitor')
|
||||
) as (health_monitor, mock_delete_db_monitor):
|
||||
mock_delete_db_monitor.return_value = None
|
||||
# reset the remove_resource() mock
|
||||
self.remove_resource_mock.reset_mock()
|
||||
# execute the method under test.
|
||||
self.driver.delete_pool_health_monitor(
|
||||
self.context,
|
||||
health_monitor['health_monitor'],
|
||||
pool['pool']['id'])
|
||||
monitor_resource_path = "%s/%s/%s/%s" % (
|
||||
netscaler_driver.POOLS_RESOURCE,
|
||||
pool['pool']['id'],
|
||||
netscaler_driver.MONITORS_RESOURCE,
|
||||
health_monitor['health_monitor']['id'])
|
||||
# Assert that delete_resource was called once
|
||||
# with expected params.
|
||||
self.remove_resource_mock.assert_called_once_with(
|
||||
None,
|
||||
monitor_resource_path)
|
||||
|
||||
def _build_testvip_contents(self, subnet, pool):
|
||||
vip_obj = dict(id=TESTVIP_ID,
|
||||
name='testvip',
|
||||
description='a test vip',
|
||||
tenant_id=self._tenant_id,
|
||||
subnet_id=subnet['id'],
|
||||
address=TESTVIP_IP,
|
||||
port_id=TESTVIP_PORT_ID,
|
||||
pool_id=pool['id'],
|
||||
protocol='HTTP',
|
||||
protocol_port=80,
|
||||
connection_limit=1000,
|
||||
admin_state_up=True,
|
||||
status='PENDING_CREATE',
|
||||
status_description='')
|
||||
return vip_obj
|
||||
|
||||
def _build_expectedvip_contents(self, testvip, subnet):
|
||||
expectedvip = dict(id=testvip['id'],
|
||||
name=testvip['name'],
|
||||
description=testvip['description'],
|
||||
tenant_id=testvip['tenant_id'],
|
||||
subnet_id=testvip['subnet_id'],
|
||||
address=testvip['address'],
|
||||
network_id=subnet['network_id'],
|
||||
port_id=testvip['port_id'],
|
||||
pool_id=testvip['pool_id'],
|
||||
protocol=testvip['protocol'],
|
||||
protocol_port=testvip['protocol_port'],
|
||||
connection_limit=testvip['connection_limit'],
|
||||
admin_state_up=testvip['admin_state_up'])
|
||||
return expectedvip
|
||||
|
||||
def _build_updated_testvip_contents(self, testvip, subnet, pool):
|
||||
#update some updateable fields of the vip
|
||||
testvip['name'] = 'udpated testvip'
|
||||
testvip['description'] = 'An updated version of test vip'
|
||||
testvip['connection_limit'] = 2000
|
||||
return testvip
|
||||
|
||||
def _build_updated_expectedvip_contents(self, testvip, subnet, pool):
|
||||
expectedvip = dict(name=testvip['name'],
|
||||
description=testvip['description'],
|
||||
connection_limit=testvip['connection_limit'],
|
||||
admin_state_up=testvip['admin_state_up'],
|
||||
pool_id=testvip['pool_id'])
|
||||
return expectedvip
|
||||
|
||||
def _build_testpool_contents(self, subnet):
|
||||
pool_obj = dict(id=TESTPOOL_ID,
|
||||
name='testpool',
|
||||
description='a test pool',
|
||||
tenant_id=self._tenant_id,
|
||||
subnet_id=subnet['id'],
|
||||
protocol='HTTP',
|
||||
vip_id=None,
|
||||
admin_state_up=True,
|
||||
lb_method='ROUND_ROBIN',
|
||||
status='PENDING_CREATE',
|
||||
status_description='',
|
||||
members=[],
|
||||
health_monitors=[],
|
||||
health_monitors_status=None,
|
||||
provider=LBAAS_PROVIDER_NAME)
|
||||
return pool_obj
|
||||
|
||||
def _build_expectedpool_contents(self, testpool, subnet):
|
||||
expectedpool = dict(id=testpool['id'],
|
||||
name=testpool['name'],
|
||||
description=testpool['description'],
|
||||
tenant_id=testpool['tenant_id'],
|
||||
subnet_id=testpool['subnet_id'],
|
||||
network_id=subnet['network_id'],
|
||||
protocol=testpool['protocol'],
|
||||
vip_id=testpool['vip_id'],
|
||||
lb_method=testpool['lb_method'],
|
||||
snat_ip=TESTPOOL_SNATIP_ADDRESS,
|
||||
port_id=TESTPOOL_PORT_ID,
|
||||
admin_state_up=testpool['admin_state_up'])
|
||||
return expectedpool
|
||||
|
||||
def _build_updated_testpool_contents(self, testpool, subnet):
|
||||
updated_pool = dict(testpool.items())
|
||||
updated_pool['name'] = 'udpated testpool'
|
||||
updated_pool['description'] = 'An updated version of test pool'
|
||||
updated_pool['lb_method'] = 'LEAST_CONNECTIONS'
|
||||
updated_pool['admin_state_up'] = True
|
||||
updated_pool['provider'] = LBAAS_PROVIDER_NAME
|
||||
updated_pool['status'] = 'PENDING_UPDATE'
|
||||
updated_pool['status_description'] = ''
|
||||
updated_pool['members'] = []
|
||||
updated_pool["health_monitors"] = []
|
||||
updated_pool["health_monitors_status"] = None
|
||||
return updated_pool
|
||||
|
||||
def _build_updated_expectedpool_contents(self, testpool, subnet):
|
||||
expectedpool = dict(name=testpool['name'],
|
||||
description=testpool['description'],
|
||||
lb_method=testpool['lb_method'],
|
||||
admin_state_up=testpool['admin_state_up'])
|
||||
return expectedpool
|
||||
|
||||
def _build_testmember_contents(self, pool):
|
||||
member_obj = dict(
|
||||
id=TESTMEMBER_ID,
|
||||
tenant_id=self._tenant_id,
|
||||
pool_id=pool['id'],
|
||||
address=TESTMEMBER_IP,
|
||||
protocol_port=8080,
|
||||
weight=2,
|
||||
admin_state_up=True,
|
||||
status='PENDING_CREATE',
|
||||
status_description='')
|
||||
return member_obj
|
||||
|
||||
def _build_expectedmember_contents(self, testmember):
|
||||
expectedmember = dict(
|
||||
id=testmember['id'],
|
||||
tenant_id=testmember['tenant_id'],
|
||||
pool_id=testmember['pool_id'],
|
||||
address=testmember['address'],
|
||||
protocol_port=testmember['protocol_port'],
|
||||
weight=testmember['weight'],
|
||||
admin_state_up=testmember['admin_state_up'])
|
||||
return expectedmember
|
||||
|
||||
def _build_updated_testmember_contents(self, testmember):
|
||||
updated_member = dict(testmember.items())
|
||||
updated_member.update(
|
||||
weight=3,
|
||||
admin_state_up=True,
|
||||
status='PENDING_CREATE',
|
||||
status_description=''
|
||||
)
|
||||
return updated_member
|
||||
|
||||
def _build_updated_expectedmember_contents(self, testmember):
|
||||
expectedmember = dict(weight=testmember['weight'],
|
||||
pool_id=testmember['pool_id'],
|
||||
admin_state_up=testmember['admin_state_up'])
|
||||
return expectedmember
|
||||
|
||||
def _build_testhealthmonitor_contents(self, pool):
|
||||
monitor_obj = dict(
|
||||
id=TESTMONITOR_ID,
|
||||
tenant_id=self._tenant_id,
|
||||
type='TCP',
|
||||
delay=10,
|
||||
timeout=5,
|
||||
max_retries=3,
|
||||
admin_state_up=True,
|
||||
pools=[])
|
||||
pool_obj = dict(status='PENDING_CREATE',
|
||||
status_description=None,
|
||||
pool_id=pool['id'])
|
||||
monitor_obj['pools'].append(pool_obj)
|
||||
return monitor_obj
|
||||
|
||||
def _build_expectedhealthmonitor_contents(self, testhealthmonitor):
|
||||
expectedmonitor = dict(id=testhealthmonitor['id'],
|
||||
tenant_id=testhealthmonitor['tenant_id'],
|
||||
type=testhealthmonitor['type'],
|
||||
delay=testhealthmonitor['delay'],
|
||||
timeout=testhealthmonitor['timeout'],
|
||||
max_retries=testhealthmonitor['max_retries'],
|
||||
admin_state_up=(
|
||||
testhealthmonitor['admin_state_up']))
|
||||
return expectedmonitor
|
||||
|
||||
def _build_updated_testhealthmonitor_contents(self, testmonitor):
|
||||
updated_monitor = dict(testmonitor.items())
|
||||
updated_monitor.update(
|
||||
delay=30,
|
||||
timeout=3,
|
||||
max_retries=5,
|
||||
admin_state_up=True
|
||||
)
|
||||
return updated_monitor
|
||||
|
||||
def _build_updated_expectedhealthmonitor_contents(self, testmonitor):
|
||||
expectedmonitor = dict(delay=testmonitor['delay'],
|
||||
timeout=testmonitor['timeout'],
|
||||
max_retries=testmonitor['max_retries'],
|
||||
admin_state_up=testmonitor['admin_state_up'])
|
||||
return expectedmonitor
|
||||
|
||||
def _mock_update_status(self):
|
||||
#patch the plugin's update_status() method with a mock object
|
||||
self.mock_update_status_patcher = mock.patch.object(
|
||||
self.driver.plugin,
|
||||
'update_status')
|
||||
self.mock_update_status_obj = self.mock_update_status_patcher.start()
|
||||
|
||||
|
||||
def mock_create_resource_func(*args, **kwargs):
|
||||
return 201, {}
|
||||
|
||||
|
||||
def mock_update_resource_func(*args, **kwargs):
|
||||
return 202, {}
|
||||
|
||||
|
||||
def mock_retrieve_resource_func(*args, **kwargs):
|
||||
return 200, {}
|
||||
|
||||
|
||||
def mock_remove_resource_func(*args, **kwargs):
|
||||
return 200, {}
|
|
@ -1,997 +0,0 @@
|
|||
# Copyright 2013 Radware LTD.
|
||||
#
|
||||
# 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 re
|
||||
|
||||
import contextlib
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
from oslo.serialization import jsonutils
|
||||
from six.moves import queue as Queue
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron import context
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.drivers.radware import driver
|
||||
from neutron.services.loadbalancer.drivers.radware import exceptions as r_exc
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
|
||||
GET_200 = ('/api/workflow/', '/api/service/', '/api/workflowTemplate')
|
||||
SERVER_DOWN_CODES = (-1, 301, 307)
|
||||
|
||||
|
||||
class QueueMock(Queue.Queue):
|
||||
def __init__(self, completion_handler):
|
||||
self.completion_handler = completion_handler
|
||||
super(QueueMock, self).__init__()
|
||||
|
||||
def put_nowait(self, oper):
|
||||
self.completion_handler(oper)
|
||||
|
||||
|
||||
def _recover_function_mock(action, resource, data, headers, binary=False):
|
||||
pass
|
||||
|
||||
|
||||
def rest_call_function_mock(action, resource, data, headers, binary=False):
|
||||
if rest_call_function_mock.RESPOND_WITH_ERROR:
|
||||
return 400, 'error_status', 'error_description', None
|
||||
if rest_call_function_mock.RESPOND_WITH_SERVER_DOWN in SERVER_DOWN_CODES:
|
||||
val = rest_call_function_mock.RESPOND_WITH_SERVER_DOWN
|
||||
return val, 'error_status', 'error_description', None
|
||||
if action == 'GET':
|
||||
return _get_handler(resource)
|
||||
elif action == 'DELETE':
|
||||
return _delete_handler(resource)
|
||||
elif action == 'POST':
|
||||
return _post_handler(resource, binary)
|
||||
else:
|
||||
return 0, None, None, None
|
||||
|
||||
|
||||
def _get_handler(resource):
|
||||
if resource == GET_200[2]:
|
||||
if rest_call_function_mock.TEMPLATES_MISSING:
|
||||
data = jsonutils.loads('[]')
|
||||
else:
|
||||
data = jsonutils.loads(
|
||||
'[{"name":"openstack_l2_l3"},{"name":"openstack_l4"}]'
|
||||
)
|
||||
return 200, '', '', data
|
||||
|
||||
if resource in GET_200:
|
||||
return 200, '', '', ''
|
||||
else:
|
||||
data = jsonutils.loads('{"complete":"True", "success": "True"}')
|
||||
return 202, '', '', data
|
||||
|
||||
|
||||
def _delete_handler(resource):
|
||||
return 404, '', '', {'message': 'Not Found'}
|
||||
|
||||
|
||||
def _post_handler(resource, binary):
|
||||
if re.search(r'/api/workflow/.+/action/.+', resource):
|
||||
data = jsonutils.loads('{"uri":"some_uri"}')
|
||||
return 202, '', '', data
|
||||
elif re.search(r'/api/service\?name=.+', resource):
|
||||
data = jsonutils.loads('{"links":{"actions":{"provision":"someuri"}}}')
|
||||
return 201, '', '', data
|
||||
elif binary:
|
||||
return 201, '', '', ''
|
||||
else:
|
||||
return 202, '', '', ''
|
||||
|
||||
RADWARE_PROVIDER = ('LOADBALANCER:radware:neutron.services.'
|
||||
'loadbalancer.drivers.radware.driver.'
|
||||
'LoadBalancerDriver:default')
|
||||
|
||||
|
||||
class TestLoadBalancerPluginBase(
|
||||
test_db_loadbalancer.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerPluginBase, self).setUp(
|
||||
lbaas_provider=RADWARE_PROVIDER)
|
||||
|
||||
loaded_plugins = manager.NeutronManager().get_service_plugins()
|
||||
self.plugin_instance = loaded_plugins[constants.LOADBALANCER]
|
||||
|
||||
|
||||
class TestLoadBalancerPlugin(TestLoadBalancerPluginBase):
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerPlugin, self).setUp()
|
||||
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'RESPOND_WITH_ERROR': False})
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'TEMPLATES_MISSING': False})
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'RESPOND_WITH_SERVER_DOWN': 200})
|
||||
|
||||
self.operation_completer_start_mock = mock.Mock(
|
||||
return_value=None)
|
||||
self.operation_completer_join_mock = mock.Mock(
|
||||
return_value=None)
|
||||
self.driver_rest_call_mock = mock.Mock(
|
||||
side_effect=rest_call_function_mock)
|
||||
self.flip_servers_mock = mock.Mock(
|
||||
return_value=None)
|
||||
self.recover_mock = mock.Mock(
|
||||
side_effect=_recover_function_mock)
|
||||
|
||||
radware_driver = self.plugin_instance.drivers['radware']
|
||||
radware_driver.completion_handler.start = (
|
||||
self.operation_completer_start_mock)
|
||||
radware_driver.completion_handler.join = (
|
||||
self.operation_completer_join_mock)
|
||||
self.orig_call = radware_driver.rest_client.call
|
||||
self.orig__call = radware_driver.rest_client._call
|
||||
radware_driver.rest_client.call = self.driver_rest_call_mock
|
||||
radware_driver.rest_client._call = self.driver_rest_call_mock
|
||||
radware_driver.rest_client._flip_servers = self.flip_servers_mock
|
||||
radware_driver.rest_client._recover = self.recover_mock
|
||||
radware_driver.completion_handler.rest_client.call = (
|
||||
self.driver_rest_call_mock)
|
||||
|
||||
radware_driver.queue = QueueMock(
|
||||
radware_driver.completion_handler.handle_operation_completion)
|
||||
|
||||
self.addCleanup(radware_driver.completion_handler.join)
|
||||
|
||||
def test_get_pip(self):
|
||||
"""Call _get_pip twice and verify that a Port is created once."""
|
||||
port_dict = {'fixed_ips': [{'subnet_id': '10.10.10.10',
|
||||
'ip_address': '11.11.11.11'}]}
|
||||
port_data = {
|
||||
'tenant_id': 'tenant_id',
|
||||
'name': 'port_name',
|
||||
'network_id': 'network_id',
|
||||
'mac_address': attributes.ATTR_NOT_SPECIFIED,
|
||||
'admin_state_up': False,
|
||||
'device_id': '',
|
||||
'device_owner': 'neutron:' + constants.LOADBALANCER,
|
||||
'fixed_ips': [{'subnet_id': '10.10.10.10'}]
|
||||
}
|
||||
self.plugin_instance._core_plugin.get_ports = mock.Mock(
|
||||
return_value=[])
|
||||
self.plugin_instance._core_plugin.create_port = mock.Mock(
|
||||
return_value=port_dict)
|
||||
radware_driver = self.plugin_instance.drivers['radware']
|
||||
radware_driver._get_pip(context.get_admin_context(),
|
||||
'tenant_id', 'port_name',
|
||||
'network_id', '10.10.10.10')
|
||||
self.plugin_instance._core_plugin.get_ports.assert_called_once_with(
|
||||
mock.ANY, filters={'name': ['port_name']})
|
||||
self.plugin_instance._core_plugin.create_port.assert_called_once_with(
|
||||
mock.ANY, {'port': port_data})
|
||||
self.plugin_instance._core_plugin.create_port.reset_mock()
|
||||
self.plugin_instance._core_plugin.get_ports.reset_mock()
|
||||
self.plugin_instance._core_plugin.get_ports.return_value = [port_dict]
|
||||
radware_driver._get_pip(context.get_admin_context(),
|
||||
'tenant_id', 'port_name',
|
||||
'network_id', '10.10.10.10')
|
||||
self.plugin_instance._core_plugin.get_ports.assert_called_once_with(
|
||||
mock.ANY, filters={'name': ['port_name']})
|
||||
self.assertFalse(self.plugin_instance._core_plugin.create_port.called)
|
||||
|
||||
def test_rest_client_recover_was_called(self):
|
||||
"""Call the real REST client and verify _recover is called."""
|
||||
radware_driver = self.plugin_instance.drivers['radware']
|
||||
radware_driver.rest_client.call = self.orig_call
|
||||
radware_driver.rest_client._call = self.orig__call
|
||||
self.assertRaises(r_exc.RESTRequestFailure,
|
||||
radware_driver._verify_workflow_templates)
|
||||
self.recover_mock.assert_called_once_with('GET',
|
||||
'/api/workflowTemplate',
|
||||
None, None, False)
|
||||
|
||||
def test_rest_client_flip_servers(self):
|
||||
radware_driver = self.plugin_instance.drivers['radware']
|
||||
server = radware_driver.rest_client.server
|
||||
sec_server = radware_driver.rest_client.secondary_server
|
||||
radware_driver.rest_client._flip_servers()
|
||||
self.assertEqual(server,
|
||||
radware_driver.rest_client.secondary_server)
|
||||
self.assertEqual(sec_server,
|
||||
radware_driver.rest_client.server)
|
||||
|
||||
def test_verify_workflow_templates_server_down(self):
|
||||
"""Test the rest call failure when backend is down."""
|
||||
for value in SERVER_DOWN_CODES:
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'RESPOND_WITH_SERVER_DOWN': value})
|
||||
self.assertRaises(r_exc.RESTRequestFailure,
|
||||
self.plugin_instance.drivers['radware'].
|
||||
_verify_workflow_templates)
|
||||
|
||||
def test_verify_workflow_templates(self):
|
||||
"""Test the rest call failure handling by Exception raising."""
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'TEMPLATES_MISSING': True})
|
||||
|
||||
self.assertRaises(r_exc.WorkflowMissing,
|
||||
self.plugin_instance.drivers['radware'].
|
||||
_verify_workflow_templates)
|
||||
|
||||
def test_create_vip_failure(self):
|
||||
"""Test the rest call failure handling by Exception raising."""
|
||||
with self.network() as network:
|
||||
with self.subnet(network=network) as subnet:
|
||||
with self.pool(do_delete=False,
|
||||
provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'RESPOND_WITH_ERROR': True})
|
||||
|
||||
self.assertRaises(r_exc.RESTRequestFailure,
|
||||
self.plugin_instance.create_vip,
|
||||
context.get_admin_context(),
|
||||
{'vip': vip_data})
|
||||
|
||||
def test_create_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
|
||||
# Test creation REST calls
|
||||
calls = [
|
||||
mock.call('GET', u'/api/service/srv_' +
|
||||
subnet['subnet']['network_id'], None, None),
|
||||
mock.call('POST', u'/api/service?name=srv_' +
|
||||
subnet['subnet']['network_id'] + '&tenant=' +
|
||||
vip['tenant_id'], mock.ANY,
|
||||
driver.CREATE_SERVICE_HEADER),
|
||||
mock.call('GET', u'/api/workflow/l2_l3_' +
|
||||
subnet['subnet']['network_id'], None, None),
|
||||
mock.call('POST', '/api/workflow/l2_l3_' +
|
||||
subnet['subnet']['network_id'] +
|
||||
'/action/setup_l2_l3',
|
||||
mock.ANY, driver.TEMPLATE_HEADER),
|
||||
mock.call('POST', 'someuri',
|
||||
None, driver.PROVISION_HEADER),
|
||||
|
||||
|
||||
mock.call('POST', '/api/workflowTemplate/' +
|
||||
'openstack_l4' +
|
||||
'?name=' + pool['pool']['id'],
|
||||
mock.ANY,
|
||||
driver.TEMPLATE_HEADER),
|
||||
mock.call('POST', '/api/workflowTemplate/' +
|
||||
'openstack_l2_l3' +
|
||||
'?name=l2_l3_' + subnet['subnet']['network_id'],
|
||||
mock.ANY,
|
||||
driver.TEMPLATE_HEADER),
|
||||
|
||||
mock.call('POST', '/api/workflow/' + pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER),
|
||||
mock.call('GET', '/api/workflow/' +
|
||||
pool['pool']['id'], None, None)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
|
||||
#Test DB
|
||||
new_vip = self.plugin_instance.get_vip(
|
||||
context.get_admin_context(),
|
||||
vip['id']
|
||||
)
|
||||
self.assertEqual(new_vip['status'], constants.ACTIVE)
|
||||
|
||||
# Delete VIP
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
# Test deletion REST calls
|
||||
calls = [
|
||||
mock.call('DELETE', u'/api/workflow/' + pool['pool']['id'],
|
||||
None, None)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
def test_create_vip_2_leg(self):
|
||||
"""Test creation of a VIP where Alteon VIP and PIP are different."""
|
||||
|
||||
with self.subnet(cidr='10.0.0.0/24') as subnet:
|
||||
with self.subnet(cidr='10.0.1.0/24') as pool_sub:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=pool_sub['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
name_suffix = '%s_%s' % (subnet['subnet']['network_id'],
|
||||
pool_sub['subnet']['network_id'])
|
||||
# Test creation REST calls
|
||||
calls = [
|
||||
mock.call('GET', '/api/workflowTemplate', None, None),
|
||||
mock.call('GET', '/api/service/srv_' + name_suffix,
|
||||
None, None),
|
||||
mock.call('POST', '/api/service?name=srv_' +
|
||||
name_suffix + '&tenant=' + vip['tenant_id'],
|
||||
mock.ANY, driver.CREATE_SERVICE_HEADER),
|
||||
mock.call('POST', 'someuri',
|
||||
None, driver.PROVISION_HEADER),
|
||||
mock.call('GET', '/api/workflow/l2_l3_' + name_suffix,
|
||||
None, None),
|
||||
mock.call('POST', '/api/workflowTemplate/' +
|
||||
'openstack_l2_l3' +
|
||||
'?name=l2_l3_' + name_suffix,
|
||||
mock.ANY,
|
||||
driver.TEMPLATE_HEADER),
|
||||
mock.call('POST', '/api/workflow/l2_l3_' +
|
||||
name_suffix + '/action/setup_l2_l3',
|
||||
mock.ANY, driver.TEMPLATE_HEADER),
|
||||
mock.call('GET', '/api/workflow/' +
|
||||
pool['pool']['id'], None, None),
|
||||
mock.call('POST', '/api/workflowTemplate/' +
|
||||
'openstack_l4' +
|
||||
'?name=' + pool['pool']['id'],
|
||||
mock.ANY,
|
||||
driver.TEMPLATE_HEADER),
|
||||
mock.call('POST', '/api/workflow/' +
|
||||
pool['pool']['id'] + '/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(calls)
|
||||
#Test DB
|
||||
new_vip = self.plugin_instance.get_vip(
|
||||
context.get_admin_context(),
|
||||
vip['id']
|
||||
)
|
||||
self.assertEqual(new_vip['status'], constants.ACTIVE)
|
||||
|
||||
# Test that PIP neutron port was created
|
||||
pip_port_filter = {
|
||||
'name': ['pip_' + vip['id']],
|
||||
}
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
num_ports = plugin.get_ports_count(
|
||||
context.get_admin_context(), filters=pip_port_filter)
|
||||
self.assertTrue(num_ports > 0)
|
||||
|
||||
# Delete VIP
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
# Test deletion REST calls
|
||||
calls = [
|
||||
mock.call('DELETE', u'/api/workflow/' +
|
||||
pool['pool']['id'], None, None)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(calls)
|
||||
|
||||
def test_update_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
do_delete=False,
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
|
||||
vip_data['status'] = constants.PENDING_UPDATE
|
||||
self.plugin_instance.update_vip(
|
||||
context.get_admin_context(),
|
||||
vip['id'], {'vip': vip_data})
|
||||
|
||||
# Test REST calls
|
||||
calls = [
|
||||
mock.call('POST', '/api/workflow/' + pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER),
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
updated_vip = self.plugin_instance.get_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
self.assertEqual(updated_vip['status'], constants.ACTIVE)
|
||||
|
||||
# delete VIP
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
def test_update_vip_2_leg(self):
|
||||
"""Test update of a VIP where Alteon VIP and PIP are different."""
|
||||
|
||||
with self.subnet(cidr='10.0.0.0/24') as subnet:
|
||||
with self.subnet(cidr='10.0.1.0/24') as pool_subnet:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=pool_subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
|
||||
self.plugin_instance.update_vip(
|
||||
context.get_admin_context(),
|
||||
vip['id'], {'vip': vip_data})
|
||||
|
||||
# Test REST calls
|
||||
calls = [
|
||||
mock.call('POST', '/api/workflow/' +
|
||||
pool['pool']['id'] + '/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER),
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(calls)
|
||||
|
||||
updated_vip = self.plugin_instance.get_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
self.assertEqual(updated_vip['status'], constants.ACTIVE)
|
||||
|
||||
# delete VIP
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
def test_delete_vip_failure(self):
|
||||
plugin = self.plugin_instance
|
||||
|
||||
with self.network() as network:
|
||||
with self.subnet(network=network) as subnet:
|
||||
with self.pool(do_delete=False,
|
||||
provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
with contextlib.nested(
|
||||
self.member(pool_id=pool['pool']['id'],
|
||||
do_delete=False),
|
||||
self.member(pool_id=pool['pool']['id'],
|
||||
address='192.168.1.101',
|
||||
do_delete=False),
|
||||
self.health_monitor(do_delete=False),
|
||||
self.vip(pool=pool, subnet=subnet, do_delete=False)
|
||||
) as (mem1, mem2, hm, vip):
|
||||
|
||||
plugin.create_pool_health_monitor(
|
||||
context.get_admin_context(), hm, pool['pool']['id']
|
||||
)
|
||||
|
||||
rest_call_function_mock.__dict__.update(
|
||||
{'RESPOND_WITH_ERROR': True})
|
||||
|
||||
plugin.delete_vip(
|
||||
context.get_admin_context(), vip['vip']['id'])
|
||||
|
||||
u_vip = plugin.get_vip(
|
||||
context.get_admin_context(), vip['vip']['id'])
|
||||
u_pool = plugin.get_pool(
|
||||
context.get_admin_context(), pool['pool']['id'])
|
||||
u_mem1 = plugin.get_member(
|
||||
context.get_admin_context(), mem1['member']['id'])
|
||||
u_mem2 = plugin.get_member(
|
||||
context.get_admin_context(), mem2['member']['id'])
|
||||
u_phm = plugin.get_pool_health_monitor(
|
||||
context.get_admin_context(),
|
||||
hm['health_monitor']['id'], pool['pool']['id'])
|
||||
|
||||
self.assertEqual(u_vip['status'], constants.ERROR)
|
||||
self.assertEqual(u_pool['status'], constants.ACTIVE)
|
||||
self.assertEqual(u_mem1['status'], constants.ACTIVE)
|
||||
self.assertEqual(u_mem2['status'], constants.ACTIVE)
|
||||
self.assertEqual(u_phm['status'], constants.ACTIVE)
|
||||
|
||||
def test_delete_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
do_delete=False,
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
calls = [
|
||||
mock.call('DELETE', '/api/workflow/' + pool['pool']['id'],
|
||||
None, None)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
self.assertRaises(loadbalancer.VipNotFound,
|
||||
self.plugin_instance.get_vip,
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
def test_delete_vip_2_leg(self):
|
||||
"""Test deletion of a VIP where Alteon VIP and PIP are different."""
|
||||
|
||||
self.driver_rest_call_mock.reset_mock()
|
||||
with self.subnet(cidr='10.0.0.0/24') as subnet:
|
||||
with self.subnet(cidr='10.0.1.0/24') as pool_subnet:
|
||||
with self.pool(provider='radware',
|
||||
do_delete=False,
|
||||
subnet_id=pool_subnet['subnet']['id']) as pool:
|
||||
vip_data = {
|
||||
'name': 'vip1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'pool_id': pool['pool']['id'],
|
||||
'description': '',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'connection_limit': -1,
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'tenant_id': self._tenant_id,
|
||||
'session_persistence': ''
|
||||
}
|
||||
|
||||
vip = self.plugin_instance.create_vip(
|
||||
context.get_admin_context(), {'vip': vip_data})
|
||||
|
||||
self.plugin_instance.delete_vip(
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
calls = [
|
||||
mock.call('DELETE', '/api/workflow/' +
|
||||
pool['pool']['id'], None, None)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(calls)
|
||||
|
||||
# Test that PIP neutron port was deleted
|
||||
pip_port_filter = {
|
||||
'name': ['pip_' + vip['id']],
|
||||
}
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
num_ports = plugin.get_ports_count(
|
||||
context.get_admin_context(), filters=pip_port_filter)
|
||||
self.assertTrue(num_ports == 0)
|
||||
|
||||
self.assertRaises(loadbalancer.VipNotFound,
|
||||
self.plugin_instance.get_vip,
|
||||
context.get_admin_context(), vip['id'])
|
||||
|
||||
def test_update_pool(self):
|
||||
with self.subnet():
|
||||
with self.pool() as pool:
|
||||
del pool['pool']['provider']
|
||||
del pool['pool']['status']
|
||||
self.plugin_instance.update_pool(
|
||||
context.get_admin_context(),
|
||||
pool['pool']['id'], pool)
|
||||
pool_db = self.plugin_instance.get_pool(
|
||||
context.get_admin_context(), pool['pool']['id'])
|
||||
self.assertEqual(pool_db['status'], constants.PENDING_UPDATE)
|
||||
|
||||
def test_delete_pool_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
do_delete=False,
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet):
|
||||
self.assertRaises(loadbalancer.PoolInUse,
|
||||
self.plugin_instance.delete_pool,
|
||||
context.get_admin_context(),
|
||||
pool['pool']['id'])
|
||||
|
||||
def test_create_member_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as p:
|
||||
with self.vip(pool=p, subnet=subnet):
|
||||
with self.member(pool_id=p['pool']['id']):
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + p['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
),
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + p['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
def test_create_member_on_different_subnets(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
self.subnet(cidr='20.0.0.0/24'),
|
||||
self.subnet(cidr='30.0.0.0/24')
|
||||
) as (vip_sub, pool_sub, member_sub):
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=pool_sub['subnet']['id']) as pool:
|
||||
with contextlib.nested(
|
||||
self.port(subnet=vip_sub,
|
||||
fixed_ips=[{'ip_address': '10.0.0.2'}]),
|
||||
self.port(subnet=pool_sub,
|
||||
fixed_ips=[{'ip_address': '20.0.0.2'}]),
|
||||
self.port(subnet=member_sub,
|
||||
fixed_ips=[{'ip_address': '30.0.0.2'}])
|
||||
):
|
||||
with contextlib.nested(
|
||||
self.member(pool_id=pool['pool']['id'],
|
||||
address='10.0.0.2'),
|
||||
self.member(pool_id=pool['pool']['id'],
|
||||
address='20.0.0.2'),
|
||||
self.member(pool_id=pool['pool']['id'],
|
||||
address='30.0.0.2')
|
||||
) as (member_vip, member_pool, member_out):
|
||||
with self.vip(pool=pool, subnet=vip_sub):
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' +
|
||||
pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
mock_calls = self.driver_rest_call_mock.mock_calls
|
||||
params = mock_calls[-2][1][2]['parameters']
|
||||
member_subnet_array = params['member_subnet_array']
|
||||
member_mask_array = params['member_mask_array']
|
||||
member_gw_array = params['member_gw_array']
|
||||
self.assertEqual(member_subnet_array,
|
||||
['10.0.0.0',
|
||||
'255.255.255.255',
|
||||
'30.0.0.0'])
|
||||
self.assertEqual(member_mask_array,
|
||||
['255.255.255.0',
|
||||
'255.255.255.255',
|
||||
'255.255.255.0'])
|
||||
self.assertEqual(
|
||||
member_gw_array,
|
||||
[pool_sub['subnet']['gateway_ip'],
|
||||
'255.255.255.255',
|
||||
pool_sub['subnet']['gateway_ip']])
|
||||
|
||||
def test_create_member_on_different_subnet_no_port(self):
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
self.subnet(cidr='20.0.0.0/24'),
|
||||
self.subnet(cidr='30.0.0.0/24')
|
||||
) as (vip_sub, pool_sub, member_sub):
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=pool_sub['subnet']['id']) as pool:
|
||||
with self.member(pool_id=pool['pool']['id'],
|
||||
address='30.0.0.2'):
|
||||
with self.vip(pool=pool, subnet=vip_sub):
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' +
|
||||
pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
mock_calls = self.driver_rest_call_mock.mock_calls
|
||||
params = mock_calls[-2][1][2]['parameters']
|
||||
member_subnet_array = params['member_subnet_array']
|
||||
member_mask_array = params['member_mask_array']
|
||||
member_gw_array = params['member_gw_array']
|
||||
self.assertEqual(member_subnet_array,
|
||||
['30.0.0.2'])
|
||||
self.assertEqual(member_mask_array,
|
||||
['255.255.255.255'])
|
||||
self.assertEqual(member_gw_array,
|
||||
[pool_sub['subnet']['gateway_ip']])
|
||||
|
||||
def test_create_member_on_different_subnet_multiple_ports(self):
|
||||
cfg.CONF.set_override("allow_overlapping_ips", 'true')
|
||||
with self.network() as other_net:
|
||||
with contextlib.nested(
|
||||
self.subnet(),
|
||||
self.subnet(cidr='20.0.0.0/24'),
|
||||
self.subnet(cidr='30.0.0.0/24'),
|
||||
self.subnet(network=other_net, cidr='30.0.0.0/24')
|
||||
) as (vip_sub, pool_sub, member_sub1, member_sub2):
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=pool_sub['subnet']['id']) as pool:
|
||||
with contextlib.nested(
|
||||
self.port(subnet=member_sub1,
|
||||
fixed_ips=[{'ip_address': '30.0.0.2'}]),
|
||||
self.port(subnet=member_sub2,
|
||||
fixed_ips=[{'ip_address': '30.0.0.2'}])):
|
||||
with self.member(pool_id=pool['pool']['id'],
|
||||
address='30.0.0.2'):
|
||||
with self.vip(pool=pool, subnet=vip_sub):
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' +
|
||||
pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
calls = self.driver_rest_call_mock.mock_calls
|
||||
params = calls[-2][1][2]['parameters']
|
||||
m_sub_array = params['member_subnet_array']
|
||||
m_mask_array = params['member_mask_array']
|
||||
m_gw_array = params['member_gw_array']
|
||||
self.assertEqual(m_sub_array,
|
||||
['30.0.0.2'])
|
||||
self.assertEqual(m_mask_array,
|
||||
['255.255.255.255'])
|
||||
self.assertEqual(
|
||||
m_gw_array,
|
||||
[pool_sub['subnet']['gateway_ip']])
|
||||
|
||||
def test_update_member_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as p:
|
||||
with self.member(pool_id=p['pool']['id']) as member:
|
||||
with self.vip(pool=p, subnet=subnet):
|
||||
self.plugin_instance.update_member(
|
||||
context.get_admin_context(),
|
||||
member['member']['id'], member
|
||||
)
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + p['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
),
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + p['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
updated_member = self.plugin_instance.get_member(
|
||||
context.get_admin_context(),
|
||||
member['member']['id']
|
||||
)
|
||||
|
||||
updated_member = self.plugin_instance.get_member(
|
||||
context.get_admin_context(),
|
||||
member['member']['id']
|
||||
)
|
||||
self.assertEqual(updated_member['status'],
|
||||
constants.ACTIVE)
|
||||
|
||||
def test_update_member_without_vip(self):
|
||||
with self.subnet():
|
||||
with self.pool(provider='radware') as pool:
|
||||
with self.member(pool_id=pool['pool']['id']) as member:
|
||||
member['member']['status'] = constants.PENDING_UPDATE
|
||||
updated_member = self.plugin_instance.update_member(
|
||||
context.get_admin_context(),
|
||||
member['member']['id'], member
|
||||
)
|
||||
self.assertEqual(updated_member['status'],
|
||||
constants.PENDING_UPDATE)
|
||||
|
||||
def test_delete_member_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as p:
|
||||
with self.member(pool_id=p['pool']['id'],
|
||||
do_delete=False) as m:
|
||||
with self.vip(pool=p, subnet=subnet):
|
||||
|
||||
# Reset mock and
|
||||
# wait for being sure the member
|
||||
# Changed status from PENDING-CREATE
|
||||
# to ACTIVE
|
||||
|
||||
self.plugin_instance.delete_member(
|
||||
context.get_admin_context(),
|
||||
m['member']['id']
|
||||
)
|
||||
|
||||
name, args, kwargs = (
|
||||
self.driver_rest_call_mock.mock_calls[-2]
|
||||
)
|
||||
deletion_post_graph = str(args[2])
|
||||
|
||||
self.assertTrue(re.search(
|
||||
r'.*\'member_address_array\': \[\].*',
|
||||
deletion_post_graph
|
||||
))
|
||||
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + p['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
self.assertRaises(loadbalancer.MemberNotFound,
|
||||
self.plugin_instance.get_member,
|
||||
context.get_admin_context(),
|
||||
m['member']['id'])
|
||||
|
||||
def test_delete_member_without_vip(self):
|
||||
with self.subnet():
|
||||
with self.pool(provider='radware') as p:
|
||||
with self.member(pool_id=p['pool']['id'],
|
||||
do_delete=False) as m:
|
||||
self.plugin_instance.delete_member(
|
||||
context.get_admin_context(), m['member']['id']
|
||||
)
|
||||
self.assertRaises(loadbalancer.MemberNotFound,
|
||||
self.plugin_instance.get_member,
|
||||
context.get_admin_context(),
|
||||
m['member']['id'])
|
||||
|
||||
def test_create_hm_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.health_monitor() as hm:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet):
|
||||
|
||||
self.plugin_instance.create_pool_health_monitor(
|
||||
context.get_admin_context(),
|
||||
hm, pool['pool']['id']
|
||||
)
|
||||
|
||||
# Test REST calls
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
),
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
phm = self.plugin_instance.get_pool_health_monitor(
|
||||
context.get_admin_context(),
|
||||
hm['health_monitor']['id'], pool['pool']['id']
|
||||
)
|
||||
self.assertEqual(phm['status'], constants.ACTIVE)
|
||||
|
||||
def test_delete_pool_hm_with_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.health_monitor(do_delete=False) as hm:
|
||||
with self.pool(provider='radware',
|
||||
subnet_id=subnet['subnet']['id']) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet):
|
||||
self.plugin_instance.create_pool_health_monitor(
|
||||
context.get_admin_context(),
|
||||
hm, pool['pool']['id']
|
||||
)
|
||||
|
||||
self.plugin_instance.delete_pool_health_monitor(
|
||||
context.get_admin_context(),
|
||||
hm['health_monitor']['id'],
|
||||
pool['pool']['id']
|
||||
)
|
||||
|
||||
name, args, kwargs = (
|
||||
self.driver_rest_call_mock.mock_calls[-2]
|
||||
)
|
||||
deletion_post_graph = str(args[2])
|
||||
|
||||
self.assertTrue(re.search(
|
||||
r'.*\'hm_uuid_array\': \[\].*',
|
||||
deletion_post_graph
|
||||
))
|
||||
|
||||
calls = [
|
||||
mock.call(
|
||||
'POST', '/api/workflow/' + pool['pool']['id'] +
|
||||
'/action/BaseCreate',
|
||||
mock.ANY, driver.TEMPLATE_HEADER
|
||||
)
|
||||
]
|
||||
self.driver_rest_call_mock.assert_has_calls(
|
||||
calls, any_order=True)
|
||||
|
||||
self.assertRaises(
|
||||
loadbalancer.PoolMonitorAssociationNotFound,
|
||||
self.plugin_instance.get_pool_health_monitor,
|
||||
context.get_admin_context(),
|
||||
hm['health_monitor']['id'],
|
||||
pool['pool']['id']
|
||||
)
|
|
@ -1,749 +0,0 @@
|
|||
# Copyright 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
from six import moves
|
||||
from webob import exc
|
||||
|
||||
from neutron import context
|
||||
from neutron.db.loadbalancer import loadbalancer_db as ldb
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron.extensions import portbindings
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer.drivers.common import agent_driver_base
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class TestLoadBalancerPluginBase(
|
||||
test_db_loadbalancer.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
def reset_device_driver():
|
||||
agent_driver_base.AgentDriverBase.device_driver = None
|
||||
self.addCleanup(reset_device_driver)
|
||||
|
||||
self.mock_importer = mock.patch.object(
|
||||
agent_driver_base, 'importutils').start()
|
||||
|
||||
# needed to reload provider configuration
|
||||
st_db.ServiceTypeManager._instance = None
|
||||
agent_driver_base.AgentDriverBase.device_driver = 'dummy'
|
||||
super(TestLoadBalancerPluginBase, self).setUp(
|
||||
lbaas_provider=('LOADBALANCER:lbaas:neutron.services.'
|
||||
'loadbalancer.drivers.common.agent_driver_base.'
|
||||
'AgentDriverBase:default'))
|
||||
|
||||
# we need access to loaded plugins to modify models
|
||||
loaded_plugins = manager.NeutronManager().get_service_plugins()
|
||||
|
||||
self.plugin_instance = loaded_plugins[constants.LOADBALANCER]
|
||||
|
||||
|
||||
class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerCallbacks, self).setUp()
|
||||
|
||||
self.callbacks = agent_driver_base.LoadBalancerCallbacks(
|
||||
self.plugin_instance
|
||||
)
|
||||
get_lbaas_agents_patcher = mock.patch(
|
||||
'neutron.services.loadbalancer.agent_scheduler'
|
||||
'.LbaasAgentSchedulerDbMixin.get_lbaas_agents')
|
||||
get_lbaas_agents_patcher.start()
|
||||
|
||||
def test_get_ready_devices(self):
|
||||
with self.vip() as vip:
|
||||
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
|
||||
'.LbaasAgentSchedulerDbMixin.'
|
||||
'list_pools_on_lbaas_agent') as mock_agent_pools:
|
||||
mock_agent_pools.return_value = {
|
||||
'pools': [{'id': vip['vip']['pool_id']}]}
|
||||
ready = self.callbacks.get_ready_devices(
|
||||
context.get_admin_context(),
|
||||
)
|
||||
self.assertEqual(ready, [vip['vip']['pool_id']])
|
||||
|
||||
def test_get_ready_devices_multiple_vips_and_pools(self):
|
||||
ctx = context.get_admin_context()
|
||||
|
||||
# add 3 pools and 2 vips directly to DB
|
||||
# to create 2 "ready" devices and one pool without vip
|
||||
pools = []
|
||||
for i in moves.xrange(3):
|
||||
pools.append(ldb.Pool(id=uuidutils.generate_uuid(),
|
||||
subnet_id=self._subnet_id,
|
||||
protocol="HTTP",
|
||||
lb_method="ROUND_ROBIN",
|
||||
status=constants.ACTIVE,
|
||||
admin_state_up=True))
|
||||
ctx.session.add(pools[i])
|
||||
|
||||
vip0 = ldb.Vip(id=uuidutils.generate_uuid(),
|
||||
protocol_port=80,
|
||||
protocol="HTTP",
|
||||
pool_id=pools[0].id,
|
||||
status=constants.ACTIVE,
|
||||
admin_state_up=True,
|
||||
connection_limit=3)
|
||||
ctx.session.add(vip0)
|
||||
pools[0].vip_id = vip0.id
|
||||
|
||||
vip1 = ldb.Vip(id=uuidutils.generate_uuid(),
|
||||
protocol_port=80,
|
||||
protocol="HTTP",
|
||||
pool_id=pools[1].id,
|
||||
status=constants.ACTIVE,
|
||||
admin_state_up=True,
|
||||
connection_limit=3)
|
||||
ctx.session.add(vip1)
|
||||
pools[1].vip_id = vip1.id
|
||||
|
||||
ctx.session.flush()
|
||||
|
||||
self.assertEqual(ctx.session.query(ldb.Pool).count(), 3)
|
||||
self.assertEqual(ctx.session.query(ldb.Vip).count(), 2)
|
||||
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
|
||||
'.LbaasAgentSchedulerDbMixin'
|
||||
'.list_pools_on_lbaas_agent') as mock_agent_pools:
|
||||
mock_agent_pools.return_value = {'pools': [{'id': pools[0].id},
|
||||
{'id': pools[1].id},
|
||||
{'id': pools[2].id}]}
|
||||
ready = self.callbacks.get_ready_devices(ctx)
|
||||
self.assertEqual(len(ready), 3)
|
||||
self.assertIn(pools[0].id, ready)
|
||||
self.assertIn(pools[1].id, ready)
|
||||
self.assertIn(pools[2].id, ready)
|
||||
# cleanup
|
||||
ctx.session.query(ldb.Pool).delete()
|
||||
ctx.session.query(ldb.Vip).delete()
|
||||
|
||||
def test_get_ready_devices_inactive_vip(self):
|
||||
with self.vip() as vip:
|
||||
|
||||
# set the vip inactive need to use plugin directly since
|
||||
# status is not tenant mutable
|
||||
self.plugin_instance.update_vip(
|
||||
context.get_admin_context(),
|
||||
vip['vip']['id'],
|
||||
{'vip': {'status': constants.INACTIVE}}
|
||||
)
|
||||
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
|
||||
'.LbaasAgentSchedulerDbMixin.'
|
||||
'list_pools_on_lbaas_agent') as mock_agent_pools:
|
||||
mock_agent_pools.return_value = {
|
||||
'pools': [{'id': vip['vip']['pool_id']}]}
|
||||
ready = self.callbacks.get_ready_devices(
|
||||
context.get_admin_context(),
|
||||
)
|
||||
self.assertEqual([vip['vip']['pool_id']], ready)
|
||||
|
||||
def test_get_ready_devices_inactive_pool(self):
|
||||
with self.vip() as vip:
|
||||
|
||||
# set the pool inactive need to use plugin directly since
|
||||
# status is not tenant mutable
|
||||
self.plugin_instance.update_pool(
|
||||
context.get_admin_context(),
|
||||
vip['vip']['pool_id'],
|
||||
{'pool': {'status': constants.INACTIVE}}
|
||||
)
|
||||
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
|
||||
'.LbaasAgentSchedulerDbMixin.'
|
||||
'list_pools_on_lbaas_agent') as mock_agent_pools:
|
||||
mock_agent_pools.return_value = {
|
||||
'pools': [{'id': vip['vip']['pool_id']}]}
|
||||
ready = self.callbacks.get_ready_devices(
|
||||
context.get_admin_context(),
|
||||
)
|
||||
self.assertFalse(ready)
|
||||
|
||||
def test_get_logical_device_non_active(self):
|
||||
with self.pool() as pool:
|
||||
ctx = context.get_admin_context()
|
||||
for status in ('INACTIVE', 'PENDING_CREATE', 'PENDING_UPDATE'):
|
||||
self.plugin_instance.update_status(
|
||||
ctx, ldb.Pool, pool['pool']['id'], status)
|
||||
pool['pool']['status'] = status
|
||||
expected = {
|
||||
'pool': pool['pool'],
|
||||
'members': [],
|
||||
'healthmonitors': [],
|
||||
'driver': 'dummy'
|
||||
}
|
||||
|
||||
logical_config = self.callbacks.get_logical_device(
|
||||
ctx, pool['pool']['id']
|
||||
)
|
||||
|
||||
self.assertEqual(expected, logical_config)
|
||||
|
||||
def test_get_logical_device_active(self):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
with self.member(pool_id=vip['vip']['pool_id']) as member:
|
||||
ctx = context.get_admin_context()
|
||||
# activate objects
|
||||
self.plugin_instance.update_status(
|
||||
ctx, ldb.Pool, pool['pool']['id'], 'ACTIVE')
|
||||
self.plugin_instance.update_status(
|
||||
ctx, ldb.Member, member['member']['id'], 'ACTIVE')
|
||||
self.plugin_instance.update_status(
|
||||
ctx, ldb.Vip, vip['vip']['id'], 'ACTIVE')
|
||||
|
||||
# build the expected
|
||||
port = self.plugin_instance._core_plugin.get_port(
|
||||
ctx, vip['vip']['port_id']
|
||||
)
|
||||
subnet = self.plugin_instance._core_plugin.get_subnet(
|
||||
ctx, vip['vip']['subnet_id']
|
||||
)
|
||||
port['fixed_ips'][0]['subnet'] = subnet
|
||||
|
||||
# reload pool to add members and vip
|
||||
pool = self.plugin_instance.get_pool(
|
||||
ctx, pool['pool']['id']
|
||||
)
|
||||
|
||||
pool['status'] = constants.ACTIVE
|
||||
vip['vip']['status'] = constants.ACTIVE
|
||||
vip['vip']['port'] = port
|
||||
member['member']['status'] = constants.ACTIVE
|
||||
|
||||
expected = {
|
||||
'pool': pool,
|
||||
'vip': vip['vip'],
|
||||
'members': [member['member']],
|
||||
'healthmonitors': [],
|
||||
'driver': 'dummy'
|
||||
}
|
||||
|
||||
logical_config = self.callbacks.get_logical_device(
|
||||
ctx, pool['id']
|
||||
)
|
||||
|
||||
self.assertEqual(logical_config, expected)
|
||||
|
||||
def test_get_logical_device_inactive_member(self):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
with self.member(pool_id=vip['vip']['pool_id']) as member:
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.update_status(ctx, ldb.Pool,
|
||||
pool['pool']['id'],
|
||||
'ACTIVE')
|
||||
self.plugin_instance.update_status(ctx, ldb.Vip,
|
||||
vip['vip']['id'],
|
||||
'ACTIVE')
|
||||
self.plugin_instance.update_status(ctx, ldb.Member,
|
||||
member['member']['id'],
|
||||
'INACTIVE')
|
||||
|
||||
logical_config = self.callbacks.get_logical_device(
|
||||
ctx, pool['pool']['id'])
|
||||
|
||||
member['member']['status'] = constants.INACTIVE
|
||||
self.assertEqual([member['member']],
|
||||
logical_config['members'])
|
||||
|
||||
def test_get_logical_device_pending_create_member(self):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
with self.member(pool_id=vip['vip']['pool_id']) as member:
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.update_status(ctx, ldb.Pool,
|
||||
pool['pool']['id'],
|
||||
'ACTIVE')
|
||||
self.plugin_instance.update_status(ctx, ldb.Vip,
|
||||
vip['vip']['id'],
|
||||
'ACTIVE')
|
||||
|
||||
member = self.plugin_instance.get_member(
|
||||
ctx, member['member']['id'])
|
||||
self.assertEqual('PENDING_CREATE',
|
||||
member['status'])
|
||||
logical_config = self.callbacks.get_logical_device(
|
||||
ctx, pool['pool']['id'])
|
||||
|
||||
self.assertEqual([member], logical_config['members'])
|
||||
|
||||
def test_get_logical_device_pending_create_health_monitor(self):
|
||||
with self.health_monitor() as monitor:
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.update_status(ctx, ldb.Pool,
|
||||
pool['pool']['id'],
|
||||
'ACTIVE')
|
||||
self.plugin_instance.update_status(ctx, ldb.Vip,
|
||||
vip['vip']['id'],
|
||||
'ACTIVE')
|
||||
self.plugin_instance.create_pool_health_monitor(
|
||||
ctx, monitor, pool['pool']['id'])
|
||||
pool = self.plugin_instance.get_pool(
|
||||
ctx, pool['pool']['id'])
|
||||
monitor = self.plugin_instance.get_health_monitor(
|
||||
ctx, monitor['health_monitor']['id'])
|
||||
|
||||
self.assertEqual(
|
||||
'PENDING_CREATE',
|
||||
pool['health_monitors_status'][0]['status'])
|
||||
logical_config = self.callbacks.get_logical_device(
|
||||
ctx, pool['id'])
|
||||
|
||||
self.assertEqual([monitor],
|
||||
logical_config['healthmonitors'])
|
||||
|
||||
def _update_port_test_helper(self, expected, func, **kwargs):
|
||||
core = self.plugin_instance._core_plugin
|
||||
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
with self.member(pool_id=vip['vip']['pool_id']):
|
||||
ctx = context.get_admin_context()
|
||||
func(ctx, port_id=vip['vip']['port_id'], **kwargs)
|
||||
|
||||
db_port = core.get_port(ctx, vip['vip']['port_id'])
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEqual(db_port[k], v)
|
||||
|
||||
def test_plug_vip_port(self):
|
||||
exp = {
|
||||
'device_owner': 'neutron:' + constants.LOADBALANCER,
|
||||
'device_id': 'c596ce11-db30-5c72-8243-15acaae8690f',
|
||||
'admin_state_up': True
|
||||
}
|
||||
self._update_port_test_helper(
|
||||
exp,
|
||||
self.callbacks.plug_vip_port,
|
||||
host='host'
|
||||
)
|
||||
|
||||
def test_plug_vip_port_mock_with_host(self):
|
||||
exp = {
|
||||
'device_owner': 'neutron:' + constants.LOADBALANCER,
|
||||
'device_id': 'c596ce11-db30-5c72-8243-15acaae8690f',
|
||||
'admin_state_up': True,
|
||||
portbindings.HOST_ID: 'host'
|
||||
}
|
||||
with mock.patch.object(
|
||||
self.plugin._core_plugin, 'update_port') as mock_update_port:
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
ctx = context.get_admin_context()
|
||||
self.callbacks.plug_vip_port(
|
||||
ctx, port_id=vip['vip']['port_id'], host='host')
|
||||
mock_update_port.assert_called_once_with(
|
||||
ctx, vip['vip']['port_id'],
|
||||
{'port': testlib_api.SubDictMatch(exp)})
|
||||
|
||||
def test_unplug_vip_port(self):
|
||||
exp = {
|
||||
'device_owner': '',
|
||||
'device_id': '',
|
||||
'admin_state_up': False
|
||||
}
|
||||
self._update_port_test_helper(
|
||||
exp,
|
||||
self.callbacks.unplug_vip_port,
|
||||
host='host'
|
||||
)
|
||||
|
||||
def test_pool_deployed(self):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
with self.member(pool_id=vip['vip']['pool_id']) as member:
|
||||
ctx = context.get_admin_context()
|
||||
p = self.plugin_instance.get_pool(ctx, pool['pool']['id'])
|
||||
self.assertEqual('PENDING_CREATE', p['status'])
|
||||
v = self.plugin_instance.get_vip(ctx, vip['vip']['id'])
|
||||
self.assertEqual('PENDING_CREATE', v['status'])
|
||||
m = self.plugin_instance.get_member(
|
||||
ctx, member['member']['id'])
|
||||
self.assertEqual('PENDING_CREATE', m['status'])
|
||||
|
||||
self.callbacks.pool_deployed(ctx, pool['pool']['id'])
|
||||
|
||||
p = self.plugin_instance.get_pool(ctx, pool['pool']['id'])
|
||||
self.assertEqual('ACTIVE', p['status'])
|
||||
v = self.plugin_instance.get_vip(ctx, vip['vip']['id'])
|
||||
self.assertEqual('ACTIVE', v['status'])
|
||||
m = self.plugin_instance.get_member(
|
||||
ctx, member['member']['id'])
|
||||
self.assertEqual('ACTIVE', m['status'])
|
||||
|
||||
def test_update_status_pool(self):
|
||||
with self.pool() as pool:
|
||||
pool_id = pool['pool']['id']
|
||||
ctx = context.get_admin_context()
|
||||
p = self.plugin_instance.get_pool(ctx, pool_id)
|
||||
self.assertEqual('PENDING_CREATE', p['status'])
|
||||
self.callbacks.update_status(ctx, 'pool', pool_id, 'ACTIVE')
|
||||
p = self.plugin_instance.get_pool(ctx, pool_id)
|
||||
self.assertEqual('ACTIVE', p['status'])
|
||||
|
||||
def test_update_status_pool_deleted_already(self):
|
||||
with mock.patch.object(agent_driver_base, 'LOG') as mock_log:
|
||||
pool_id = 'deleted_pool'
|
||||
ctx = context.get_admin_context()
|
||||
self.assertRaises(loadbalancer.PoolNotFound,
|
||||
self.plugin_instance.get_pool, ctx, pool_id)
|
||||
self.callbacks.update_status(ctx, 'pool', pool_id, 'ACTIVE')
|
||||
self.assertTrue(mock_log.warning.called)
|
||||
|
||||
def test_update_status_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.health_monitor(),
|
||||
self.pool()
|
||||
) as (hm, pool):
|
||||
pool_id = pool['pool']['id']
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id)
|
||||
hm_id = hm['health_monitor']['id']
|
||||
h = self.plugin_instance.get_pool_health_monitor(ctx, hm_id,
|
||||
pool_id)
|
||||
self.assertEqual('PENDING_CREATE', h['status'])
|
||||
self.callbacks.update_status(
|
||||
ctx, 'health_monitor',
|
||||
{'monitor_id': hm_id, 'pool_id': pool_id}, 'ACTIVE')
|
||||
h = self.plugin_instance.get_pool_health_monitor(ctx, hm_id,
|
||||
pool_id)
|
||||
self.assertEqual('ACTIVE', h['status'])
|
||||
|
||||
|
||||
class TestLoadBalancerAgentApi(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerAgentApi, self).setUp()
|
||||
|
||||
self.api = agent_driver_base.LoadBalancerAgentApi('topic')
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.api.client.target.topic, 'topic')
|
||||
|
||||
def _call_test_helper(self, method_name, method_args):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.api.client, 'cast'),
|
||||
mock.patch.object(self.api.client, 'prepare'),
|
||||
) as (
|
||||
rpc_mock, prepare_mock
|
||||
):
|
||||
prepare_mock.return_value = self.api.client
|
||||
getattr(self.api, method_name)(mock.sentinel.context,
|
||||
host='host',
|
||||
**method_args)
|
||||
|
||||
prepare_args = {'server': 'host'}
|
||||
prepare_mock.assert_called_once_with(**prepare_args)
|
||||
|
||||
if method_name == 'agent_updated':
|
||||
method_args = {'payload': method_args}
|
||||
rpc_mock.assert_called_once_with(mock.sentinel.context, method_name,
|
||||
**method_args)
|
||||
|
||||
def test_agent_updated(self):
|
||||
self._call_test_helper('agent_updated', {'admin_state_up': 'test'})
|
||||
|
||||
def test_create_pool(self):
|
||||
self._call_test_helper('create_pool', {'pool': 'test',
|
||||
'driver_name': 'dummy'})
|
||||
|
||||
def test_update_pool(self):
|
||||
self._call_test_helper('update_pool', {'old_pool': 'test',
|
||||
'pool': 'test'})
|
||||
|
||||
def test_delete_pool(self):
|
||||
self._call_test_helper('delete_pool', {'pool': 'test'})
|
||||
|
||||
def test_create_vip(self):
|
||||
self._call_test_helper('create_vip', {'vip': 'test'})
|
||||
|
||||
def test_update_vip(self):
|
||||
self._call_test_helper('update_vip', {'old_vip': 'test',
|
||||
'vip': 'test'})
|
||||
|
||||
def test_delete_vip(self):
|
||||
self._call_test_helper('delete_vip', {'vip': 'test'})
|
||||
|
||||
def test_create_member(self):
|
||||
self._call_test_helper('create_member', {'member': 'test'})
|
||||
|
||||
def test_update_member(self):
|
||||
self._call_test_helper('update_member', {'old_member': 'test',
|
||||
'member': 'test'})
|
||||
|
||||
def test_delete_member(self):
|
||||
self._call_test_helper('delete_member', {'member': 'test'})
|
||||
|
||||
def test_create_monitor(self):
|
||||
self._call_test_helper('create_pool_health_monitor',
|
||||
{'health_monitor': 'test', 'pool_id': 'test'})
|
||||
|
||||
def test_update_monitor(self):
|
||||
self._call_test_helper('update_pool_health_monitor',
|
||||
{'old_health_monitor': 'test',
|
||||
'health_monitor': 'test',
|
||||
'pool_id': 'test'})
|
||||
|
||||
def test_delete_monitor(self):
|
||||
self._call_test_helper('delete_pool_health_monitor',
|
||||
{'health_monitor': 'test', 'pool_id': 'test'})
|
||||
|
||||
|
||||
class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
|
||||
def setUp(self):
|
||||
self.log = mock.patch.object(agent_driver_base, 'LOG')
|
||||
api_cls = mock.patch.object(agent_driver_base,
|
||||
'LoadBalancerAgentApi').start()
|
||||
super(TestLoadBalancerPluginNotificationWrapper, self).setUp()
|
||||
self.mock_api = api_cls.return_value
|
||||
|
||||
self.mock_get_driver = mock.patch.object(self.plugin_instance,
|
||||
'_get_driver')
|
||||
self.mock_get_driver.return_value = (agent_driver_base.
|
||||
AgentDriverBase(
|
||||
self.plugin_instance
|
||||
))
|
||||
|
||||
def test_create_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(subnet=subnet) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet) as vip:
|
||||
self.mock_api.create_vip.assert_called_once_with(
|
||||
mock.ANY,
|
||||
vip['vip'],
|
||||
'host'
|
||||
)
|
||||
|
||||
def test_update_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(subnet=subnet) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet) as vip:
|
||||
ctx = context.get_admin_context()
|
||||
old_vip = vip['vip'].copy()
|
||||
vip['vip'].pop('status')
|
||||
new_vip = self.plugin_instance.update_vip(
|
||||
ctx,
|
||||
vip['vip']['id'],
|
||||
vip
|
||||
)
|
||||
|
||||
self.mock_api.update_vip.assert_called_once_with(
|
||||
mock.ANY,
|
||||
old_vip,
|
||||
new_vip,
|
||||
'host'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
new_vip['status'],
|
||||
constants.PENDING_UPDATE
|
||||
)
|
||||
|
||||
def test_delete_vip(self):
|
||||
with self.subnet() as subnet:
|
||||
with self.pool(subnet=subnet) as pool:
|
||||
with self.vip(pool=pool, subnet=subnet,
|
||||
do_delete=False) as vip:
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.delete_vip(ctx, vip['vip']['id'])
|
||||
vip['vip']['status'] = 'PENDING_DELETE'
|
||||
self.mock_api.delete_vip.assert_called_once_with(
|
||||
mock.ANY,
|
||||
vip['vip'],
|
||||
'host'
|
||||
)
|
||||
|
||||
def test_create_pool(self):
|
||||
with self.pool() as pool:
|
||||
self.mock_api.create_pool.assert_called_once_with(
|
||||
mock.ANY,
|
||||
pool['pool'],
|
||||
mock.ANY,
|
||||
'dummy'
|
||||
)
|
||||
|
||||
def test_update_pool_non_active(self):
|
||||
with self.pool() as pool:
|
||||
pool['pool']['status'] = 'INACTIVE'
|
||||
ctx = context.get_admin_context()
|
||||
orig_pool = pool['pool'].copy()
|
||||
del pool['pool']['provider']
|
||||
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
|
||||
self.mock_api.delete_pool.assert_called_once_with(
|
||||
mock.ANY, orig_pool, 'host')
|
||||
|
||||
def test_update_pool_no_vip_id(self):
|
||||
with self.pool() as pool:
|
||||
ctx = context.get_admin_context()
|
||||
orig_pool = pool['pool'].copy()
|
||||
del pool['pool']['provider']
|
||||
updated = self.plugin_instance.update_pool(
|
||||
ctx, pool['pool']['id'], pool)
|
||||
self.mock_api.update_pool.assert_called_once_with(
|
||||
mock.ANY, orig_pool, updated, 'host')
|
||||
|
||||
def test_update_pool_with_vip_id(self):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool) as vip:
|
||||
ctx = context.get_admin_context()
|
||||
old_pool = pool['pool'].copy()
|
||||
old_pool['vip_id'] = vip['vip']['id']
|
||||
del pool['pool']['provider']
|
||||
updated = self.plugin_instance.update_pool(
|
||||
ctx, pool['pool']['id'], pool)
|
||||
self.mock_api.update_pool.assert_called_once_with(
|
||||
mock.ANY, old_pool, updated, 'host')
|
||||
|
||||
def test_delete_pool(self):
|
||||
with self.pool(do_delete=False) as pool:
|
||||
req = self.new_delete_request('pools',
|
||||
pool['pool']['id'])
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
pool['pool']['status'] = 'PENDING_DELETE'
|
||||
self.mock_api.delete_pool.assert_called_once_with(
|
||||
mock.ANY, pool['pool'], 'host')
|
||||
|
||||
def test_create_member(self):
|
||||
with self.pool() as pool:
|
||||
pool_id = pool['pool']['id']
|
||||
with self.member(pool_id=pool_id) as member:
|
||||
self.mock_api.create_member.assert_called_once_with(
|
||||
mock.ANY, member['member'], 'host')
|
||||
|
||||
def test_update_member(self):
|
||||
with self.pool() as pool:
|
||||
pool_id = pool['pool']['id']
|
||||
with self.member(pool_id=pool_id) as member:
|
||||
ctx = context.get_admin_context()
|
||||
updated = self.plugin_instance.update_member(
|
||||
ctx, member['member']['id'], member)
|
||||
self.mock_api.update_member.assert_called_once_with(
|
||||
mock.ANY, member['member'], updated, 'host')
|
||||
|
||||
def test_update_member_new_pool(self):
|
||||
with self.pool() as pool1:
|
||||
pool1_id = pool1['pool']['id']
|
||||
with self.pool() as pool2:
|
||||
pool2_id = pool2['pool']['id']
|
||||
with self.member(pool_id=pool1_id) as member:
|
||||
self.mock_api.create_member.reset_mock()
|
||||
ctx = context.get_admin_context()
|
||||
old_member = member['member'].copy()
|
||||
member['member']['pool_id'] = pool2_id
|
||||
updated = self.plugin_instance.update_member(
|
||||
ctx, member['member']['id'], member)
|
||||
self.mock_api.delete_member.assert_called_once_with(
|
||||
mock.ANY, old_member, 'host')
|
||||
self.mock_api.create_member.assert_called_once_with(
|
||||
mock.ANY, updated, 'host')
|
||||
|
||||
def test_delete_member(self):
|
||||
with self.pool() as pool:
|
||||
pool_id = pool['pool']['id']
|
||||
with self.member(pool_id=pool_id,
|
||||
do_delete=False) as member:
|
||||
req = self.new_delete_request('members',
|
||||
member['member']['id'])
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
member['member']['status'] = 'PENDING_DELETE'
|
||||
self.mock_api.delete_member.assert_called_once_with(
|
||||
mock.ANY, member['member'], 'host')
|
||||
|
||||
def test_create_pool_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.health_monitor(),
|
||||
self.pool(),
|
||||
) as (hm, pool):
|
||||
pool_id = pool['pool']['id']
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id)
|
||||
# hm now has a ref to the pool with which it is associated
|
||||
hm = self.plugin.get_health_monitor(
|
||||
ctx, hm['health_monitor']['id'])
|
||||
self.mock_api.create_pool_health_monitor.assert_called_once_with(
|
||||
mock.ANY, hm, pool_id, 'host')
|
||||
|
||||
def test_delete_pool_health_monitor(self):
|
||||
with contextlib.nested(
|
||||
self.pool(),
|
||||
self.health_monitor()
|
||||
) as (pool, hm):
|
||||
pool_id = pool['pool']['id']
|
||||
ctx = context.get_admin_context()
|
||||
self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id)
|
||||
# hm now has a ref to the pool with which it is associated
|
||||
hm = self.plugin.get_health_monitor(
|
||||
ctx, hm['health_monitor']['id'])
|
||||
hm['pools'][0]['status'] = 'PENDING_DELETE'
|
||||
self.plugin_instance.delete_pool_health_monitor(
|
||||
ctx, hm['id'], pool_id)
|
||||
self.mock_api.delete_pool_health_monitor.assert_called_once_with(
|
||||
mock.ANY, hm, pool_id, 'host')
|
||||
|
||||
def test_update_health_monitor_associated_with_pool(self):
|
||||
with contextlib.nested(
|
||||
self.health_monitor(type='HTTP'),
|
||||
self.pool()
|
||||
) as (monitor, pool):
|
||||
data = {
|
||||
'health_monitor': {
|
||||
'id': monitor['health_monitor']['id'],
|
||||
'tenant_id': self._tenant_id
|
||||
}
|
||||
}
|
||||
req = self.new_create_request(
|
||||
'pools',
|
||||
data,
|
||||
fmt=self.fmt,
|
||||
id=pool['pool']['id'],
|
||||
subresource='health_monitors')
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
# hm now has a ref to the pool with which it is associated
|
||||
ctx = context.get_admin_context()
|
||||
hm = self.plugin.get_health_monitor(
|
||||
ctx, monitor['health_monitor']['id'])
|
||||
self.mock_api.create_pool_health_monitor.assert_called_once_with(
|
||||
mock.ANY,
|
||||
hm,
|
||||
pool['pool']['id'],
|
||||
'host'
|
||||
)
|
||||
|
||||
self.mock_api.reset_mock()
|
||||
data = {'health_monitor': {'delay': 20,
|
||||
'timeout': 20,
|
||||
'max_retries': 2,
|
||||
'admin_state_up': False}}
|
||||
updated = hm.copy()
|
||||
updated.update(data['health_monitor'])
|
||||
req = self.new_update_request("health_monitors",
|
||||
data,
|
||||
monitor['health_monitor']['id'])
|
||||
req.get_response(self.ext_api)
|
||||
self.mock_api.update_pool_health_monitor.assert_called_once_with(
|
||||
mock.ANY,
|
||||
hm,
|
||||
updated,
|
||||
pool['pool']['id'],
|
||||
'host')
|
|
@ -1,217 +0,0 @@
|
|||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
#
|
||||
# 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
|
||||
from oslo.config import cfg
|
||||
from webob import exc
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import constants
|
||||
from neutron import context
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.extensions import agent
|
||||
from neutron.extensions import lbaas_agentscheduler
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as plugin_const
|
||||
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
|
||||
from neutron.tests.unit.openvswitch import test_agent_scheduler
|
||||
from neutron.tests.unit import test_agent_ext_plugin
|
||||
from neutron.tests.unit import test_db_plugin as test_plugin
|
||||
from neutron.tests.unit import test_extensions
|
||||
|
||||
LBAAS_HOSTA = 'hosta'
|
||||
|
||||
|
||||
class AgentSchedulerTestMixIn(test_agent_scheduler.AgentSchedulerTestMixIn):
|
||||
def _list_pools_hosted_by_lbaas_agent(self, agent_id,
|
||||
expected_code=exc.HTTPOk.code,
|
||||
admin_context=True):
|
||||
path = "/agents/%s/%s.%s" % (agent_id,
|
||||
lbaas_agentscheduler.LOADBALANCER_POOLS,
|
||||
self.fmt)
|
||||
return self._request_list(path, expected_code=expected_code,
|
||||
admin_context=admin_context)
|
||||
|
||||
def _get_lbaas_agent_hosting_pool(self, pool_id,
|
||||
expected_code=exc.HTTPOk.code,
|
||||
admin_context=True):
|
||||
path = "/lb/pools/%s/%s.%s" % (pool_id,
|
||||
lbaas_agentscheduler.LOADBALANCER_AGENT,
|
||||
self.fmt)
|
||||
return self._request_list(path, expected_code=expected_code,
|
||||
admin_context=admin_context)
|
||||
|
||||
|
||||
class LBaaSAgentSchedulerTestCase(test_agent_ext_plugin.AgentDBTestMixIn,
|
||||
AgentSchedulerTestMixIn,
|
||||
test_db_loadbalancer.LoadBalancerTestMixin,
|
||||
test_plugin.NeutronDbPluginV2TestCase):
|
||||
fmt = 'json'
|
||||
plugin_str = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||
|
||||
def setUp(self):
|
||||
# Save the global RESOURCE_ATTRIBUTE_MAP
|
||||
self.saved_attr_map = {}
|
||||
for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
|
||||
self.saved_attr_map[resource] = attrs.copy()
|
||||
service_plugins = {
|
||||
'lb_plugin_name': test_db_loadbalancer.DB_LB_PLUGIN_KLASS}
|
||||
|
||||
#default provider should support agent scheduling
|
||||
cfg.CONF.set_override(
|
||||
'service_provider',
|
||||
[('LOADBALANCER:lbaas:neutron.services.'
|
||||
'loadbalancer.drivers.haproxy.plugin_driver.'
|
||||
'HaproxyOnHostPluginDriver:default')],
|
||||
'service_providers')
|
||||
|
||||
# need to reload provider configuration
|
||||
st_db.ServiceTypeManager._instance = None
|
||||
|
||||
super(LBaaSAgentSchedulerTestCase, self).setUp(
|
||||
self.plugin_str, service_plugins=service_plugins)
|
||||
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
|
||||
self.adminContext = context.get_admin_context()
|
||||
# Add the resources to the global attribute map
|
||||
# This is done here as the setup process won't
|
||||
# initialize the main API router which extends
|
||||
# the global attribute map
|
||||
attributes.RESOURCE_ATTRIBUTE_MAP.update(
|
||||
agent.RESOURCE_ATTRIBUTE_MAP)
|
||||
self.addCleanup(self.restore_attribute_map)
|
||||
|
||||
def restore_attribute_map(self):
|
||||
# Restore the original RESOURCE_ATTRIBUTE_MAP
|
||||
attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
|
||||
|
||||
def test_report_states(self):
|
||||
self._register_agent_states(lbaas_agents=True)
|
||||
agents = self._list_agents()
|
||||
self.assertEqual(6, len(agents['agents']))
|
||||
|
||||
def test_pool_scheduling_on_pool_creation(self):
|
||||
self._register_agent_states(lbaas_agents=True)
|
||||
with self.pool() as pool:
|
||||
lbaas_agent = self._get_lbaas_agent_hosting_pool(
|
||||
pool['pool']['id'])
|
||||
self.assertIsNotNone(lbaas_agent)
|
||||
self.assertEqual(lbaas_agent['agent']['agent_type'],
|
||||
constants.AGENT_TYPE_LOADBALANCER)
|
||||
pools = self._list_pools_hosted_by_lbaas_agent(
|
||||
lbaas_agent['agent']['id'])
|
||||
self.assertEqual(1, len(pools['pools']))
|
||||
self.assertEqual(pool['pool'], pools['pools'][0])
|
||||
|
||||
def test_schedule_pool_with_disabled_agent(self):
|
||||
lbaas_hosta = {
|
||||
'binary': 'neutron-loadbalancer-agent',
|
||||
'host': LBAAS_HOSTA,
|
||||
'topic': 'LOADBALANCER_AGENT',
|
||||
'configurations': {'device_drivers': ['haproxy_ns']},
|
||||
'agent_type': constants.AGENT_TYPE_LOADBALANCER}
|
||||
self._register_one_agent_state(lbaas_hosta)
|
||||
with self.pool() as pool:
|
||||
lbaas_agent = self._get_lbaas_agent_hosting_pool(
|
||||
pool['pool']['id'])
|
||||
self.assertIsNotNone(lbaas_agent)
|
||||
|
||||
agents = self._list_agents()
|
||||
self._disable_agent(agents['agents'][0]['id'])
|
||||
pool = {'pool': {'name': 'test',
|
||||
'subnet_id': 'test',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'protocol': 'HTTP',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'test',
|
||||
'description': 'test'}}
|
||||
lbaas_plugin = manager.NeutronManager.get_service_plugins()[
|
||||
plugin_const.LOADBALANCER]
|
||||
self.assertRaises(loadbalancer.NoEligibleBackend,
|
||||
lbaas_plugin.create_pool, self.adminContext, pool)
|
||||
pools = lbaas_plugin.get_pools(self.adminContext)
|
||||
self.assertEqual('ERROR', pools[0]['status'])
|
||||
self.assertEqual('No eligible backend',
|
||||
pools[0]['status_description'])
|
||||
|
||||
def test_schedule_pool_with_down_agent(self):
|
||||
lbaas_hosta = {
|
||||
'binary': 'neutron-loadbalancer-agent',
|
||||
'host': LBAAS_HOSTA,
|
||||
'topic': 'LOADBALANCER_AGENT',
|
||||
'configurations': {'device_drivers': ['haproxy_ns']},
|
||||
'agent_type': constants.AGENT_TYPE_LOADBALANCER}
|
||||
self._register_one_agent_state(lbaas_hosta)
|
||||
is_agent_down_str = 'neutron.db.agents_db.AgentDbMixin.is_agent_down'
|
||||
with mock.patch(is_agent_down_str) as mock_is_agent_down:
|
||||
mock_is_agent_down.return_value = False
|
||||
with self.pool() as pool:
|
||||
lbaas_agent = self._get_lbaas_agent_hosting_pool(
|
||||
pool['pool']['id'])
|
||||
self.assertIsNotNone(lbaas_agent)
|
||||
with mock.patch(is_agent_down_str) as mock_is_agent_down:
|
||||
mock_is_agent_down.return_value = True
|
||||
pool = {'pool': {'name': 'test',
|
||||
'subnet_id': 'test',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'protocol': 'HTTP',
|
||||
'provider': 'lbaas',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'test',
|
||||
'description': 'test'}}
|
||||
lbaas_plugin = manager.NeutronManager.get_service_plugins()[
|
||||
plugin_const.LOADBALANCER]
|
||||
self.assertRaises(loadbalancer.NoEligibleBackend,
|
||||
lbaas_plugin.create_pool,
|
||||
self.adminContext, pool)
|
||||
pools = lbaas_plugin.get_pools(self.adminContext)
|
||||
self.assertEqual('ERROR', pools[0]['status'])
|
||||
self.assertEqual('No eligible backend',
|
||||
pools[0]['status_description'])
|
||||
|
||||
def test_pool_unscheduling_on_pool_deletion(self):
|
||||
self._register_agent_states(lbaas_agents=True)
|
||||
with self.pool(do_delete=False) as pool:
|
||||
lbaas_agent = self._get_lbaas_agent_hosting_pool(
|
||||
pool['pool']['id'])
|
||||
self.assertIsNotNone(lbaas_agent)
|
||||
self.assertEqual(lbaas_agent['agent']['agent_type'],
|
||||
constants.AGENT_TYPE_LOADBALANCER)
|
||||
pools = self._list_pools_hosted_by_lbaas_agent(
|
||||
lbaas_agent['agent']['id'])
|
||||
self.assertEqual(1, len(pools['pools']))
|
||||
self.assertEqual(pool['pool'], pools['pools'][0])
|
||||
|
||||
req = self.new_delete_request('pools',
|
||||
pool['pool']['id'])
|
||||
res = req.get_response(self.ext_api)
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
||||
pools = self._list_pools_hosted_by_lbaas_agent(
|
||||
lbaas_agent['agent']['id'])
|
||||
self.assertEqual(0, len(pools['pools']))
|
||||
|
||||
def test_pool_scheduling_non_admin_access(self):
|
||||
self._register_agent_states(lbaas_agents=True)
|
||||
with self.pool() as pool:
|
||||
self._get_lbaas_agent_hosting_pool(
|
||||
pool['pool']['id'],
|
||||
expected_code=exc.HTTPForbidden.code,
|
||||
admin_context=False)
|
||||
self._list_pools_hosted_by_lbaas_agent(
|
||||
'fake_id',
|
||||
expected_code=exc.HTTPForbidden.code,
|
||||
admin_context=False)
|
|
@ -1,458 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 webob import exc
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.tests.unit import test_api_v2
|
||||
from neutron.tests.unit import test_api_v2_extension
|
||||
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
_get_path = test_api_v2._get_path
|
||||
|
||||
|
||||
class LoadBalancerExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
|
||||
fmt = 'json'
|
||||
|
||||
def setUp(self):
|
||||
super(LoadBalancerExtensionTestCase, self).setUp()
|
||||
self._setUpExtension(
|
||||
'neutron.extensions.loadbalancer.LoadBalancerPluginBase',
|
||||
constants.LOADBALANCER, loadbalancer.RESOURCE_ATTRIBUTE_MAP,
|
||||
loadbalancer.Loadbalancer, 'lb', use_quota=True)
|
||||
|
||||
def test_vip_create(self):
|
||||
vip_id = _uuid()
|
||||
data = {'vip': {'name': 'vip1',
|
||||
'description': 'descr_vip1',
|
||||
'subnet_id': _uuid(),
|
||||
'address': '127.0.0.1',
|
||||
'protocol_port': 80,
|
||||
'protocol': 'HTTP',
|
||||
'pool_id': _uuid(),
|
||||
'session_persistence': {'type': 'HTTP_COOKIE'},
|
||||
'connection_limit': 100,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['vip'])
|
||||
return_value.update({'status': "ACTIVE", 'id': vip_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_vip.return_value = return_value
|
||||
res = self.api.post(_get_path('lb/vips', fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
instance.create_vip.assert_called_with(mock.ANY,
|
||||
vip=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('vip', res)
|
||||
self.assertEqual(res['vip'], return_value)
|
||||
|
||||
def test_vip_list(self):
|
||||
vip_id = _uuid()
|
||||
return_value = [{'name': 'vip1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': vip_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_vips.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/vips', fmt=self.fmt))
|
||||
|
||||
instance.get_vips.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_vip_update(self):
|
||||
vip_id = _uuid()
|
||||
update_data = {'vip': {'admin_state_up': False}}
|
||||
return_value = {'name': 'vip1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': vip_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_vip.return_value = return_value
|
||||
|
||||
res = self.api.put(_get_path('lb/vips', id=vip_id, fmt=self.fmt),
|
||||
self.serialize(update_data))
|
||||
|
||||
instance.update_vip.assert_called_with(mock.ANY, vip_id,
|
||||
vip=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('vip', res)
|
||||
self.assertEqual(res['vip'], return_value)
|
||||
|
||||
def test_vip_get(self):
|
||||
vip_id = _uuid()
|
||||
return_value = {'name': 'vip1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': vip_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_vip.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/vips', id=vip_id, fmt=self.fmt))
|
||||
|
||||
instance.get_vip.assert_called_with(mock.ANY, vip_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('vip', res)
|
||||
self.assertEqual(res['vip'], return_value)
|
||||
|
||||
def test_vip_delete(self):
|
||||
self._test_entity_delete('vip')
|
||||
|
||||
def test_pool_create(self):
|
||||
pool_id = _uuid()
|
||||
hm_id = _uuid()
|
||||
data = {'pool': {'name': 'pool1',
|
||||
'description': 'descr_pool1',
|
||||
'subnet_id': _uuid(),
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'health_monitors': [hm_id],
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['pool'])
|
||||
return_value['provider'] = 'lbaas'
|
||||
return_value.update({'status': "ACTIVE", 'id': pool_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_pool.return_value = return_value
|
||||
res = self.api.post(_get_path('lb/pools', fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
data['pool']['provider'] = attr.ATTR_NOT_SPECIFIED
|
||||
instance.create_pool.assert_called_with(mock.ANY,
|
||||
pool=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('pool', res)
|
||||
self.assertEqual(res['pool'], return_value)
|
||||
|
||||
def test_pool_list(self):
|
||||
pool_id = _uuid()
|
||||
return_value = [{'name': 'pool1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': pool_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_pools.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/pools', fmt=self.fmt))
|
||||
|
||||
instance.get_pools.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_pool_update(self):
|
||||
pool_id = _uuid()
|
||||
update_data = {'pool': {'admin_state_up': False}}
|
||||
return_value = {'name': 'pool1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': pool_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_pool.return_value = return_value
|
||||
|
||||
res = self.api.put(_get_path('lb/pools', id=pool_id, fmt=self.fmt),
|
||||
self.serialize(update_data))
|
||||
|
||||
instance.update_pool.assert_called_with(mock.ANY, pool_id,
|
||||
pool=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('pool', res)
|
||||
self.assertEqual(res['pool'], return_value)
|
||||
|
||||
def test_pool_get(self):
|
||||
pool_id = _uuid()
|
||||
return_value = {'name': 'pool1',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': pool_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_pool.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/pools', id=pool_id, fmt=self.fmt))
|
||||
|
||||
instance.get_pool.assert_called_with(mock.ANY, pool_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('pool', res)
|
||||
self.assertEqual(res['pool'], return_value)
|
||||
|
||||
def test_pool_delete(self):
|
||||
self._test_entity_delete('pool')
|
||||
|
||||
def test_pool_stats(self):
|
||||
pool_id = _uuid()
|
||||
|
||||
stats = {'stats': 'dummy'}
|
||||
instance = self.plugin.return_value
|
||||
instance.stats.return_value = stats
|
||||
|
||||
path = _get_path('lb/pools', id=pool_id,
|
||||
action="stats", fmt=self.fmt)
|
||||
res = self.api.get(path)
|
||||
|
||||
instance.stats.assert_called_with(mock.ANY, pool_id)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('stats', res)
|
||||
self.assertEqual(res['stats'], stats['stats'])
|
||||
|
||||
def test_member_create(self):
|
||||
member_id = _uuid()
|
||||
data = {'member': {'pool_id': _uuid(),
|
||||
'address': '127.0.0.1',
|
||||
'protocol_port': 80,
|
||||
'weight': 1,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['member'])
|
||||
return_value.update({'status': "ACTIVE", 'id': member_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_member.return_value = return_value
|
||||
res = self.api.post(_get_path('lb/members', fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
instance.create_member.assert_called_with(mock.ANY,
|
||||
member=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('member', res)
|
||||
self.assertEqual(res['member'], return_value)
|
||||
|
||||
def test_member_list(self):
|
||||
member_id = _uuid()
|
||||
return_value = [{'name': 'member1',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': member_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_members.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/members', fmt=self.fmt))
|
||||
|
||||
instance.get_members.assert_called_with(mock.ANY, fields=mock.ANY,
|
||||
filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_member_update(self):
|
||||
member_id = _uuid()
|
||||
update_data = {'member': {'admin_state_up': False}}
|
||||
return_value = {'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': member_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_member.return_value = return_value
|
||||
|
||||
res = self.api.put(_get_path('lb/members', id=member_id,
|
||||
fmt=self.fmt),
|
||||
self.serialize(update_data))
|
||||
|
||||
instance.update_member.assert_called_with(mock.ANY, member_id,
|
||||
member=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('member', res)
|
||||
self.assertEqual(res['member'], return_value)
|
||||
|
||||
def test_member_get(self):
|
||||
member_id = _uuid()
|
||||
return_value = {'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': member_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_member.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/members', id=member_id,
|
||||
fmt=self.fmt))
|
||||
|
||||
instance.get_member.assert_called_with(mock.ANY, member_id,
|
||||
fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('member', res)
|
||||
self.assertEqual(res['member'], return_value)
|
||||
|
||||
def test_member_delete(self):
|
||||
self._test_entity_delete('member')
|
||||
|
||||
def test_health_monitor_create(self):
|
||||
health_monitor_id = _uuid()
|
||||
data = {'health_monitor': {'type': 'HTTP',
|
||||
'delay': 2,
|
||||
'timeout': 1,
|
||||
'max_retries': 3,
|
||||
'http_method': 'GET',
|
||||
'url_path': '/path',
|
||||
'expected_codes': '200-300',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['health_monitor'])
|
||||
return_value.update({'status': "ACTIVE", 'id': health_monitor_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.create_health_monitor.return_value = return_value
|
||||
res = self.api.post(_get_path('lb/health_monitors',
|
||||
fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
instance.create_health_monitor.assert_called_with(mock.ANY,
|
||||
health_monitor=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('health_monitor', res)
|
||||
self.assertEqual(res['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_create_with_timeout_negative(self):
|
||||
data = {'health_monitor': {'type': 'HTTP',
|
||||
'delay': 2,
|
||||
'timeout': -1,
|
||||
'max_retries': 3,
|
||||
'http_method': 'GET',
|
||||
'url_path': '/path',
|
||||
'expected_codes': '200-300',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
res = self.api.post(_get_path('lb/health_monitors',
|
||||
fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt,
|
||||
expect_errors=True)
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_health_monitor_list(self):
|
||||
health_monitor_id = _uuid()
|
||||
return_value = [{'type': 'HTTP',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid(),
|
||||
'id': health_monitor_id}]
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_health_monitors.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/health_monitors', fmt=self.fmt))
|
||||
|
||||
instance.get_health_monitors.assert_called_with(
|
||||
mock.ANY, fields=mock.ANY, filters=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
|
||||
def test_health_monitor_update(self):
|
||||
health_monitor_id = _uuid()
|
||||
update_data = {'health_monitor': {'admin_state_up': False}}
|
||||
return_value = {'type': 'HTTP',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': health_monitor_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.update_health_monitor.return_value = return_value
|
||||
|
||||
res = self.api.put(_get_path('lb/health_monitors',
|
||||
id=health_monitor_id,
|
||||
fmt=self.fmt),
|
||||
self.serialize(update_data))
|
||||
|
||||
instance.update_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, health_monitor=update_data)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('health_monitor', res)
|
||||
self.assertEqual(res['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_get(self):
|
||||
health_monitor_id = _uuid()
|
||||
return_value = {'type': 'HTTP',
|
||||
'admin_state_up': False,
|
||||
'tenant_id': _uuid(),
|
||||
'status': "ACTIVE",
|
||||
'id': health_monitor_id}
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.get_health_monitor.return_value = return_value
|
||||
|
||||
res = self.api.get(_get_path('lb/health_monitors',
|
||||
id=health_monitor_id,
|
||||
fmt=self.fmt))
|
||||
|
||||
instance.get_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, fields=mock.ANY)
|
||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('health_monitor', res)
|
||||
self.assertEqual(res['health_monitor'], return_value)
|
||||
|
||||
def test_health_monitor_delete(self):
|
||||
self._test_entity_delete('health_monitor')
|
||||
|
||||
def test_create_pool_health_monitor(self):
|
||||
health_monitor_id = _uuid()
|
||||
data = {'health_monitor': {'id': health_monitor_id,
|
||||
'tenant_id': _uuid()}}
|
||||
|
||||
return_value = copy.copy(data['health_monitor'])
|
||||
instance = self.plugin.return_value
|
||||
instance.create_pool_health_monitor.return_value = return_value
|
||||
res = self.api.post('/lb/pools/id1/health_monitors',
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
instance.create_pool_health_monitor.assert_called_with(
|
||||
mock.ANY, pool_id='id1', health_monitor=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('health_monitor', res)
|
||||
self.assertEqual(res['health_monitor'], return_value)
|
||||
|
||||
def test_delete_pool_health_monitor(self):
|
||||
health_monitor_id = _uuid()
|
||||
|
||||
res = self.api.delete('/lb/pools/id1/health_monitors/%s' %
|
||||
health_monitor_id)
|
||||
|
||||
instance = self.plugin.return_value
|
||||
instance.delete_pool_health_monitor.assert_called_with(
|
||||
mock.ANY, health_monitor_id, pool_id='id1')
|
||||
self.assertEqual(res.status_int, exc.HTTPNoContent.code)
|
|
@ -1,158 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
|
||||
from neutron import context
|
||||
from neutron import quota
|
||||
from neutron.tests.unit import test_api_v2
|
||||
from neutron.tests.unit import test_quota_ext
|
||||
|
||||
_get_path = test_api_v2._get_path
|
||||
|
||||
|
||||
class LBaaSQuotaExtensionTestCase(
|
||||
test_quota_ext.QuotaExtensionTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LBaaSQuotaExtensionTestCase, self).setUp()
|
||||
cfg.CONF.set_override(
|
||||
'quota_items',
|
||||
['vip', 'pool', 'member', 'health_monitor', 'extra1'],
|
||||
group='QUOTAS')
|
||||
quota.register_resources_from_config()
|
||||
|
||||
|
||||
class LBaaSQuotaExtensionDbTestCase(LBaaSQuotaExtensionTestCase):
|
||||
fmt = 'json'
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override(
|
||||
'quota_driver',
|
||||
'neutron.db.quota_db.DbQuotaDriver',
|
||||
group='QUOTAS')
|
||||
super(LBaaSQuotaExtensionDbTestCase, self).setUp()
|
||||
|
||||
def test_quotas_loaded_right(self):
|
||||
res = self.api.get(_get_path('quotas', fmt=self.fmt))
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual([], quota['quotas'])
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
def test_quotas_default_values(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id)}
|
||||
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
extra_environ=env)
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual(10, quota['quota']['vip'])
|
||||
self.assertEqual(10, quota['quota']['pool'])
|
||||
self.assertEqual(-1, quota['quota']['member'])
|
||||
self.assertEqual(-1, quota['quota']['health_monitor'])
|
||||
self.assertEqual(-1, quota['quota']['extra1'])
|
||||
|
||||
def test_show_quotas_with_admin(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id + '2',
|
||||
is_admin=True)}
|
||||
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
extra_environ=env)
|
||||
self.assertEqual(200, res.status_int)
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual(10, quota['quota']['vip'])
|
||||
self.assertEqual(10, quota['quota']['pool'])
|
||||
self.assertEqual(-1, quota['quota']['member'])
|
||||
self.assertEqual(-1, quota['quota']['health_monitor'])
|
||||
|
||||
def test_show_quotas_with_owner_tenant(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id,
|
||||
is_admin=False)}
|
||||
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
extra_environ=env)
|
||||
self.assertEqual(200, res.status_int)
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual(10, quota['quota']['vip'])
|
||||
self.assertEqual(10, quota['quota']['pool'])
|
||||
self.assertEqual(-1, quota['quota']['member'])
|
||||
self.assertEqual(-1, quota['quota']['health_monitor'])
|
||||
|
||||
def test_update_quotas_to_unlimited(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id,
|
||||
is_admin=True)}
|
||||
quotas = {'quota': {'pool': -1}}
|
||||
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
self.serialize(quotas), extra_environ=env,
|
||||
expect_errors=False)
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
def test_update_quotas_exceeding_current_limit(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id,
|
||||
is_admin=True)}
|
||||
quotas = {'quota': {'pool': 120}}
|
||||
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
self.serialize(quotas), extra_environ=env,
|
||||
expect_errors=False)
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
def test_update_quotas_with_admin(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id + '2',
|
||||
is_admin=True)}
|
||||
quotas = {'quota': {'pool': 100}}
|
||||
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
self.serialize(quotas), extra_environ=env)
|
||||
self.assertEqual(200, res.status_int)
|
||||
env2 = {'neutron.context': context.Context('', tenant_id)}
|
||||
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
extra_environ=env2)
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual(10, quota['quota']['vip'])
|
||||
self.assertEqual(100, quota['quota']['pool'])
|
||||
self.assertEqual(-1, quota['quota']['member'])
|
||||
self.assertEqual(-1, quota['quota']['health_monitor'])
|
||||
|
||||
|
||||
class LBaaSQuotaExtensionCfgTestCase(
|
||||
LBaaSQuotaExtensionTestCase):
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override(
|
||||
'quota_driver',
|
||||
'neutron.quota.ConfDriver',
|
||||
group='QUOTAS')
|
||||
super(LBaaSQuotaExtensionCfgTestCase, self).setUp()
|
||||
|
||||
def test_quotas_default_values(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
env = {'neutron.context': context.Context('', tenant_id)}
|
||||
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
extra_environ=env)
|
||||
quota = self.deserialize(res)
|
||||
self.assertEqual(10, quota['quota']['vip'])
|
||||
self.assertEqual(10, quota['quota']['pool'])
|
||||
self.assertEqual(-1, quota['quota']['member'])
|
||||
self.assertEqual(-1, quota['quota']['health_monitor'])
|
||||
self.assertEqual(-1, quota['quota']['extra1'])
|
||||
|
||||
def test_update_quotas_forbidden(self):
|
||||
tenant_id = 'tenant_id1'
|
||||
quotas = {'quota': {'pool': 100}}
|
||||
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
|
||||
self.serialize(quotas),
|
||||
expect_errors=True)
|
||||
self.assertEqual(403, res.status_int)
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue