Merge "Split services code out of Neutron, pass 1"
This commit is contained in:
commit
ab866ffe2f
@ -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…
x
Reference in New Issue
Block a user