Nuke remaining service config and extensions from main repo
Change-Id: Ic8ab865a387808c925cc311d9b70ac53f5c2c5b8 Partially-implements: blueprint services-split
This commit is contained in:
parent
014b4be568
commit
3cd60de8f5
@ -663,32 +663,3 @@ admin_password = %SERVICE_PASSWORD%
|
|||||||
|
|
||||||
# If set, use this value for pool_timeout with sqlalchemy
|
# If set, use this value for pool_timeout with sqlalchemy
|
||||||
# pool_timeout = 10
|
# pool_timeout = 10
|
||||||
|
|
||||||
# TODO(dougwig) - remove these lines once service repos have them
|
|
||||||
[service_providers]
|
|
||||||
# Specify service providers (drivers) for advanced services like loadbalancer, VPN, Firewall.
|
|
||||||
# Must be in form:
|
|
||||||
# service_provider=<service_type>:<name>:<driver>[:default]
|
|
||||||
# List of allowed service types includes LOADBALANCER, FIREWALL, VPN
|
|
||||||
# Combination of <service type> and <name> must be unique; <driver> must also be unique
|
|
||||||
# This is multiline option, example for default provider:
|
|
||||||
# service_provider=LOADBALANCER:name:lbaas_plugin_driver_path:default
|
|
||||||
# example of non-default provider:
|
|
||||||
# service_provider=FIREWALL:name2:firewall_driver_path
|
|
||||||
# --- Reference implementations ---
|
|
||||||
service_provider=LOADBALANCER:Haproxy:neutron_lbaas.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
|
|
||||||
service_provider=VPN:openswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default
|
|
||||||
# In order to activate Radware's lbaas driver you need to uncomment the next line.
|
|
||||||
# If you want to keep the HA Proxy as the default lbaas driver, remove the attribute default from the line below.
|
|
||||||
# Otherwise comment the HA Proxy line
|
|
||||||
# service_provider = LOADBALANCER:Radware:neutron_lbaas.services.loadbalancer.drivers.radware.driver.LoadBalancerDriver:default
|
|
||||||
# uncomment the following line to make the 'netscaler' LBaaS provider available.
|
|
||||||
# service_provider=LOADBALANCER:NetScaler:neutron_lbaas.services.loadbalancer.drivers.netscaler.netscaler_driver.NetScalerPluginDriver
|
|
||||||
# Uncomment the following line (and comment out the OpenSwan VPN line) to enable Cisco's VPN driver.
|
|
||||||
# service_provider=VPN:cisco:neutron_vpnaas.services.vpn.service_drivers.cisco_ipsec.CiscoCsrIPsecVPNDriver:default
|
|
||||||
# Uncomment the line below to use Embrane heleos as Load Balancer service provider.
|
|
||||||
# service_provider=LOADBALANCER:Embrane:neutron_lbaas.services.loadbalancer.drivers.embrane.driver.EmbraneLbaas:default
|
|
||||||
# Uncomment the line below to use the A10 Networks LBaaS driver. Requires 'pip install a10-neutron-lbaas'.
|
|
||||||
# service_provider = LOADBALANCER:A10Networks:neutron_lbaas.services.loadbalancer.drivers.a10networks.driver_v1.ThunderDriver:default
|
|
||||||
# Uncomment the following line to test the LBaaS v2 API _WITHOUT_ a real backend
|
|
||||||
# service_provider = LOADBALANCERV2:LoggingNoop:neutron_lbaas.services.loadbalancer.drivers.logging_noop.driver.LoggingNoopLoadBalancerDriver:default
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
[radware]
|
|
||||||
#vdirect_address = 0.0.0.0
|
|
||||||
#ha_secondary_address=
|
|
||||||
#vdirect_user = vDirect
|
|
||||||
#vdirect_password = radware
|
|
||||||
#service_ha_pair = False
|
|
||||||
#service_throughput = 1000
|
|
||||||
#service_ssl_throughput = 200
|
|
||||||
#service_compression_throughput = 100
|
|
||||||
#service_cache = 20
|
|
||||||
#service_adc_type = VA
|
|
||||||
#service_adc_version=
|
|
||||||
#service_session_mirroring_enabled = False
|
|
||||||
#service_isl_vlan = -1
|
|
||||||
#service_resource_pool_ids = []
|
|
||||||
#actions_to_skip = 'setup_l2_l3'
|
|
||||||
#l4_action_name = 'BaseCreate'
|
|
||||||
#l2_l3_workflow_name = openstack_l2_l3
|
|
||||||
#l4_workflow_name = openstack_l4
|
|
||||||
#l2_l3_ctor_params = service: _REPLACE_, ha_network_name: HA-Network, ha_ip_pool_name: default, allocate_ha_vrrp: True, allocate_ha_ips: True
|
|
||||||
#l2_l3_setup_params = data_port: 1, data_ip_address: 192.168.200.99, data_ip_mask: 255.255.255.0, gateway: 192.168.200.1, ha_port: 2
|
|
||||||
|
|
||||||
[netscaler_driver]
|
|
||||||
#netscaler_ncc_uri = https://ncc_server.acme.org/ncc/v1/api
|
|
||||||
#netscaler_ncc_username = admin
|
|
||||||
#netscaler_ncc_password = secret
|
|
||||||
|
|
||||||
[heleoslb]
|
|
||||||
#esm_mgmt =
|
|
||||||
#admin_username =
|
|
||||||
#admin_password =
|
|
||||||
#lb_image =
|
|
||||||
#inband_id =
|
|
||||||
#oob_id =
|
|
||||||
#mgmt_id =
|
|
||||||
#dummy_utif_id =
|
|
||||||
#resource_pool_id =
|
|
||||||
#async_requests =
|
|
||||||
#lb_flavor = small
|
|
||||||
#sync_interval = 60
|
|
||||||
|
|
||||||
[haproxy]
|
|
||||||
#jinja_config_template = /opt/stack/neutron/neutron/services/drivers/haproxy/templates/haproxy_v1.4.template
|
|
@ -520,18 +520,13 @@ class ExtensionManager(object):
|
|||||||
implementation.
|
implementation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO(dougwig) - remove this after the service extensions move out
|
|
||||||
# While moving the extensions out of neutron into the service repos,
|
|
||||||
# don't double-load the same thing.
|
|
||||||
loaded = []
|
|
||||||
|
|
||||||
for path in self.path.split(':'):
|
for path in self.path.split(':'):
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
self._load_all_extensions_from_path(path, loaded)
|
self._load_all_extensions_from_path(path)
|
||||||
else:
|
else:
|
||||||
LOG.error(_LE("Extension path '%s' doesn't exist!"), path)
|
LOG.error(_LE("Extension path '%s' doesn't exist!"), path)
|
||||||
|
|
||||||
def _load_all_extensions_from_path(self, path, loaded):
|
def _load_all_extensions_from_path(self, path):
|
||||||
# Sorting the extension list makes the order in which they
|
# Sorting the extension list makes the order in which they
|
||||||
# are loaded predictable across a cluster of load-balanced
|
# are loaded predictable across a cluster of load-balanced
|
||||||
# Neutron Servers
|
# Neutron Servers
|
||||||
@ -541,12 +536,7 @@ class ExtensionManager(object):
|
|||||||
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
|
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
|
||||||
ext_path = os.path.join(path, f)
|
ext_path = os.path.join(path, f)
|
||||||
if file_ext.lower() == '.py' and not mod_name.startswith('_'):
|
if file_ext.lower() == '.py' and not mod_name.startswith('_'):
|
||||||
if mod_name in loaded:
|
|
||||||
LOG.warn(_LW("Extension already loaded, skipping: %s"),
|
|
||||||
mod_name)
|
|
||||||
continue
|
|
||||||
mod = imp.load_source(mod_name, ext_path)
|
mod = imp.load_source(mod_name, ext_path)
|
||||||
loaded.append(mod_name)
|
|
||||||
ext_name = mod_name[0].upper() + mod_name[1:]
|
ext_name = mod_name[0].upper() + mod_name[1:]
|
||||||
new_ext_class = getattr(mod, ext_name, None)
|
new_ext_class = getattr(mod, ext_name, None)
|
||||||
if not new_ext_class:
|
if not new_ext_class:
|
||||||
|
@ -17,6 +17,8 @@ import ConfigParser
|
|||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
from neutron.openstack.common import log as logging
|
from neutron.openstack.common import log as logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -24,15 +26,21 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NeutronModules(object):
|
class NeutronModules(object):
|
||||||
|
|
||||||
MODULES = [
|
MODULES = {
|
||||||
'neutron_fwaas',
|
'neutron_fwaas': {
|
||||||
'neutron_lbaas',
|
'alembic-name': 'fwaas',
|
||||||
'neutron_vpnaas'
|
},
|
||||||
]
|
'neutron_lbaas': {
|
||||||
|
'alembic-name': 'lbaas',
|
||||||
|
},
|
||||||
|
'neutron_vpnaas': {
|
||||||
|
'alembic-name': 'vpnaas',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.repos = {}
|
self.repos = {}
|
||||||
for repo in self.MODULES:
|
for repo in self.MODULES.iterkeys():
|
||||||
self.repos[repo] = {}
|
self.repos[repo] = {}
|
||||||
self.repos[repo]['mod'] = self._import_or_none(repo)
|
self.repos[repo]['mod'] = self._import_or_none(repo)
|
||||||
self.repos[repo]['ini'] = None
|
self.repos[repo]['ini'] = None
|
||||||
@ -51,18 +59,42 @@ class NeutronModules(object):
|
|||||||
def module(self, module):
|
def module(self, module):
|
||||||
return self.repos[module]['mod']
|
return self.repos[module]['mod']
|
||||||
|
|
||||||
|
def alembic_name(self, module):
|
||||||
|
return self.MODULES[module]['alembic-name']
|
||||||
|
|
||||||
# Return an INI parser for the child module. oslo.conf is a bit too
|
# Return an INI parser for the child module. oslo.conf is a bit too
|
||||||
# magical in its INI loading, and in one notable case, we need to merge
|
# magical in its INI loading, and in one notable case, we need to merge
|
||||||
# together the [service_providers] section for across at least four
|
# together the [service_providers] section across at least four
|
||||||
# repositories.
|
# repositories.
|
||||||
def ini(self, module):
|
def ini(self, module):
|
||||||
if self.repos[module]['ini'] is None:
|
if self.repos[module]['ini'] is None:
|
||||||
ini = ConfigParser.SafeConfigParser()
|
neutron_dir = None
|
||||||
|
try:
|
||||||
|
neutron_dir = cfg.CONF.config_dir
|
||||||
|
except cfg.NoSuchOptError:
|
||||||
|
pass
|
||||||
|
|
||||||
ini_path = '/etc/neutron/%s.conf' % module
|
if neutron_dir is None:
|
||||||
|
neutron_dir = '/etc/neutron'
|
||||||
|
|
||||||
|
ini = ConfigParser.SafeConfigParser()
|
||||||
|
ini_path = os.path.join(neutron_dir, '%s.conf' % module)
|
||||||
if os.path.exists(ini_path):
|
if os.path.exists(ini_path):
|
||||||
ini.read(ini_path)
|
ini.read(ini_path)
|
||||||
|
|
||||||
self.repos[module]['ini'] = ini
|
self.repos[module]['ini'] = ini
|
||||||
|
|
||||||
return self.repos[module]['ini']
|
return self.repos[module]['ini']
|
||||||
|
|
||||||
|
def service_providers(self, module):
|
||||||
|
ini = self.ini(module)
|
||||||
|
|
||||||
|
sp = []
|
||||||
|
try:
|
||||||
|
for name, value in ini.items('service_providers'):
|
||||||
|
if name == 'service_provider':
|
||||||
|
sp.append(value)
|
||||||
|
except ConfigParser.NoSectionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return sp
|
||||||
|
@ -23,12 +23,12 @@ from alembic import util as alembic_util
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
from oslo.utils import importutils
|
from oslo.utils import importutils
|
||||||
|
|
||||||
|
from neutron.common import repos
|
||||||
|
|
||||||
HEAD_FILENAME = 'HEAD'
|
HEAD_FILENAME = 'HEAD'
|
||||||
LBAAS_SERVICE = 'lbaas'
|
|
||||||
FWAAS_SERVICE = 'fwaas'
|
mods = repos.NeutronModules()
|
||||||
VPNAAS_SERVICE = 'vpnaas'
|
VALID_SERVICES = map(lambda x: mods.alembic_name(x), mods.installed_list())
|
||||||
VALID_SERVICES = (LBAAS_SERVICE, FWAAS_SERVICE, VPNAAS_SERVICE)
|
|
||||||
|
|
||||||
|
|
||||||
_core_opts = [
|
_core_opts = [
|
||||||
|
@ -1,470 +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 abc
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
import six
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.api.v2 import resource_helper
|
|
||||||
from neutron.common import exceptions as nexception
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
from neutron.services import service_base
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Firewall Exceptions
|
|
||||||
class FirewallNotFound(nexception.NotFound):
|
|
||||||
message = _("Firewall %(firewall_id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallInUse(nexception.InUse):
|
|
||||||
message = _("Firewall %(firewall_id)s is still active.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallInPendingState(nexception.Conflict):
|
|
||||||
message = _("Operation cannot be performed since associated Firewall "
|
|
||||||
"%(firewall_id)s is in %(pending_state)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallPolicyNotFound(nexception.NotFound):
|
|
||||||
message = _("Firewall Policy %(firewall_policy_id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallPolicyInUse(nexception.InUse):
|
|
||||||
message = _("Firewall Policy %(firewall_policy_id)s is being used.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleSharingConflict(nexception.Conflict):
|
|
||||||
|
|
||||||
"""FWaaS exception for firewall rules
|
|
||||||
|
|
||||||
When a shared policy is created or updated with unshared rules,
|
|
||||||
this exception will be raised.
|
|
||||||
"""
|
|
||||||
message = _("Operation cannot be performed since Firewall Policy "
|
|
||||||
"%(firewall_policy_id)s is shared but Firewall Rule "
|
|
||||||
"%(firewall_rule_id)s is not shared")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallPolicySharingConflict(nexception.Conflict):
|
|
||||||
|
|
||||||
"""FWaaS exception for firewall policy
|
|
||||||
|
|
||||||
When a policy is shared without sharing its associated rules,
|
|
||||||
this exception will be raised.
|
|
||||||
"""
|
|
||||||
message = _("Operation cannot be performed. Before sharing Firewall "
|
|
||||||
"Policy %(firewall_policy_id)s, share associated Firewall "
|
|
||||||
"Rule %(firewall_rule_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleNotFound(nexception.NotFound):
|
|
||||||
message = _("Firewall Rule %(firewall_rule_id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleInUse(nexception.InUse):
|
|
||||||
message = _("Firewall Rule %(firewall_rule_id)s is being used.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleNotAssociatedWithPolicy(nexception.InvalidInput):
|
|
||||||
message = _("Firewall Rule %(firewall_rule_id)s is not associated "
|
|
||||||
" with Firewall Policy %(firewall_policy_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleInvalidProtocol(nexception.InvalidInput):
|
|
||||||
message = _("Firewall Rule protocol %(protocol)s is not supported. "
|
|
||||||
"Only protocol values %(values)s and their integer "
|
|
||||||
"representation (0 to 255) are supported.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleInvalidAction(nexception.InvalidInput):
|
|
||||||
message = _("Firewall rule action %(action)s is not supported. "
|
|
||||||
"Only action values %(values)s are supported.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleInvalidICMPParameter(nexception.InvalidInput):
|
|
||||||
message = _("%(param)s are not allowed when protocol "
|
|
||||||
"is set to ICMP.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleWithPortWithoutProtocolInvalid(nexception.InvalidInput):
|
|
||||||
message = _("Source/destination port requires a protocol")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallInvalidPortValue(nexception.InvalidInput):
|
|
||||||
message = _("Invalid value for port %(port)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleInfoMissing(nexception.InvalidInput):
|
|
||||||
message = _("Missing rule info argument for insert/remove "
|
|
||||||
"rule operation.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallInternalDriverError(nexception.NeutronException):
|
|
||||||
"""Fwaas exception for all driver errors.
|
|
||||||
|
|
||||||
On any failure or exception in the driver, driver should log it and
|
|
||||||
raise this exception to the agent
|
|
||||||
"""
|
|
||||||
message = _("%(driver)s: Internal driver error.")
|
|
||||||
|
|
||||||
|
|
||||||
class FirewallRuleConflict(nexception.Conflict):
|
|
||||||
|
|
||||||
"""Firewall rule conflict exception.
|
|
||||||
|
|
||||||
Occurs when admin policy tries to use another tenant's unshared
|
|
||||||
rule.
|
|
||||||
"""
|
|
||||||
|
|
||||||
message = _("Operation cannot be performed since Firewall Rule "
|
|
||||||
"%(firewall_rule_id)s is not shared and belongs to "
|
|
||||||
"another tenant %(tenant_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
fw_valid_protocol_values = [None, constants.TCP, constants.UDP, constants.ICMP]
|
|
||||||
fw_valid_action_values = [constants.FWAAS_ALLOW, constants.FWAAS_DENY]
|
|
||||||
|
|
||||||
|
|
||||||
def convert_protocol(value):
|
|
||||||
if value is None:
|
|
||||||
return
|
|
||||||
if value.isdigit():
|
|
||||||
val = int(value)
|
|
||||||
if 0 <= val <= 255:
|
|
||||||
return val
|
|
||||||
else:
|
|
||||||
raise FirewallRuleInvalidProtocol(
|
|
||||||
protocol=value,
|
|
||||||
values=fw_valid_protocol_values)
|
|
||||||
elif value.lower() in fw_valid_protocol_values:
|
|
||||||
return value.lower()
|
|
||||||
else:
|
|
||||||
raise FirewallRuleInvalidProtocol(
|
|
||||||
protocol=value,
|
|
||||||
values=fw_valid_protocol_values)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_action_to_case_insensitive(value):
|
|
||||||
if value is None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return value.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def convert_port_to_string(value):
|
|
||||||
if value is None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_port_range(data, key_specs=None):
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
data = str(data)
|
|
||||||
ports = data.split(':')
|
|
||||||
for p in ports:
|
|
||||||
try:
|
|
||||||
val = int(p)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
msg = _("Port '%s' is not a valid number") % p
|
|
||||||
LOG.debug(msg)
|
|
||||||
return msg
|
|
||||||
if val <= 0 or val > 65535:
|
|
||||||
msg = _("Invalid port '%s'") % p
|
|
||||||
LOG.debug(msg)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_ip_or_subnet_or_none(data, valid_values=None):
|
|
||||||
if data is None:
|
|
||||||
return None
|
|
||||||
msg_ip = attr._validate_ip_address(data, valid_values)
|
|
||||||
if not msg_ip:
|
|
||||||
return
|
|
||||||
msg_subnet = attr._validate_subnet(data, valid_values)
|
|
||||||
if not msg_subnet:
|
|
||||||
return
|
|
||||||
return _("%(msg_ip)s and %(msg_subnet)s") % {'msg_ip': msg_ip,
|
|
||||||
'msg_subnet': msg_subnet}
|
|
||||||
|
|
||||||
|
|
||||||
attr.validators['type:port_range'] = _validate_port_range
|
|
||||||
attr.validators['type:ip_or_subnet_or_none'] = _validate_ip_or_subnet_or_none
|
|
||||||
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'firewall_rules': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True, 'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'firewall_policy_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid_or_none': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'shared': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True, 'required_by_policy': True,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'protocol': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': None,
|
|
||||||
'convert_to': convert_protocol,
|
|
||||||
'validate': {'type:values': fw_valid_protocol_values}},
|
|
||||||
'ip_version': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 4, 'convert_to': attr.convert_to_int,
|
|
||||||
'validate': {'type:values': [4, 6]},
|
|
||||||
'is_visible': True},
|
|
||||||
'source_ip_address': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:ip_or_subnet_or_none': None},
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
'destination_ip_address': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:ip_or_subnet_or_none':
|
|
||||||
None},
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
'source_port': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:port_range': None},
|
|
||||||
'convert_to': convert_port_to_string,
|
|
||||||
'default': None, 'is_visible': True},
|
|
||||||
'destination_port': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:port_range': None},
|
|
||||||
'convert_to': convert_port_to_string,
|
|
||||||
'default': None, 'is_visible': True},
|
|
||||||
'position': {'allow_post': False, 'allow_put': False,
|
|
||||||
'default': None, 'is_visible': True},
|
|
||||||
'action': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': convert_action_to_case_insensitive,
|
|
||||||
'validate': {'type:values': fw_valid_action_values},
|
|
||||||
'is_visible': True, 'default': 'deny'},
|
|
||||||
'enabled': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True, 'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
'firewall_policies': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'shared': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True, 'required_by_policy': True,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'firewall_rules': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid_list': None},
|
|
||||||
'convert_to': attr.convert_none_to_empty_list,
|
|
||||||
'default': None, 'is_visible': True},
|
|
||||||
'audited': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
'firewalls': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'shared': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': False, 'required_by_policy': True,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'firewall_policy_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid_or_none': None},
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
firewall_quota_opts = [
|
|
||||||
cfg.IntOpt('quota_firewall',
|
|
||||||
default=1,
|
|
||||||
help=_('Number of firewalls allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_firewall_policy',
|
|
||||||
default=1,
|
|
||||||
help=_('Number of firewall policies allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_firewall_rule',
|
|
||||||
default=100,
|
|
||||||
help=_('Number of firewall rules allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
]
|
|
||||||
cfg.CONF.register_opts(firewall_quota_opts, 'QUOTAS')
|
|
||||||
|
|
||||||
|
|
||||||
class Firewall(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Firewall service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "fwaas"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Extension for Firewall service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://wiki.openstack.org/Neutron/FWaaS/API_1.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2013-02-25T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
special_mappings = {'firewall_policies': 'firewall_policy'}
|
|
||||||
plural_mappings = resource_helper.build_plural_mappings(
|
|
||||||
special_mappings, RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
attr.PLURALS.update(plural_mappings)
|
|
||||||
action_map = {'firewall_policy': {'insert_rule': 'PUT',
|
|
||||||
'remove_rule': 'PUT'}}
|
|
||||||
return resource_helper.build_resource_info(plural_mappings,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
constants.FIREWALL,
|
|
||||||
action_map=action_map)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_plugin_interface(cls):
|
|
||||||
return FirewallPluginBase
|
|
||||||
|
|
||||||
def update_attributes_map(self, attributes):
|
|
||||||
super(Firewall, self).update_attributes_map(
|
|
||||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return RESOURCE_ATTRIBUTE_MAP
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class FirewallPluginBase(service_base.ServicePluginBase):
|
|
||||||
|
|
||||||
def get_plugin_name(self):
|
|
||||||
return constants.FIREWALL
|
|
||||||
|
|
||||||
def get_plugin_type(self):
|
|
||||||
return constants.FIREWALL
|
|
||||||
|
|
||||||
def get_plugin_description(self):
|
|
||||||
return 'Firewall service plugin'
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewalls(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewall(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_firewall(self, context, firewall):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_firewall(self, context, id, firewall):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_firewall(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewall_rules(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewall_rule(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_firewall_rule(self, context, firewall_rule):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_firewall_rule(self, context, id, firewall_rule):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_firewall_rule(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewall_policy(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_firewall_policies(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_firewall_policy(self, context, firewall_policy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_firewall_policy(self, context, id, firewall_policy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_firewall_policy(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def insert_rule(self, context, id, rule_info):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def remove_rule(self, context, id, rule_info):
|
|
||||||
pass
|
|
@ -1,137 +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 abc
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron.api.v2 import resource
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron.extensions import agent
|
|
||||||
from neutron.extensions import loadbalancer
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.common import constants as plugin_const
|
|
||||||
from neutron import policy
|
|
||||||
from neutron import wsgi
|
|
||||||
|
|
||||||
LOADBALANCER_POOL = 'loadbalancer-pool'
|
|
||||||
LOADBALANCER_POOLS = LOADBALANCER_POOL + 's'
|
|
||||||
LOADBALANCER_AGENT = 'loadbalancer-agent'
|
|
||||||
|
|
||||||
|
|
||||||
class PoolSchedulerController(wsgi.Controller):
|
|
||||||
def index(self, request, **kwargs):
|
|
||||||
lbaas_plugin = manager.NeutronManager.get_service_plugins().get(
|
|
||||||
plugin_const.LOADBALANCER)
|
|
||||||
if not lbaas_plugin:
|
|
||||||
return {'pools': []}
|
|
||||||
|
|
||||||
policy.enforce(request.context,
|
|
||||||
"get_%s" % LOADBALANCER_POOLS,
|
|
||||||
{},
|
|
||||||
plugin=lbaas_plugin)
|
|
||||||
return lbaas_plugin.list_pools_on_lbaas_agent(
|
|
||||||
request.context, kwargs['agent_id'])
|
|
||||||
|
|
||||||
|
|
||||||
class LbaasAgentHostingPoolController(wsgi.Controller):
|
|
||||||
def index(self, request, **kwargs):
|
|
||||||
lbaas_plugin = manager.NeutronManager.get_service_plugins().get(
|
|
||||||
plugin_const.LOADBALANCER)
|
|
||||||
if not lbaas_plugin:
|
|
||||||
return
|
|
||||||
|
|
||||||
policy.enforce(request.context,
|
|
||||||
"get_%s" % LOADBALANCER_AGENT,
|
|
||||||
{},
|
|
||||||
plugin=lbaas_plugin)
|
|
||||||
return lbaas_plugin.get_lbaas_agent_hosting_pool(
|
|
||||||
request.context, kwargs['pool_id'])
|
|
||||||
|
|
||||||
|
|
||||||
class Lbaas_agentscheduler(extensions.ExtensionDescriptor):
|
|
||||||
"""Extension class supporting LBaaS agent scheduler.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Loadbalancer Agent Scheduler"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return constants.LBAAS_AGENT_SCHEDULER_EXT_ALIAS
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Schedule pools among lbaas agents"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://docs.openstack.org/ext/lbaas_agent_scheduler/api/v1.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2013-02-07T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
"""Returns Ext Resources."""
|
|
||||||
exts = []
|
|
||||||
parent = dict(member_name="agent",
|
|
||||||
collection_name="agents")
|
|
||||||
|
|
||||||
controller = resource.Resource(PoolSchedulerController(),
|
|
||||||
base.FAULT_MAP)
|
|
||||||
exts.append(extensions.ResourceExtension(
|
|
||||||
LOADBALANCER_POOLS, controller, parent))
|
|
||||||
|
|
||||||
parent = dict(member_name="pool",
|
|
||||||
collection_name="pools")
|
|
||||||
|
|
||||||
controller = resource.Resource(LbaasAgentHostingPoolController(),
|
|
||||||
base.FAULT_MAP)
|
|
||||||
exts.append(extensions.ResourceExtension(
|
|
||||||
LOADBALANCER_AGENT, controller, parent,
|
|
||||||
path_prefix=plugin_const.
|
|
||||||
COMMON_PREFIXES[plugin_const.LOADBALANCER]))
|
|
||||||
return exts
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class NoEligibleLbaasAgent(loadbalancer.NoEligibleBackend):
|
|
||||||
message = _("No eligible loadbalancer agent found "
|
|
||||||
"for pool %(pool_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoActiveLbaasAgent(agent.AgentNotFound):
|
|
||||||
message = _("No active loadbalancer agent found "
|
|
||||||
"for pool %(pool_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class LbaasAgentSchedulerPluginBase(object):
|
|
||||||
"""REST API to operate the lbaas agent scheduler.
|
|
||||||
|
|
||||||
All of method must be in an admin context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def list_pools_on_lbaas_agent(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_lbaas_agent_hosting_pool(self, context, pool_id):
|
|
||||||
pass
|
|
@ -1,508 +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 abc
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
import six
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron.api.v2 import resource_helper
|
|
||||||
from neutron.common import exceptions as nexception
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
from neutron.services import service_base
|
|
||||||
|
|
||||||
|
|
||||||
# Loadbalancer Exceptions
|
|
||||||
class DelayOrTimeoutInvalid(nexception.BadRequest):
|
|
||||||
message = _("Delay must be greater than or equal to timeout")
|
|
||||||
|
|
||||||
|
|
||||||
class NoEligibleBackend(nexception.NotFound):
|
|
||||||
message = _("No eligible backend for pool %(pool_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class VipNotFound(nexception.NotFound):
|
|
||||||
message = _("Vip %(vip_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class VipExists(nexception.NeutronException):
|
|
||||||
message = _("Another Vip already exists for pool %(pool_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class PoolNotFound(nexception.NotFound):
|
|
||||||
message = _("Pool %(pool_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class MemberNotFound(nexception.NotFound):
|
|
||||||
message = _("Member %(member_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class HealthMonitorNotFound(nexception.NotFound):
|
|
||||||
message = _("Health_monitor %(monitor_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class PoolMonitorAssociationNotFound(nexception.NotFound):
|
|
||||||
message = _("Monitor %(monitor_id)s is not associated "
|
|
||||||
"with Pool %(pool_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class PoolMonitorAssociationExists(nexception.Conflict):
|
|
||||||
message = _('health_monitor %(monitor_id)s is already associated '
|
|
||||||
'with pool %(pool_id)s')
|
|
||||||
|
|
||||||
|
|
||||||
class StateInvalid(nexception.NeutronException):
|
|
||||||
message = _("Invalid state %(state)s of Loadbalancer resource %(id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class PoolInUse(nexception.InUse):
|
|
||||||
message = _("Pool %(pool_id)s is still in use")
|
|
||||||
|
|
||||||
|
|
||||||
class HealthMonitorInUse(nexception.InUse):
|
|
||||||
message = _("Health monitor %(monitor_id)s still has associations with "
|
|
||||||
"pools")
|
|
||||||
|
|
||||||
|
|
||||||
class PoolStatsNotFound(nexception.NotFound):
|
|
||||||
message = _("Statistics of Pool %(pool_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class ProtocolMismatch(nexception.BadRequest):
|
|
||||||
message = _("Protocol %(vip_proto)s does not match "
|
|
||||||
"pool protocol %(pool_proto)s")
|
|
||||||
|
|
||||||
|
|
||||||
class MemberExists(nexception.NeutronException):
|
|
||||||
message = _("Member with address %(address)s and port %(port)s "
|
|
||||||
"already present in pool %(pool)s")
|
|
||||||
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'vips': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '',
|
|
||||||
'is_visible': True},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'address': {'allow_post': True, 'allow_put': False,
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED,
|
|
||||||
'validate': {'type:ip_address_or_none': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'port_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol_port': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': [0, 65535]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': ['TCP', 'HTTP', 'HTTPS']},
|
|
||||||
'is_visible': True},
|
|
||||||
'pool_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'session_persistence': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_none_to_empty_dict,
|
|
||||||
'default': {},
|
|
||||||
'validate': {
|
|
||||||
'type:dict_or_empty': {
|
|
||||||
'type': {'type:values': ['APP_COOKIE',
|
|
||||||
'HTTP_COOKIE',
|
|
||||||
'SOURCE_IP'],
|
|
||||||
'required': True},
|
|
||||||
'cookie_name': {'type:string': None,
|
|
||||||
'required': False}}},
|
|
||||||
'is_visible': True},
|
|
||||||
'connection_limit': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': -1,
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'status_description': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'pools': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'vip_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '',
|
|
||||||
'is_visible': True},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': ['TCP', 'HTTP', 'HTTPS']},
|
|
||||||
'is_visible': True},
|
|
||||||
'provider': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
|
|
||||||
'lb_method': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:values': ['ROUND_ROBIN',
|
|
||||||
'LEAST_CONNECTIONS',
|
|
||||||
'SOURCE_IP']},
|
|
||||||
'is_visible': True},
|
|
||||||
'members': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'health_monitors': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': None,
|
|
||||||
'validate': {'type:uuid_list': None},
|
|
||||||
'convert_to': attr.convert_to_list,
|
|
||||||
'is_visible': True},
|
|
||||||
'health_monitors_status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'status_description': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'members': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'pool_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'address': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:ip_address': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol_port': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': [0, 65535]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'weight': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 1,
|
|
||||||
'validate': {'type:range': [0, 256]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'status_description': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'health_monitors': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'type': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': ['PING', 'TCP', 'HTTP', 'HTTPS']},
|
|
||||||
'is_visible': True},
|
|
||||||
'delay': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:non_negative': None},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'timeout': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:non_negative': None},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'max_retries': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:range': [1, 10]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'http_method': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': 'GET',
|
|
||||||
'is_visible': True},
|
|
||||||
'url_path': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '/',
|
|
||||||
'is_visible': True},
|
|
||||||
'expected_codes': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {
|
|
||||||
'type:regex':
|
|
||||||
r'^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'},
|
|
||||||
'default': '200',
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'status_description': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'pools': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'health_monitors': {
|
|
||||||
'parent': {'collection_name': 'pools',
|
|
||||||
'member_name': 'pool'},
|
|
||||||
'parameters': {'id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lbaas_quota_opts = [
|
|
||||||
cfg.IntOpt('quota_vip',
|
|
||||||
default=10,
|
|
||||||
help=_('Number of vips allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_pool',
|
|
||||||
default=10,
|
|
||||||
help=_('Number of pools allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_member',
|
|
||||||
default=-1,
|
|
||||||
help=_('Number of pool members allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_health_monitor',
|
|
||||||
default=-1,
|
|
||||||
help=_('Number of health monitors allowed per tenant. '
|
|
||||||
'A negative value means unlimited.'))
|
|
||||||
]
|
|
||||||
cfg.CONF.register_opts(lbaas_quota_opts, 'QUOTAS')
|
|
||||||
|
|
||||||
|
|
||||||
class Loadbalancer(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "LoadBalancing service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "lbaas"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Extension for LoadBalancing service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://wiki.openstack.org/neutron/LBaaS/API_1.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2012-10-07T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
plural_mappings = resource_helper.build_plural_mappings(
|
|
||||||
{}, RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
plural_mappings['health_monitors_status'] = 'health_monitor_status'
|
|
||||||
attr.PLURALS.update(plural_mappings)
|
|
||||||
action_map = {'pool': {'stats': 'GET'}}
|
|
||||||
resources = resource_helper.build_resource_info(plural_mappings,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
constants.LOADBALANCER,
|
|
||||||
action_map=action_map,
|
|
||||||
register_quota=True)
|
|
||||||
plugin = manager.NeutronManager.get_service_plugins()[
|
|
||||||
constants.LOADBALANCER]
|
|
||||||
for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP:
|
|
||||||
# Special handling needed for sub-resources with 'y' ending
|
|
||||||
# (e.g. proxies -> proxy)
|
|
||||||
resource_name = collection_name[:-1]
|
|
||||||
parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent')
|
|
||||||
params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
|
|
||||||
'parameters')
|
|
||||||
|
|
||||||
controller = base.create_resource(collection_name, resource_name,
|
|
||||||
plugin, params,
|
|
||||||
allow_bulk=True,
|
|
||||||
parent=parent)
|
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
|
||||||
collection_name,
|
|
||||||
controller, parent,
|
|
||||||
path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER],
|
|
||||||
attr_map=params)
|
|
||||||
resources.append(resource)
|
|
||||||
|
|
||||||
return resources
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_plugin_interface(cls):
|
|
||||||
return LoadBalancerPluginBase
|
|
||||||
|
|
||||||
def update_attributes_map(self, attributes):
|
|
||||||
super(Loadbalancer, self).update_attributes_map(
|
|
||||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return RESOURCE_ATTRIBUTE_MAP
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class LoadBalancerPluginBase(service_base.ServicePluginBase):
|
|
||||||
|
|
||||||
def get_plugin_name(self):
|
|
||||||
return constants.LOADBALANCER
|
|
||||||
|
|
||||||
def get_plugin_type(self):
|
|
||||||
return constants.LOADBALANCER
|
|
||||||
|
|
||||||
def get_plugin_description(self):
|
|
||||||
return 'LoadBalancer service plugin'
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_vips(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_vip(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_vip(self, context, vip):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_vip(self, context, id, vip):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_vip(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pools(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pool(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_pool(self, context, pool):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_pool(self, context, id, pool):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_pool(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def stats(self, context, pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pool_health_monitor(self, context, id, pool_id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_pool_health_monitor(self, context, id, pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_members(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_member(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_member(self, context, member):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_member(self, context, id, member):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_member(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_health_monitors(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_health_monitor(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_health_monitor(self, context, health_monitor):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_health_monitor(self, context, id, health_monitor):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_health_monitor(self, context, id):
|
|
||||||
pass
|
|
@ -1,566 +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.
|
|
||||||
|
|
||||||
|
|
||||||
import abc
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
import six
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron.api.v2 import resource_helper
|
|
||||||
from neutron.common import exceptions as nexception
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
from neutron.services import service_base
|
|
||||||
|
|
||||||
# TODO(dougw) - stop hard-coding these constants when this extension moves
|
|
||||||
# to the neutron-lbaas repo
|
|
||||||
#from neutron.services.loadbalancer import constants as lb_const
|
|
||||||
|
|
||||||
LB_METHOD_ROUND_ROBIN = 'ROUND_ROBIN'
|
|
||||||
LB_METHOD_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS'
|
|
||||||
LB_METHOD_SOURCE_IP = 'SOURCE_IP'
|
|
||||||
SUPPORTED_LB_ALGORITHMS = (LB_METHOD_LEAST_CONNECTIONS, LB_METHOD_ROUND_ROBIN,
|
|
||||||
LB_METHOD_SOURCE_IP)
|
|
||||||
|
|
||||||
PROTOCOL_TCP = 'TCP'
|
|
||||||
PROTOCOL_HTTP = 'HTTP'
|
|
||||||
PROTOCOL_HTTPS = 'HTTPS'
|
|
||||||
SUPPORTED_PROTOCOLS = (PROTOCOL_TCP, PROTOCOL_HTTPS, PROTOCOL_HTTP)
|
|
||||||
|
|
||||||
|
|
||||||
HEALTH_MONITOR_PING = 'PING'
|
|
||||||
HEALTH_MONITOR_TCP = 'TCP'
|
|
||||||
HEALTH_MONITOR_HTTP = 'HTTP'
|
|
||||||
HEALTH_MONITOR_HTTPS = 'HTTPS'
|
|
||||||
SUPPORTED_HEALTH_MONITOR_TYPES = (HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS,
|
|
||||||
HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP)
|
|
||||||
|
|
||||||
|
|
||||||
SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP'
|
|
||||||
SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE'
|
|
||||||
SESSION_PERSISTENCE_APP_COOKIE = 'APP_COOKIE'
|
|
||||||
SUPPORTED_SP_TYPES = (SESSION_PERSISTENCE_SOURCE_IP,
|
|
||||||
SESSION_PERSISTENCE_HTTP_COOKIE,
|
|
||||||
SESSION_PERSISTENCE_APP_COOKIE)
|
|
||||||
|
|
||||||
|
|
||||||
# Loadbalancer Exceptions
|
|
||||||
# This exception is only for a workaround when having v1 and v2 lbaas extension
|
|
||||||
# and plugins enabled
|
|
||||||
class RequiredAttributeNotSpecified(nexception.BadRequest):
|
|
||||||
message = _("Required attribute %(attr_name)s not specified")
|
|
||||||
|
|
||||||
|
|
||||||
class EntityNotFound(nexception.NotFound):
|
|
||||||
message = _("%(name)s %(id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class DelayOrTimeoutInvalid(nexception.BadRequest):
|
|
||||||
message = _("Delay must be greater than or equal to timeout")
|
|
||||||
|
|
||||||
|
|
||||||
class EntityInUse(nexception.InUse):
|
|
||||||
message = _("%(entity_using)s %(id)s is using this %(entity_in_use)s")
|
|
||||||
|
|
||||||
|
|
||||||
class LoadBalancerListenerProtocolPortExists(nexception.Conflict):
|
|
||||||
message = _("Load Balancer %(lb_id)s already has a listener with "
|
|
||||||
"protocol_port of %(protocol_port)s")
|
|
||||||
|
|
||||||
|
|
||||||
class ListenerPoolProtocolMismatch(nexception.Conflict):
|
|
||||||
message = _("Listener protocol %(listener_proto)s and pool protocol "
|
|
||||||
"%(pool_proto)s are not compatible.")
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeIDImmutable(nexception.NeutronException):
|
|
||||||
message = _("Cannot change %(attribute)s if one already exists")
|
|
||||||
|
|
||||||
|
|
||||||
class StateInvalid(nexception.NeutronException):
|
|
||||||
message = _("Invalid state %(state)s of loadbalancer resource %(id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class MemberNotFoundForPool(nexception.NotFound):
|
|
||||||
message = _("Member %(member_id)s could not be found in pool %(pool_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class MemberExists(nexception.Conflict):
|
|
||||||
message = _("Member with address %(address)s and protocol_port %(port)s "
|
|
||||||
"already present in pool %(pool)s")
|
|
||||||
|
|
||||||
|
|
||||||
class MemberAddressTypeSubnetTypeMismatch(nexception.NeutronException):
|
|
||||||
message = _("Member with address %(address)s and subnet %(subnet_id) "
|
|
||||||
" have mismatched IP versions")
|
|
||||||
|
|
||||||
|
|
||||||
class DriverError(nexception.NeutronException):
|
|
||||||
message = _("An error happened in the driver")
|
|
||||||
|
|
||||||
|
|
||||||
class LBConfigurationUnsupported(nexception.NeutronException):
|
|
||||||
message = _("Load balancer %(load_balancer_id)s configuration is not"
|
|
||||||
"supported by driver %(driver_name)s")
|
|
||||||
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'loadbalancers': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '',
|
|
||||||
'is_visible': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'vip_subnet_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'vip_address': {'allow_post': True, 'allow_put': False,
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED,
|
|
||||||
'validate': {'type:ip_address_or_none': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'listeners': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '',
|
|
||||||
'is_visible': True},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'loadbalancer_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid_or_none': None},
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
'default_pool_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:uuid_or_none': None},
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
'connection_limit': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': -1,
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': SUPPORTED_PROTOCOLS},
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol_port': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': [0, 65535]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'pools': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'healthmonitor_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string_or_none': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED},
|
|
||||||
'protocol': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': SUPPORTED_PROTOCOLS},
|
|
||||||
'is_visible': True},
|
|
||||||
'lb_algorithm': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {
|
|
||||||
'type:values': SUPPORTED_LB_ALGORITHMS},
|
|
||||||
# TODO(brandon-logan) remove when old API is removed
|
|
||||||
# because this is a required attribute)
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
'session_persistence': {
|
|
||||||
'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_none_to_empty_dict,
|
|
||||||
'default': {},
|
|
||||||
'validate': {
|
|
||||||
'type:dict_or_empty': {
|
|
||||||
'type': {
|
|
||||||
'type:values': SUPPORTED_SP_TYPES,
|
|
||||||
'required': True},
|
|
||||||
'cookie_name': {'type:string': None,
|
|
||||||
'required': False}}},
|
|
||||||
'is_visible': True},
|
|
||||||
'members': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
'healthmonitors': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'type': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {
|
|
||||||
'type:values': SUPPORTED_HEALTH_MONITOR_TYPES},
|
|
||||||
'is_visible': True},
|
|
||||||
'delay': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:non_negative': None},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'timeout': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:non_negative': None},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'max_retries': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:range': [1, 10]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'http_method': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': 'GET',
|
|
||||||
'is_visible': True},
|
|
||||||
'url_path': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'default': '/',
|
|
||||||
'is_visible': True},
|
|
||||||
'expected_codes': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'validate': {
|
|
||||||
'type:regex': r'^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'
|
|
||||||
},
|
|
||||||
'default': '200',
|
|
||||||
'is_visible': True
|
|
||||||
},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'members': {
|
|
||||||
'parent': {'collection_name': 'pools',
|
|
||||||
'member_name': 'pool'},
|
|
||||||
'parameters': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'address': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:ip_address': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'protocol_port': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': [0, 65535]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'weight': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 1,
|
|
||||||
'validate': {'type:range': [0, 256]},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lbaasv2_quota_opts = [
|
|
||||||
cfg.IntOpt('quota_loadbalancer',
|
|
||||||
default=10,
|
|
||||||
help=_('Number of LoadBalancers allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_listener',
|
|
||||||
default=-1,
|
|
||||||
help=_('Number of Loadbalancer Listeners allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_pool',
|
|
||||||
default=10,
|
|
||||||
help=_('Number of pools allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_member',
|
|
||||||
default=-1,
|
|
||||||
help=_('Number of pool members allowed per tenant. '
|
|
||||||
'A negative value means unlimited.')),
|
|
||||||
cfg.IntOpt('quota_healthmonitor',
|
|
||||||
default=-1,
|
|
||||||
help=_('Number of health monitors allowed per tenant. '
|
|
||||||
'A negative value means unlimited.'))
|
|
||||||
]
|
|
||||||
cfg.CONF.register_opts(lbaasv2_quota_opts, 'QUOTAS')
|
|
||||||
|
|
||||||
|
|
||||||
class Loadbalancerv2(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "LoadBalancing service v2"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "lbaasv2"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Extension for LoadBalancing service v2"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://wiki.openstack.org/neutron/LBaaS/API_2.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2014-06-18T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
plural_mappings = resource_helper.build_plural_mappings(
|
|
||||||
{}, RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
action_map = {'loadbalancer': {'stats': 'GET'}}
|
|
||||||
plural_mappings['members'] = 'member'
|
|
||||||
attr.PLURALS.update(plural_mappings)
|
|
||||||
resources = resource_helper.build_resource_info(
|
|
||||||
plural_mappings,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
constants.LOADBALANCERV2,
|
|
||||||
action_map=action_map,
|
|
||||||
register_quota=True)
|
|
||||||
plugin = manager.NeutronManager.get_service_plugins()[
|
|
||||||
constants.LOADBALANCERV2]
|
|
||||||
for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP:
|
|
||||||
# Special handling needed for sub-resources with 'y' ending
|
|
||||||
# (e.g. proxies -> proxy)
|
|
||||||
resource_name = collection_name[:-1]
|
|
||||||
parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent')
|
|
||||||
params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
|
|
||||||
'parameters')
|
|
||||||
|
|
||||||
controller = base.create_resource(collection_name, resource_name,
|
|
||||||
plugin, params,
|
|
||||||
allow_bulk=True,
|
|
||||||
parent=parent,
|
|
||||||
allow_pagination=True,
|
|
||||||
allow_sorting=True)
|
|
||||||
|
|
||||||
resource = extensions.ResourceExtension(
|
|
||||||
collection_name,
|
|
||||||
controller, parent,
|
|
||||||
path_prefix=constants.COMMON_PREFIXES[
|
|
||||||
constants.LOADBALANCERV2],
|
|
||||||
attr_map=params)
|
|
||||||
resources.append(resource)
|
|
||||||
|
|
||||||
return resources
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_plugin_interface(cls):
|
|
||||||
return LoadBalancerPluginBaseV2
|
|
||||||
|
|
||||||
def update_attributes_map(self, attributes, extension_attrs_map=None):
|
|
||||||
super(Loadbalancerv2, self).update_attributes_map(
|
|
||||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return RESOURCE_ATTRIBUTE_MAP
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class LoadBalancerPluginBaseV2(service_base.ServicePluginBase):
|
|
||||||
|
|
||||||
def get_plugin_name(self):
|
|
||||||
return constants.LOADBALANCERV2
|
|
||||||
|
|
||||||
def get_plugin_type(self):
|
|
||||||
return constants.LOADBALANCERV2
|
|
||||||
|
|
||||||
def get_plugin_description(self):
|
|
||||||
return 'LoadBalancer service plugin v2'
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_loadbalancers(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_loadbalancer(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_loadbalancer(self, context, loadbalancer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_loadbalancer(self, context, id, loadbalancer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_loadbalancer(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_listener(self, context, listener):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_listener(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_listeners(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_listener(self, context, id, listener):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_listener(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pools(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pool(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_pool(self, context, pool):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_pool(self, context, id, pool):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_pool(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def stats(self, context, loadbalancer_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pool_members(self, context, pool_id,
|
|
||||||
filters=None,
|
|
||||||
fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_pool_member(self, context, id, pool_id,
|
|
||||||
fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_pool_member(self, context, member,
|
|
||||||
pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_pool_member(self, context, member, id,
|
|
||||||
pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_pool_member(self, context, id, pool_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_healthmonitors(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_healthmonitor(self, context, id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_healthmonitor(self, context, healthmonitor):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_healthmonitor(self, context, id, healthmonitor):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_healthmonitor(self, context, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_members(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_member(self, context, id, fields=None):
|
|
||||||
pass
|
|
@ -1,484 +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.
|
|
||||||
|
|
||||||
import abc
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.api.v2 import resource_helper
|
|
||||||
from neutron.common import exceptions as nexception
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
from neutron.services import service_base
|
|
||||||
|
|
||||||
|
|
||||||
class VPNServiceNotFound(nexception.NotFound):
|
|
||||||
message = _("VPNService %(vpnservice_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class IPsecSiteConnectionNotFound(nexception.NotFound):
|
|
||||||
message = _("ipsec_site_connection %(ipsec_site_conn_id)s not found")
|
|
||||||
|
|
||||||
|
|
||||||
class IPsecSiteConnectionDpdIntervalValueError(nexception.InvalidInput):
|
|
||||||
message = _("ipsec_site_connection %(attr)s is "
|
|
||||||
"equal to or less than dpd_interval")
|
|
||||||
|
|
||||||
|
|
||||||
class IPsecSiteConnectionMtuError(nexception.InvalidInput):
|
|
||||||
message = _("ipsec_site_connection MTU %(mtu)d is too small "
|
|
||||||
"for ipv%(version)s")
|
|
||||||
|
|
||||||
|
|
||||||
class IKEPolicyNotFound(nexception.NotFound):
|
|
||||||
message = _("IKEPolicy %(ikepolicy_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class IPsecPolicyNotFound(nexception.NotFound):
|
|
||||||
message = _("IPsecPolicy %(ipsecpolicy_id)s could not be found")
|
|
||||||
|
|
||||||
|
|
||||||
class IKEPolicyInUse(nexception.InUse):
|
|
||||||
message = _("IKEPolicy %(ikepolicy_id)s is in use by existing "
|
|
||||||
"IPsecSiteConnection and can't be updated or deleted")
|
|
||||||
|
|
||||||
|
|
||||||
class VPNServiceInUse(nexception.InUse):
|
|
||||||
message = _("VPNService %(vpnservice_id)s is still in use")
|
|
||||||
|
|
||||||
|
|
||||||
class RouterInUseByVPNService(nexception.InUse):
|
|
||||||
message = _("Router %(router_id)s is used by VPNService %(vpnservice_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class SubnetInUseByVPNService(nexception.InUse):
|
|
||||||
message = _("Subnet %(subnet_id)s is used by VPNService %(vpnservice_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class VPNStateInvalidToUpdate(nexception.BadRequest):
|
|
||||||
message = _("Invalid state %(state)s of vpnaas resource %(id)s"
|
|
||||||
" for updating")
|
|
||||||
|
|
||||||
|
|
||||||
class IPsecPolicyInUse(nexception.InUse):
|
|
||||||
message = _("IPsecPolicy %(ipsecpolicy_id)s is in use by existing "
|
|
||||||
"IPsecSiteConnection and can't be updated or deleted")
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceDriverImportError(nexception.NeutronException):
|
|
||||||
message = _("Can not load driver :%(device_driver)s")
|
|
||||||
|
|
||||||
|
|
||||||
class SubnetIsNotConnectedToRouter(nexception.BadRequest):
|
|
||||||
message = _("Subnet %(subnet_id)s is not "
|
|
||||||
"connected to Router %(router_id)s")
|
|
||||||
|
|
||||||
|
|
||||||
class RouterIsNotExternal(nexception.BadRequest):
|
|
||||||
message = _("Router %(router_id)s has no external network gateway set")
|
|
||||||
|
|
||||||
|
|
||||||
vpn_supported_initiators = ['bi-directional', 'response-only']
|
|
||||||
vpn_supported_encryption_algorithms = ['3des', 'aes-128',
|
|
||||||
'aes-192', 'aes-256']
|
|
||||||
vpn_dpd_supported_actions = [
|
|
||||||
'hold', 'clear', 'restart', 'restart-by-peer', 'disabled'
|
|
||||||
]
|
|
||||||
vpn_supported_transform_protocols = ['esp', 'ah', 'ah-esp']
|
|
||||||
vpn_supported_encapsulation_mode = ['tunnel', 'transport']
|
|
||||||
#TODO(nati) add kilobytes when we support it
|
|
||||||
vpn_supported_lifetime_units = ['seconds']
|
|
||||||
vpn_supported_pfs = ['group2', 'group5', 'group14']
|
|
||||||
vpn_supported_ike_versions = ['v1', 'v2']
|
|
||||||
vpn_supported_auth_mode = ['psk']
|
|
||||||
vpn_supported_auth_algorithms = ['sha1']
|
|
||||||
vpn_supported_phase1_negotiation_mode = ['main']
|
|
||||||
|
|
||||||
vpn_lifetime_limits = (60, attr.UNLIMITED)
|
|
||||||
positive_int = (0, attr.UNLIMITED)
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
|
|
||||||
'vpnservices': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'router_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
|
|
||||||
'ipsec_site_connections': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'peer_address': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'peer_id': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'peer_cidrs': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_to_list,
|
|
||||||
'validate': {'type:subnet_list': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'route_mode': {'allow_post': False, 'allow_put': False,
|
|
||||||
'default': 'static',
|
|
||||||
'is_visible': True},
|
|
||||||
'mtu': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': '1500',
|
|
||||||
'validate': {'type:range': positive_int},
|
|
||||||
'convert_to': attr.convert_to_int,
|
|
||||||
'is_visible': True},
|
|
||||||
'initiator': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'bi-directional',
|
|
||||||
'validate': {'type:values': vpn_supported_initiators},
|
|
||||||
'is_visible': True},
|
|
||||||
'auth_mode': {'allow_post': False, 'allow_put': False,
|
|
||||||
'default': 'psk',
|
|
||||||
'validate': {'type:values': vpn_supported_auth_mode},
|
|
||||||
'is_visible': True},
|
|
||||||
'psk': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'dpd': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_none_to_empty_dict,
|
|
||||||
'is_visible': True,
|
|
||||||
'default': {},
|
|
||||||
'validate': {
|
|
||||||
'type:dict_or_empty': {
|
|
||||||
'actions': {
|
|
||||||
'type:values': vpn_dpd_supported_actions,
|
|
||||||
},
|
|
||||||
'interval': {
|
|
||||||
'type:range': positive_int
|
|
||||||
},
|
|
||||||
'timeout': {
|
|
||||||
'type:range': positive_int
|
|
||||||
}}}},
|
|
||||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': True,
|
|
||||||
'convert_to': attr.convert_to_boolean,
|
|
||||||
'is_visible': True},
|
|
||||||
'status': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
'vpnservice_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'ikepolicy_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'ipsecpolicy_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
|
|
||||||
'ipsecpolicies': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'transform_protocol': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'default': 'esp',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_transform_protocols},
|
|
||||||
'is_visible': True},
|
|
||||||
'auth_algorithm': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'default': 'sha1',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_auth_algorithms
|
|
||||||
},
|
|
||||||
'is_visible': True},
|
|
||||||
'encryption_algorithm': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'default': 'aes-128',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_encryption_algorithms
|
|
||||||
},
|
|
||||||
'is_visible': True},
|
|
||||||
'encapsulation_mode': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'default': 'tunnel',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_encapsulation_mode
|
|
||||||
},
|
|
||||||
'is_visible': True},
|
|
||||||
'lifetime': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_none_to_empty_dict,
|
|
||||||
'default': {},
|
|
||||||
'validate': {
|
|
||||||
'type:dict_or_empty': {
|
|
||||||
'units': {
|
|
||||||
'type:values': vpn_supported_lifetime_units,
|
|
||||||
},
|
|
||||||
'value': {
|
|
||||||
'type:range': vpn_lifetime_limits
|
|
||||||
}}},
|
|
||||||
'is_visible': True},
|
|
||||||
'pfs': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'group5',
|
|
||||||
'validate': {'type:values': vpn_supported_pfs},
|
|
||||||
'is_visible': True}
|
|
||||||
},
|
|
||||||
|
|
||||||
'ikepolicies': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True,
|
|
||||||
'primary_key': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'required_by_policy': True,
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'auth_algorithm': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'sha1',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_auth_algorithms},
|
|
||||||
'is_visible': True},
|
|
||||||
'encryption_algorithm': {
|
|
||||||
'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'aes-128',
|
|
||||||
'validate': {'type:values': vpn_supported_encryption_algorithms},
|
|
||||||
'is_visible': True},
|
|
||||||
'phase1_negotiation_mode': {
|
|
||||||
'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'main',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_phase1_negotiation_mode
|
|
||||||
},
|
|
||||||
'is_visible': True},
|
|
||||||
'lifetime': {'allow_post': True, 'allow_put': True,
|
|
||||||
'convert_to': attr.convert_none_to_empty_dict,
|
|
||||||
'default': {},
|
|
||||||
'validate': {
|
|
||||||
'type:dict_or_empty': {
|
|
||||||
'units': {
|
|
||||||
'type:values': vpn_supported_lifetime_units,
|
|
||||||
},
|
|
||||||
'value': {
|
|
||||||
'type:range': vpn_lifetime_limits,
|
|
||||||
}}},
|
|
||||||
'is_visible': True},
|
|
||||||
'ike_version': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'v1',
|
|
||||||
'validate': {
|
|
||||||
'type:values': vpn_supported_ike_versions},
|
|
||||||
'is_visible': True},
|
|
||||||
'pfs': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': 'group5',
|
|
||||||
'validate': {'type:values': vpn_supported_pfs},
|
|
||||||
'is_visible': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Vpnaas(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "VPN service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "vpnaas"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Extension for VPN service"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "https://wiki.openstack.org/Neutron/VPNaaS"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2013-05-29T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
special_mappings = {'ikepolicies': 'ikepolicy',
|
|
||||||
'ipsecpolicies': 'ipsecpolicy'}
|
|
||||||
plural_mappings = resource_helper.build_plural_mappings(
|
|
||||||
special_mappings, RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
plural_mappings['peer_cidrs'] = 'peer_cidr'
|
|
||||||
attr.PLURALS.update(plural_mappings)
|
|
||||||
return resource_helper.build_resource_info(plural_mappings,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
constants.VPN,
|
|
||||||
register_quota=True,
|
|
||||||
translate_name=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_plugin_interface(cls):
|
|
||||||
return VPNPluginBase
|
|
||||||
|
|
||||||
def update_attributes_map(self, attributes):
|
|
||||||
super(Vpnaas, self).update_attributes_map(
|
|
||||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return RESOURCE_ATTRIBUTE_MAP
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class VPNPluginBase(service_base.ServicePluginBase):
|
|
||||||
|
|
||||||
def get_plugin_name(self):
|
|
||||||
return constants.VPN
|
|
||||||
|
|
||||||
def get_plugin_type(self):
|
|
||||||
return constants.VPN
|
|
||||||
|
|
||||||
def get_plugin_description(self):
|
|
||||||
return 'VPN service plugin'
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_vpnservices(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_vpnservice(self, context, vpnservice_id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_vpnservice(self, context, vpnservice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_vpnservice(self, context, vpnservice_id, vpnservice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_vpnservice(self, context, vpnservice_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ipsec_site_connections(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ipsec_site_connection(self, context,
|
|
||||||
ipsecsite_conn_id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_ipsec_site_connection(self, context,
|
|
||||||
ipsecsite_conn_id, ipsec_site_connection):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_ipsec_site_connection(self, context, ipsecsite_conn_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ikepolicy(self, context, ikepolicy_id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ikepolicies(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_ikepolicy(self, context, ikepolicy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_ikepolicy(self, context, ikepolicy_id, ikepolicy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_ikepolicy(self, context, ikepolicy_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ipsecpolicies(self, context, filters=None, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_ipsecpolicy(self, context, ipsecpolicy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_ipsecpolicy(self, context, ipsecpolicy_id):
|
|
||||||
pass
|
|
@ -79,23 +79,7 @@ def parse_service_provider_opt():
|
|||||||
# Add in entries from the *aas conf files
|
# Add in entries from the *aas conf files
|
||||||
neutron_mods = repos.NeutronModules()
|
neutron_mods = repos.NeutronModules()
|
||||||
for x in neutron_mods.installed_list():
|
for x in neutron_mods.installed_list():
|
||||||
ini = neutron_mods.ini(x)
|
svc_providers_opt += neutron_mods.service_providers(x)
|
||||||
if ini is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
sp = ini.items('service_providers')
|
|
||||||
for name, value in sp:
|
|
||||||
if name == 'service_provider':
|
|
||||||
svc_providers_opt.append(value)
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO(dougwig) - remove this next bit after we've migrated all entries
|
|
||||||
# to the service repo config files. Some tests require a default driver
|
|
||||||
# to be present, but not two, which leads to a cross-repo breakage
|
|
||||||
# issue. uniq the list as a short-term workaround.
|
|
||||||
svc_providers_opt = list(set(svc_providers_opt))
|
|
||||||
|
|
||||||
LOG.debug("Service providers = %s", svc_providers_opt)
|
LOG.debug("Service providers = %s", svc_providers_opt)
|
||||||
|
|
||||||
|
@ -1,486 +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 copy
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from webob import exc
|
|
||||||
import webtest
|
|
||||||
|
|
||||||
from neutron.extensions import firewall
|
|
||||||
from neutron.openstack.common import uuidutils
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
from neutron.tests import base
|
|
||||||
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 FirewallExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
|
|
||||||
fmt = 'json'
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(FirewallExtensionTestCase, self).setUp()
|
|
||||||
plural_mappings = {'firewall_policy': 'firewall_policies'}
|
|
||||||
self._setUpExtension(
|
|
||||||
'neutron.extensions.firewall.FirewallPluginBase',
|
|
||||||
constants.FIREWALL, firewall.RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
firewall.Firewall, 'fw', plural_mappings=plural_mappings)
|
|
||||||
|
|
||||||
def test_create_firewall(self):
|
|
||||||
fw_id = _uuid()
|
|
||||||
data = {'firewall': {'description': 'descr_firewall1',
|
|
||||||
'name': 'firewall1',
|
|
||||||
'admin_state_up': True,
|
|
||||||
'firewall_policy_id': _uuid(),
|
|
||||||
'shared': False,
|
|
||||||
'tenant_id': _uuid()}}
|
|
||||||
return_value = copy.copy(data['firewall'])
|
|
||||||
return_value.update({'id': fw_id})
|
|
||||||
# since 'shared' is hidden
|
|
||||||
del return_value['shared']
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.create_firewall.return_value = return_value
|
|
||||||
res = self.api.post(_get_path('fw/firewalls', fmt=self.fmt),
|
|
||||||
self.serialize(data),
|
|
||||||
content_type='application/%s' % self.fmt)
|
|
||||||
instance.create_firewall.assert_called_with(mock.ANY,
|
|
||||||
firewall=data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall', res)
|
|
||||||
self.assertEqual(res['firewall'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_list(self):
|
|
||||||
fw_id = _uuid()
|
|
||||||
return_value = [{'tenant_id': _uuid(),
|
|
||||||
'id': fw_id}]
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewalls.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewalls', fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewalls.assert_called_with(mock.ANY,
|
|
||||||
fields=mock.ANY,
|
|
||||||
filters=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
|
|
||||||
def test_firewall_get(self):
|
|
||||||
fw_id = _uuid()
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': fw_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewall.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewalls',
|
|
||||||
id=fw_id, fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewall.assert_called_with(mock.ANY,
|
|
||||||
fw_id,
|
|
||||||
fields=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall', res)
|
|
||||||
self.assertEqual(res['firewall'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_update(self):
|
|
||||||
fw_id = _uuid()
|
|
||||||
update_data = {'firewall': {'name': 'new_name'}}
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': fw_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.update_firewall.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.put(_get_path('fw/firewalls', id=fw_id,
|
|
||||||
fmt=self.fmt),
|
|
||||||
self.serialize(update_data))
|
|
||||||
|
|
||||||
instance.update_firewall.assert_called_with(mock.ANY, fw_id,
|
|
||||||
firewall=update_data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall', res)
|
|
||||||
self.assertEqual(res['firewall'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_delete(self):
|
|
||||||
self._test_entity_delete('firewall')
|
|
||||||
|
|
||||||
def _test_create_firewall_rule(self, src_port, dst_port):
|
|
||||||
rule_id = _uuid()
|
|
||||||
data = {'firewall_rule': {'description': 'descr_firewall_rule1',
|
|
||||||
'name': 'rule1',
|
|
||||||
'shared': False,
|
|
||||||
'protocol': 'tcp',
|
|
||||||
'ip_version': 4,
|
|
||||||
'source_ip_address': '192.168.0.1',
|
|
||||||
'destination_ip_address': '127.0.0.1',
|
|
||||||
'source_port': src_port,
|
|
||||||
'destination_port': dst_port,
|
|
||||||
'action': 'allow',
|
|
||||||
'enabled': True,
|
|
||||||
'tenant_id': _uuid()}}
|
|
||||||
expected_ret_val = copy.copy(data['firewall_rule'])
|
|
||||||
expected_ret_val['source_port'] = str(src_port)
|
|
||||||
expected_ret_val['destination_port'] = str(dst_port)
|
|
||||||
expected_call_args = copy.copy(expected_ret_val)
|
|
||||||
expected_ret_val['id'] = rule_id
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.create_firewall_rule.return_value = expected_ret_val
|
|
||||||
res = self.api.post(_get_path('fw/firewall_rules', fmt=self.fmt),
|
|
||||||
self.serialize(data),
|
|
||||||
content_type='application/%s' % self.fmt)
|
|
||||||
instance.create_firewall_rule.assert_called_with(
|
|
||||||
mock.ANY,
|
|
||||||
firewall_rule={'firewall_rule': expected_call_args})
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_rule', res)
|
|
||||||
self.assertEqual(res['firewall_rule'], expected_ret_val)
|
|
||||||
|
|
||||||
def test_create_firewall_rule_with_integer_ports(self):
|
|
||||||
self._test_create_firewall_rule(1, 10)
|
|
||||||
|
|
||||||
def test_create_firewall_rule_with_string_ports(self):
|
|
||||||
self._test_create_firewall_rule('1', '10')
|
|
||||||
|
|
||||||
def test_create_firewall_rule_with_port_range(self):
|
|
||||||
self._test_create_firewall_rule('1:20', '30:40')
|
|
||||||
|
|
||||||
def test_firewall_rule_list(self):
|
|
||||||
rule_id = _uuid()
|
|
||||||
return_value = [{'tenant_id': _uuid(),
|
|
||||||
'id': rule_id}]
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewall_rules.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewall_rules', fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewall_rules.assert_called_with(mock.ANY,
|
|
||||||
fields=mock.ANY,
|
|
||||||
filters=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
|
|
||||||
def test_firewall_rule_get(self):
|
|
||||||
rule_id = _uuid()
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': rule_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewall_rule.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewall_rules',
|
|
||||||
id=rule_id, fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewall_rule.assert_called_with(mock.ANY,
|
|
||||||
rule_id,
|
|
||||||
fields=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_rule', res)
|
|
||||||
self.assertEqual(res['firewall_rule'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_rule_update(self):
|
|
||||||
rule_id = _uuid()
|
|
||||||
update_data = {'firewall_rule': {'action': 'deny'}}
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': rule_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.update_firewall_rule.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.put(_get_path('fw/firewall_rules', id=rule_id,
|
|
||||||
fmt=self.fmt),
|
|
||||||
self.serialize(update_data))
|
|
||||||
|
|
||||||
instance.update_firewall_rule.assert_called_with(
|
|
||||||
mock.ANY,
|
|
||||||
rule_id,
|
|
||||||
firewall_rule=update_data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_rule', res)
|
|
||||||
self.assertEqual(res['firewall_rule'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_rule_delete(self):
|
|
||||||
self._test_entity_delete('firewall_rule')
|
|
||||||
|
|
||||||
def test_create_firewall_policy(self):
|
|
||||||
policy_id = _uuid()
|
|
||||||
data = {'firewall_policy': {'description': 'descr_firewall_policy1',
|
|
||||||
'name': 'new_fw_policy1',
|
|
||||||
'shared': False,
|
|
||||||
'firewall_rules': [_uuid(), _uuid()],
|
|
||||||
'audited': False,
|
|
||||||
'tenant_id': _uuid()}}
|
|
||||||
return_value = copy.copy(data['firewall_policy'])
|
|
||||||
return_value.update({'id': policy_id})
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.create_firewall_policy.return_value = return_value
|
|
||||||
res = self.api.post(_get_path('fw/firewall_policies',
|
|
||||||
fmt=self.fmt),
|
|
||||||
self.serialize(data),
|
|
||||||
content_type='application/%s' % self.fmt)
|
|
||||||
instance.create_firewall_policy.assert_called_with(
|
|
||||||
mock.ANY,
|
|
||||||
firewall_policy=data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_policy', res)
|
|
||||||
self.assertEqual(res['firewall_policy'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_policy_list(self):
|
|
||||||
policy_id = _uuid()
|
|
||||||
return_value = [{'tenant_id': _uuid(),
|
|
||||||
'id': policy_id}]
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewall_policies.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewall_policies',
|
|
||||||
fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewall_policies.assert_called_with(mock.ANY,
|
|
||||||
fields=mock.ANY,
|
|
||||||
filters=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
|
|
||||||
def test_firewall_policy_get(self):
|
|
||||||
policy_id = _uuid()
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': policy_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.get_firewall_policy.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.get(_get_path('fw/firewall_policies',
|
|
||||||
id=policy_id, fmt=self.fmt))
|
|
||||||
|
|
||||||
instance.get_firewall_policy.assert_called_with(mock.ANY,
|
|
||||||
policy_id,
|
|
||||||
fields=mock.ANY)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_policy', res)
|
|
||||||
self.assertEqual(res['firewall_policy'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_policy_update(self):
|
|
||||||
policy_id = _uuid()
|
|
||||||
update_data = {'firewall_policy': {'audited': True}}
|
|
||||||
return_value = {'tenant_id': _uuid(),
|
|
||||||
'id': policy_id}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.update_firewall_policy.return_value = return_value
|
|
||||||
|
|
||||||
res = self.api.put(_get_path('fw/firewall_policies',
|
|
||||||
id=policy_id,
|
|
||||||
fmt=self.fmt),
|
|
||||||
self.serialize(update_data))
|
|
||||||
|
|
||||||
instance.update_firewall_policy.assert_called_with(
|
|
||||||
mock.ANY,
|
|
||||||
policy_id,
|
|
||||||
firewall_policy=update_data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertIn('firewall_policy', res)
|
|
||||||
self.assertEqual(res['firewall_policy'], return_value)
|
|
||||||
|
|
||||||
def test_firewall_policy_update_malformed_rules(self):
|
|
||||||
# emulating client request when no rule uuids are provided for
|
|
||||||
# --firewall_rules parameter
|
|
||||||
update_data = {'firewall_policy': {'firewall_rules': True}}
|
|
||||||
# have to check for generic AppError
|
|
||||||
self.assertRaises(
|
|
||||||
webtest.AppError,
|
|
||||||
self.api.put,
|
|
||||||
_get_path('fw/firewall_policies', id=_uuid(), fmt=self.fmt),
|
|
||||||
self.serialize(update_data))
|
|
||||||
|
|
||||||
def test_firewall_policy_delete(self):
|
|
||||||
self._test_entity_delete('firewall_policy')
|
|
||||||
|
|
||||||
def test_firewall_policy_insert_rule(self):
|
|
||||||
firewall_policy_id = _uuid()
|
|
||||||
firewall_rule_id = _uuid()
|
|
||||||
ref_firewall_rule_id = _uuid()
|
|
||||||
|
|
||||||
insert_data = {'firewall_rule_id': firewall_rule_id,
|
|
||||||
'insert_before': ref_firewall_rule_id,
|
|
||||||
'insert_after': None}
|
|
||||||
return_value = {'firewall_policy':
|
|
||||||
{'tenant_id': _uuid(),
|
|
||||||
'id': firewall_policy_id,
|
|
||||||
'firewall_rules': [ref_firewall_rule_id,
|
|
||||||
firewall_rule_id]}}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.insert_rule.return_value = return_value
|
|
||||||
|
|
||||||
path = _get_path('fw/firewall_policies', id=firewall_policy_id,
|
|
||||||
action="insert_rule",
|
|
||||||
fmt=self.fmt)
|
|
||||||
res = self.api.put(path, self.serialize(insert_data))
|
|
||||||
instance.insert_rule.assert_called_with(mock.ANY, firewall_policy_id,
|
|
||||||
insert_data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertEqual(res, return_value)
|
|
||||||
|
|
||||||
def test_firewall_policy_remove_rule(self):
|
|
||||||
firewall_policy_id = _uuid()
|
|
||||||
firewall_rule_id = _uuid()
|
|
||||||
|
|
||||||
remove_data = {'firewall_rule_id': firewall_rule_id}
|
|
||||||
return_value = {'firewall_policy':
|
|
||||||
{'tenant_id': _uuid(),
|
|
||||||
'id': firewall_policy_id,
|
|
||||||
'firewall_rules': []}}
|
|
||||||
|
|
||||||
instance = self.plugin.return_value
|
|
||||||
instance.remove_rule.return_value = return_value
|
|
||||||
|
|
||||||
path = _get_path('fw/firewall_policies', id=firewall_policy_id,
|
|
||||||
action="remove_rule",
|
|
||||||
fmt=self.fmt)
|
|
||||||
res = self.api.put(path, self.serialize(remove_data))
|
|
||||||
instance.remove_rule.assert_called_with(mock.ANY, firewall_policy_id,
|
|
||||||
remove_data)
|
|
||||||
self.assertEqual(res.status_int, exc.HTTPOk.code)
|
|
||||||
res = self.deserialize(res)
|
|
||||||
self.assertEqual(res, return_value)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFirewallAttributeValidators(base.BaseTestCase):
|
|
||||||
|
|
||||||
def test_validate_port_range(self):
|
|
||||||
msg = firewall._validate_port_range(None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('10')
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range(10)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range(-1)
|
|
||||||
self.assertEqual(msg, "Invalid port '-1'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('66000')
|
|
||||||
self.assertEqual(msg, "Invalid port '66000'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('10:20')
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('1:65535')
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('0:65535')
|
|
||||||
self.assertEqual(msg, "Invalid port '0'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('1:65536')
|
|
||||||
self.assertEqual(msg, "Invalid port '65536'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('abc:efg')
|
|
||||||
self.assertEqual(msg, "Port 'abc' is not a valid number")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('1:efg')
|
|
||||||
self.assertEqual(msg, "Port 'efg' is not a valid number")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('-1:10')
|
|
||||||
self.assertEqual(msg, "Invalid port '-1'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('66000:10')
|
|
||||||
self.assertEqual(msg, "Invalid port '66000'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('10:66000')
|
|
||||||
self.assertEqual(msg, "Invalid port '66000'")
|
|
||||||
|
|
||||||
msg = firewall._validate_port_range('1:-10')
|
|
||||||
self.assertEqual(msg, "Invalid port '-10'")
|
|
||||||
|
|
||||||
def test_validate_ip_or_subnet_or_none(self):
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none('1.1.1.1')
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none('1.1.1.0/24')
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
ip_addr = '1111.1.1.1'
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(ip_addr)
|
|
||||||
self.assertEqual(msg, ("'%s' is not a valid IP address and "
|
|
||||||
"'%s' is not a valid IP subnet") % (ip_addr,
|
|
||||||
ip_addr))
|
|
||||||
|
|
||||||
ip_addr = '1.1.1.1 has whitespace'
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(ip_addr)
|
|
||||||
self.assertEqual(msg, ("'%s' is not a valid IP address and "
|
|
||||||
"'%s' is not a valid IP subnet") % (ip_addr,
|
|
||||||
ip_addr))
|
|
||||||
|
|
||||||
ip_addr = '111.1.1.1\twhitespace'
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(ip_addr)
|
|
||||||
self.assertEqual(msg, ("'%s' is not a valid IP address and "
|
|
||||||
"'%s' is not a valid IP subnet") % (ip_addr,
|
|
||||||
ip_addr))
|
|
||||||
|
|
||||||
ip_addr = '111.1.1.1\nwhitespace'
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(ip_addr)
|
|
||||||
self.assertEqual(msg, ("'%s' is not a valid IP address and "
|
|
||||||
"'%s' is not a valid IP subnet") % (ip_addr,
|
|
||||||
ip_addr))
|
|
||||||
|
|
||||||
# Valid - IPv4
|
|
||||||
cidr = "10.0.2.0/24"
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
# Valid - IPv6 without final octets
|
|
||||||
cidr = "fe80::/24"
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
# Valid - IPv6 with final octets
|
|
||||||
cidr = "fe80::0/24"
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
cidr = "fe80::"
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
# Invalid - IPv6 with final octets, missing mask
|
|
||||||
cidr = "fe80::0"
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertIsNone(msg)
|
|
||||||
|
|
||||||
# Invalid - Address format error
|
|
||||||
cidr = 'invalid'
|
|
||||||
msg = firewall._validate_ip_or_subnet_or_none(cidr, None)
|
|
||||||
self.assertEqual(msg, ("'%s' is not a valid IP address and "
|
|
||||||
"'%s' is not a valid IP subnet") % (cidr,
|
|
||||||
cidr))
|
|
Loading…
Reference in New Issue
Block a user