From b950be43911046eb5d05a5e87224013cb39cc116 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Sun, 1 Mar 2015 12:25:29 -0800 Subject: [PATCH] Big Switch Networks code split This commit removes all of the Big Switch Networks code from the tree and replaces it with the import statements required to pull in the entry points from the new 'bsnstacklib' module. All patches for BSN code should now be submitted to the stackforge/networking-bigswitch project. Partial-implements: blueprint core-vendor-decomposition Change-Id: I4d83c9fd6a2953c329c247c78425f8e3280e8a42 --- neutron/plugins/bigswitch/README | 14 - .../bigswitch/agent/restproxy_agent.py | 145 +-- neutron/plugins/bigswitch/config.py | 116 --- .../plugins/bigswitch/db/consistency_db.py | 195 ---- .../plugins/bigswitch/db/porttracker_db.py | 52 -- .../plugins/bigswitch/extensions/__init__.py | 0 .../bigswitch/extensions/routerrule.py | 140 --- neutron/plugins/bigswitch/l3_router_plugin.py | 282 +----- neutron/plugins/bigswitch/plugin.py | 877 +----------------- neutron/plugins/bigswitch/requirements.txt | 1 + neutron/plugins/bigswitch/routerrule_db.py | 108 --- neutron/plugins/bigswitch/servermanager.py | 657 ------------- neutron/plugins/bigswitch/tests/__init__.py | 0 .../plugins/bigswitch/tests/test_server.py | 185 ---- neutron/plugins/bigswitch/vcsversion.py | 22 - neutron/plugins/bigswitch/version.py | 48 - .../ml2/drivers/mech_bigswitch/driver.py | 193 +--- .../drivers/mech_bigswitch/requirements.txt | 1 + neutron/tests/unit/bigswitch/__init__.py | 0 .../unit/bigswitch/etc/restproxy.ini.test | 46 - .../unit/bigswitch/etc/ssl/ca_certs/README | 2 - .../unit/bigswitch/etc/ssl/combined/README | 2 - .../unit/bigswitch/etc/ssl/host_certs/README | 2 - neutron/tests/unit/bigswitch/fake_server.py | 181 ---- .../unit/bigswitch/test_agent_scheduler.py | 33 - neutron/tests/unit/bigswitch/test_base.py | 84 -- .../tests/unit/bigswitch/test_capabilities.py | 81 -- .../unit/bigswitch/test_restproxy_agent.py | 185 ---- .../unit/bigswitch/test_restproxy_plugin.py | 328 ------- .../tests/unit/bigswitch/test_router_db.py | 574 ------------ .../unit/bigswitch/test_security_groups.py | 48 - .../unit/bigswitch/test_servermanager.py | 668 ------------- neutron/tests/unit/bigswitch/test_ssl.py | 250 ----- .../unit/ml2/drivers/test_bigswitch_mech.py | 212 ----- 34 files changed, 11 insertions(+), 5721 deletions(-) delete mode 100644 neutron/plugins/bigswitch/README delete mode 100644 neutron/plugins/bigswitch/config.py delete mode 100644 neutron/plugins/bigswitch/db/porttracker_db.py delete mode 100644 neutron/plugins/bigswitch/extensions/__init__.py delete mode 100644 neutron/plugins/bigswitch/extensions/routerrule.py create mode 100644 neutron/plugins/bigswitch/requirements.txt delete mode 100644 neutron/plugins/bigswitch/servermanager.py delete mode 100644 neutron/plugins/bigswitch/tests/__init__.py delete mode 100755 neutron/plugins/bigswitch/tests/test_server.py delete mode 100644 neutron/plugins/bigswitch/vcsversion.py delete mode 100755 neutron/plugins/bigswitch/version.py create mode 100644 neutron/plugins/ml2/drivers/mech_bigswitch/requirements.txt delete mode 100644 neutron/tests/unit/bigswitch/__init__.py delete mode 100644 neutron/tests/unit/bigswitch/etc/restproxy.ini.test delete mode 100644 neutron/tests/unit/bigswitch/etc/ssl/ca_certs/README delete mode 100644 neutron/tests/unit/bigswitch/etc/ssl/combined/README delete mode 100644 neutron/tests/unit/bigswitch/etc/ssl/host_certs/README delete mode 100644 neutron/tests/unit/bigswitch/fake_server.py delete mode 100644 neutron/tests/unit/bigswitch/test_agent_scheduler.py delete mode 100644 neutron/tests/unit/bigswitch/test_base.py delete mode 100644 neutron/tests/unit/bigswitch/test_capabilities.py delete mode 100644 neutron/tests/unit/bigswitch/test_restproxy_agent.py delete mode 100644 neutron/tests/unit/bigswitch/test_restproxy_plugin.py delete mode 100644 neutron/tests/unit/bigswitch/test_router_db.py delete mode 100644 neutron/tests/unit/bigswitch/test_security_groups.py delete mode 100644 neutron/tests/unit/bigswitch/test_servermanager.py delete mode 100644 neutron/tests/unit/bigswitch/test_ssl.py delete mode 100644 neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py diff --git a/neutron/plugins/bigswitch/README b/neutron/plugins/bigswitch/README deleted file mode 100644 index 43f157d1239..00000000000 --- a/neutron/plugins/bigswitch/README +++ /dev/null @@ -1,14 +0,0 @@ -# Neuron REST Proxy Plug-in for Big Switch and FloodLight Controllers - -This module provides a generic neutron plugin 'NeutronRestProxy' that -translates neutron function calls to authenticated REST requests (JSON supported) -to a set of redundant external network controllers. - -It also keeps a local persistent store of neutron state that has been -setup using that API. - -Currently the FloodLight Openflow Controller or the Big Switch Networks Controller -can be configured as external network controllers for this plugin. - -For more details on this plugin, please refer to the following link: -http://www.openflowhub.org/display/floodlightcontroller/Neutron+REST+Proxy+Plugin diff --git a/neutron/plugins/bigswitch/agent/restproxy_agent.py b/neutron/plugins/bigswitch/agent/restproxy_agent.py index 54f5d34bf4f..c11f6bea74a 100644 --- a/neutron/plugins/bigswitch/agent/restproxy_agent.py +++ b/neutron/plugins/bigswitch/agent/restproxy_agent.py @@ -15,152 +15,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -import sys -import time - import eventlet eventlet.monkey_patch() -from oslo_config import cfg -import oslo_messaging -from oslo_utils import excutils +from bsnstacklib.plugins.bigswitch.agent import restproxy_agent -from neutron.agent.linux import ovs_lib -from neutron.agent.linux import utils -from neutron.agent import rpc as agent_rpc -from neutron.agent import securitygroups_rpc as sg_rpc -from neutron.common import config -from neutron.common import topics -from neutron import context as q_context -from neutron.extensions import securitygroup as ext_sg -from neutron.i18n import _LE -from neutron.openstack.common import log -from neutron.plugins.bigswitch import config as pl_config - -LOG = log.getLogger(__name__) - - -class IVSBridge(ovs_lib.OVSBridge): - ''' - This class does not provide parity with OVS using IVS. - It's only the bare minimum necessary to use IVS with this agent. - ''' - def run_vsctl(self, args, check_error=False): - full_args = ["ivs-ctl"] + args - try: - return utils.execute(full_args, run_as_root=True) - except Exception as e: - with excutils.save_and_reraise_exception() as ctxt: - LOG.error(_LE("Unable to execute %(cmd)s. " - "Exception: %(exception)s"), - {'cmd': full_args, 'exception': e}) - if not check_error: - ctxt.reraise = False - - def get_vif_port_set(self): - port_names = self.get_port_name_list() - edge_ports = set(port_names) - return edge_ports - - def get_vif_port_by_id(self, port_id): - # IVS in nova uses hybrid method with last 14 chars of UUID - name = 'qvo%s' % port_id[:14] - if name in self.get_vif_port_set(): - return name - return False - - -class RestProxyAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): - - target = oslo_messaging.Target(version='1.1') - - def __init__(self, integ_br, polling_interval, vs='ovs'): - super(RestProxyAgent, self).__init__() - self.polling_interval = polling_interval - self._setup_rpc() - self.sg_agent = sg_rpc.SecurityGroupAgentRpc(self.context, - self.sg_plugin_rpc) - if vs == 'ivs': - self.int_br = IVSBridge(integ_br) - else: - self.int_br = ovs_lib.OVSBridge(integ_br) - - def _setup_rpc(self): - self.topic = topics.AGENT - self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) - self.sg_plugin_rpc = sg_rpc.SecurityGroupServerRpcApi(topics.PLUGIN) - self.context = q_context.get_admin_context_without_session() - self.endpoints = [self] - consumers = [[topics.PORT, topics.UPDATE], - [topics.SECURITY_GROUP, topics.UPDATE]] - self.connection = agent_rpc.create_consumers(self.endpoints, - self.topic, - consumers) - - def port_update(self, context, **kwargs): - LOG.debug("Port update received") - port = kwargs.get('port') - vif_port = self.int_br.get_vif_port_by_id(port['id']) - if not vif_port: - LOG.debug("Port %s is not present on this host.", port['id']) - return - - LOG.debug("Port %s found. Refreshing firewall.", port['id']) - if ext_sg.SECURITYGROUPS in port: - self.sg_agent.refresh_firewall() - - def _update_ports(self, registered_ports): - ports = self.int_br.get_vif_port_set() - if ports == registered_ports: - return - added = ports - registered_ports - removed = registered_ports - ports - return {'current': ports, - 'added': added, - 'removed': removed} - - def _process_devices_filter(self, port_info): - if 'added' in port_info: - self.sg_agent.prepare_devices_filter(port_info['added']) - if 'removed' in port_info: - self.sg_agent.remove_devices_filter(port_info['removed']) - - def daemon_loop(self): - ports = set() - - while True: - start = time.time() - try: - port_info = self._update_ports(ports) - if port_info: - LOG.debug("Agent loop has new device") - self._process_devices_filter(port_info) - ports = port_info['current'] - except Exception: - LOG.exception(_LE("Error in agent event loop")) - - elapsed = max(time.time() - start, 0) - if (elapsed < self.polling_interval): - time.sleep(self.polling_interval - elapsed) - else: - LOG.debug("Loop iteration exceeded interval " - "(%(polling_interval)s vs. %(elapsed)s)!", - {'polling_interval': self.polling_interval, - 'elapsed': elapsed}) - - -def main(): - config.init(sys.argv[1:]) - config.setup_logging() - pl_config.register_config() - - integ_br = cfg.CONF.RESTPROXYAGENT.integration_bridge - polling_interval = cfg.CONF.RESTPROXYAGENT.polling_interval - bsnagent = RestProxyAgent(integ_br, polling_interval, - cfg.CONF.RESTPROXYAGENT.virtual_switch_type) - bsnagent.daemon_loop() - sys.exit(0) if __name__ == "__main__": - main() + restproxy_agent.main() diff --git a/neutron/plugins/bigswitch/config.py b/neutron/plugins/bigswitch/config.py deleted file mode 100644 index 1c74fd871c3..00000000000 --- a/neutron/plugins/bigswitch/config.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2014 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. - -""" -This module manages configuration options -""" - -from oslo_config import cfg - -from neutron.common import utils -from neutron.extensions import portbindings - -restproxy_opts = [ - cfg.ListOpt('servers', default=['localhost:8800'], - help=_("A comma separated list of Big Switch or Floodlight " - "servers and port numbers. The plugin proxies the " - "requests to the Big Switch/Floodlight server, " - "which performs the networking configuration. Only one" - "server is needed per deployment, but you may wish to" - "deploy multiple servers to support failover.")), - cfg.StrOpt('server_auth', secret=True, - help=_("The username and password for authenticating against " - " the Big Switch or Floodlight controller.")), - cfg.BoolOpt('server_ssl', default=True, - help=_("If True, Use SSL when connecting to the Big Switch or " - "Floodlight controller.")), - cfg.BoolOpt('ssl_sticky', default=True, - help=_("Trust and store the first certificate received for " - "each controller address and use it to validate future " - "connections to that address.")), - cfg.BoolOpt('no_ssl_validation', default=False, - help=_("Disables SSL certificate validation for controllers")), - cfg.BoolOpt('cache_connections', default=True, - help=_("Re-use HTTP/HTTPS connections to the controller.")), - cfg.StrOpt('ssl_cert_directory', - default='/etc/neutron/plugins/bigswitch/ssl', - help=_("Directory containing ca_certs and host_certs " - "certificate directories.")), - cfg.BoolOpt('sync_data', default=False, - help=_("Sync data on connect")), - cfg.BoolOpt('auto_sync_on_failure', default=True, - help=_("If neutron fails to create a resource because " - "the backend controller doesn't know of a dependency, " - "the plugin automatically triggers a full data " - "synchronization to the controller.")), - cfg.IntOpt('consistency_interval', default=60, - help=_("Time between verifications that the backend controller " - "database is consistent with Neutron. (0 to disable)")), - cfg.IntOpt('server_timeout', default=10, - help=_("Maximum number of seconds to wait for proxy request " - "to connect and complete.")), - cfg.IntOpt('thread_pool_size', default=4, - help=_("Maximum number of threads to spawn to handle large " - "volumes of port creations.")), - cfg.StrOpt('neutron_id', default='neutron-' + utils.get_hostname(), - deprecated_name='quantum_id', - help=_("User defined identifier for this Neutron deployment")), - cfg.BoolOpt('add_meta_server_route', default=True, - help=_("Flag to decide if a route to the metadata server " - "should be injected into the VM")), -] -router_opts = [ - cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'], - help=_("The default router rules installed in new tenant " - "routers. Repeat the config option for each rule. " - "Format is :::" - " Use an * to specify default for all tenants.")), - cfg.IntOpt('max_router_rules', default=200, - help=_("Maximum number of router rules")), -] -nova_opts = [ - cfg.StrOpt('vif_type', default='ovs', - help=_("Virtual interface type to configure on " - "Nova compute nodes")), -] - -# Each VIF Type can have a list of nova host IDs that are fixed to that type -for i in portbindings.VIF_TYPES: - opt = cfg.ListOpt('node_override_vif_' + i, default=[], - help=_("Nova compute nodes to manually set VIF " - "type to %s") % i) - nova_opts.append(opt) - -# Add the vif types for reference later -nova_opts.append(cfg.ListOpt('vif_types', - default=portbindings.VIF_TYPES, - help=_('List of allowed vif_type values.'))) - -agent_opts = [ - cfg.StrOpt('integration_bridge', default='br-int', - help=_('Name of integration bridge on compute ' - 'nodes used for security group insertion.')), - cfg.IntOpt('polling_interval', default=5, - help=_('Seconds between agent checks for port changes')), - cfg.StrOpt('virtual_switch_type', default='ovs', - help=_('Virtual switch type.')) -] - - -def register_config(): - cfg.CONF.register_opts(restproxy_opts, "RESTPROXY") - cfg.CONF.register_opts(router_opts, "ROUTER") - cfg.CONF.register_opts(nova_opts, "NOVA") - cfg.CONF.register_opts(agent_opts, "RESTPROXYAGENT") diff --git a/neutron/plugins/bigswitch/db/consistency_db.py b/neutron/plugins/bigswitch/db/consistency_db.py index 55212a8c632..0067e423b2d 100644 --- a/neutron/plugins/bigswitch/db/consistency_db.py +++ b/neutron/plugins/bigswitch/db/consistency_db.py @@ -12,42 +12,9 @@ # 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 random -import re -import string -import time - -from oslo_config import cfg -from oslo_db import exception as db_exc -from oslo_db.sqlalchemy import session import sqlalchemy as sa from neutron.db import model_base -from neutron.i18n import _LI, _LW -from neutron.openstack.common import log as logging - -LOG = logging.getLogger(__name__) -# Maximum time in seconds to wait for a single record lock to be released -# NOTE: The total time waiting may exceed this if there are multiple servers -# waiting for the same lock -MAX_LOCK_WAIT_TIME = 15 - - -def setup_db(): - '''Helper to register models for unit tests''' - if HashHandler._FACADE is None: - HashHandler._FACADE = session.EngineFacade.from_config( - cfg.CONF, sqlite_fk=True) - ConsistencyHash.metadata.create_all( - HashHandler._FACADE.get_engine()) - - -def clear_db(): - '''Helper to unregister models and clear engine in unit tests''' - if not HashHandler._FACADE: - return - ConsistencyHash.metadata.drop_all(HashHandler._FACADE.get_engine()) - HashHandler._FACADE = None class ConsistencyHash(model_base.BASEV2): @@ -61,165 +28,3 @@ class ConsistencyHash(model_base.BASEV2): hash_id = sa.Column(sa.String(255), primary_key=True) hash = sa.Column(sa.String(255), nullable=False) - - -class HashHandler(object): - ''' - A wrapper object to keep track of the session between the read - and the update operations. - - This class needs an SQL engine completely independent of the main - neutron connection so rollbacks from consistency hash operations don't - affect the parent sessions. - ''' - _FACADE = None - - def __init__(self, hash_id='1'): - if HashHandler._FACADE is None: - HashHandler._FACADE = session.EngineFacade.from_config( - cfg.CONF, sqlite_fk=True) - self.hash_id = hash_id - self.session = HashHandler._FACADE.get_session(autocommit=True, - expire_on_commit=False) - self.random_lock_id = ''.join(random.choice(string.ascii_uppercase - + string.digits) - for _ in range(10)) - self.lock_marker = 'LOCKED_BY[%s]' % self.random_lock_id - - def _get_current_record(self): - with self.session.begin(subtransactions=True): - res = (self.session.query(ConsistencyHash). - filter_by(hash_id=self.hash_id).first()) - if res: - self.session.refresh(res) # make sure latest is loaded from db - return res - - def _insert_empty_hash_with_lock(self): - # try to insert a new hash, return False on conflict - try: - with self.session.begin(subtransactions=True): - res = ConsistencyHash(hash_id=self.hash_id, - hash=self.lock_marker) - self.session.add(res) - return True - except db_exc.DBDuplicateEntry: - # another server created a new record at the same time - return False - - def _optimistic_update_hash_record(self, old_record, new_hash): - # Optimistic update strategy. Returns True if successful, else False. - query = sa.update(ConsistencyHash.__table__).values(hash=new_hash) - query = query.where(ConsistencyHash.hash_id == old_record.hash_id) - query = query.where(ConsistencyHash.hash == old_record.hash) - with self._FACADE.get_engine().begin() as conn: - result = conn.execute(query) - # We need to check update row count in case another server is - # doing this at the same time. Only one will succeed, the other will - # not update any rows. - return result.rowcount != 0 - - def _get_lock_owner(self, record): - matches = re.findall(r"^LOCKED_BY\[(\w+)\]", record) - if not matches: - return None - return matches[0] - - def read_for_update(self): - # An optimistic locking strategy with a timeout to avoid using a - # consistency hash while another server is using it. This will - # not return until a lock is acquired either normally or by stealing - # it after an individual ID holds it for greater than - # MAX_LOCK_WAIT_TIME. - lock_wait_start = None - last_lock_owner = None - while True: - res = self._get_current_record() - if not res: - # no current entry. try to insert to grab lock - if not self._insert_empty_hash_with_lock(): - # A failed insert after missing current record means - # a concurrent insert occurred. Start process over to - # find the new record. - LOG.debug("Concurrent record inserted. Retrying.") - time.sleep(0.25) - continue - # The empty hash was successfully inserted with our lock - return '' - - current_lock_owner = self._get_lock_owner(res.hash) - if not current_lock_owner: - # no current lock. attempt to lock - new = self.lock_marker + res.hash - if not self._optimistic_update_hash_record(res, new): - # someone else beat us to it. restart process to wait - # for new lock ID to be removed - LOG.debug( - "Failed to acquire lock. Restarting lock wait. " - "Previous hash: %(prev)s. Attempted update: %(new)s", - {'prev': res.hash, 'new': new}) - time.sleep(0.25) - continue - # successfully got the lock - return res.hash - - LOG.debug("This request's lock ID is %(this)s. " - "DB lock held by %(that)s", - {'this': self.random_lock_id, - 'that': current_lock_owner}) - - if current_lock_owner == self.random_lock_id: - # no change needed, we already have the table lock due to - # previous read_for_update call. - # return hash with lock tag stripped off for use in a header - return res.hash.replace(self.lock_marker, '') - - if current_lock_owner != last_lock_owner: - # The owner changed since the last iteration, but it - # wasn't to us. Reset the counter. Log if not - # first iteration. - if lock_wait_start: - LOG.debug("Lock owner changed from %(old)s to %(new)s " - "while waiting to acquire it.", - {'old': last_lock_owner, - 'new': current_lock_owner}) - lock_wait_start = time.time() - last_lock_owner = current_lock_owner - if time.time() - lock_wait_start > MAX_LOCK_WAIT_TIME: - # the lock has been held too long, steal it - LOG.warning(_LW("Gave up waiting for consistency DB " - "lock, trying to take it. " - "Current hash is: %s"), res.hash) - new_db_value = res.hash.replace(current_lock_owner, - self.random_lock_id) - if self._optimistic_update_hash_record(res, new_db_value): - return res.hash.replace(new_db_value, '') - LOG.info(_LI("Failed to take lock. Another process updated " - "the DB first.")) - - def clear_lock(self): - LOG.debug("Clearing hash record lock of id %s", self.random_lock_id) - with self.session.begin(subtransactions=True): - res = (self.session.query(ConsistencyHash). - filter_by(hash_id=self.hash_id).first()) - if not res: - LOG.warning(_LW("Hash record already gone, no lock to clear.")) - return - if not res.hash.startswith(self.lock_marker): - # if these are frequent the server is too slow - LOG.warning(_LW("Another server already removed the lock. %s"), - res.hash) - return - res.hash = res.hash.replace(self.lock_marker, '') - - def put_hash(self, hash): - hash = hash or '' - with self.session.begin(subtransactions=True): - res = (self.session.query(ConsistencyHash). - filter_by(hash_id=self.hash_id).first()) - if res: - res.hash = hash - else: - conhash = ConsistencyHash(hash_id=self.hash_id, hash=hash) - self.session.merge(conhash) - LOG.debug("Consistency hash for group %(hash_id)s updated " - "to %(hash)s", {'hash_id': self.hash_id, 'hash': hash}) diff --git a/neutron/plugins/bigswitch/db/porttracker_db.py b/neutron/plugins/bigswitch/db/porttracker_db.py deleted file mode 100644 index e999dd55f6d..00000000000 --- a/neutron/plugins/bigswitch/db/porttracker_db.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2013, Big Switch Networks -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from neutron.api.v2 import attributes -from neutron.i18n import _LW -from neutron.openstack.common import log as logging - -LOG = logging.getLogger(__name__) - - -def get_port_hostid(context, port_id): - # REVISIT(kevinbenton): this is a workaround to avoid portbindings_db - # relational table generation until one of the functions is called. - from neutron.db import portbindings_db - with context.session.begin(subtransactions=True): - query = context.session.query(portbindings_db.PortBindingPort) - res = query.filter_by(port_id=port_id).first() - if not res: - return False - return res.host - - -def put_port_hostid(context, port_id, host): - # REVISIT(kevinbenton): this is a workaround to avoid portbindings_db - # relational table generation until one of the functions is called. - from neutron.db import portbindings_db - if not attributes.is_attr_set(host): - LOG.warning(_LW("No host_id in port request to track port location.")) - return - if port_id == '': - LOG.warning(_LW("Received an empty port ID for host_id '%s'"), host) - return - if host == '': - LOG.debug("Received an empty host_id for port '%s'", port_id) - return - LOG.debug("Logging port %(port)s on host_id %(host)s", - {'port': port_id, 'host': host}) - with context.session.begin(subtransactions=True): - location = portbindings_db.PortBindingPort(port_id=port_id, host=host) - context.session.merge(location) diff --git a/neutron/plugins/bigswitch/extensions/__init__.py b/neutron/plugins/bigswitch/extensions/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/neutron/plugins/bigswitch/extensions/routerrule.py b/neutron/plugins/bigswitch/extensions/routerrule.py deleted file mode 100644 index 61e87d639f1..00000000000 --- a/neutron/plugins/bigswitch/extensions/routerrule.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2013 Big Switch Networks, Inc. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from neutron.api.v2 import attributes as attr -from neutron.common import exceptions as nexception -from neutron.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - - -# Router Rules Exceptions -class InvalidRouterRules(nexception.InvalidInput): - message = _("Invalid format for router rules: %(rule)s, %(reason)s") - - -class RulesExhausted(nexception.BadRequest): - message = _("Unable to complete rules update for %(router_id)s. " - "The number of rules exceeds the maximum %(quota)s.") - - -def convert_to_valid_router_rules(data): - """ - Validates and converts router rules to the appropriate data structure - Example argument = [{'source': 'any', 'destination': 'any', - 'action':'deny'}, - {'source': '1.1.1.1/32', 'destination': 'external', - 'action':'permit', - 'nexthops': ['1.1.1.254', '1.1.1.253']} - ] - """ - V4ANY = '0.0.0.0/0' - CIDRALL = ['any', 'external'] - if not isinstance(data, list): - emsg = _("Invalid data format for router rule: '%s'") % data - LOG.debug(emsg) - raise nexception.InvalidInput(error_message=emsg) - _validate_uniquerules(data) - rules = [] - expected_keys = ['source', 'destination', 'action'] - for rule in data: - rule['nexthops'] = rule.get('nexthops', []) - if not isinstance(rule['nexthops'], list): - rule['nexthops'] = rule['nexthops'].split('+') - - src = V4ANY if rule['source'] in CIDRALL else rule['source'] - dst = V4ANY if rule['destination'] in CIDRALL else rule['destination'] - - errors = [attr._verify_dict_keys(expected_keys, rule, False), - attr._validate_subnet(dst), - attr._validate_subnet(src), - _validate_nexthops(rule['nexthops']), - _validate_action(rule['action'])] - errors = [m for m in errors if m] - if errors: - LOG.debug(errors) - raise nexception.InvalidInput(error_message=errors) - rules.append(rule) - return rules - - -def _validate_nexthops(nexthops): - seen = [] - for ip in nexthops: - msg = attr._validate_ip_address(ip) - if ip in seen: - msg = _("Duplicate nexthop in rule '%s'") % ip - seen.append(ip) - if msg: - return msg - - -def _validate_action(action): - if action not in ['permit', 'deny']: - return _("Action must be either permit or deny." - " '%s' was provided") % action - - -def _validate_uniquerules(rules): - pairs = [] - for r in rules: - if 'source' not in r or 'destination' not in r: - continue - pairs.append((r['source'], r['destination'])) - - if len(set(pairs)) != len(pairs): - error = _("Duplicate router rules (src,dst) found '%s'") % pairs - LOG.debug(error) - raise nexception.InvalidInput(error_message=error) - - -class Routerrule(object): - - @classmethod - def get_name(cls): - return "Neutron Router Rule" - - @classmethod - def get_alias(cls): - return "router_rules" - - @classmethod - def get_description(cls): - return "Router rule configuration for L3 router" - - @classmethod - def get_namespace(cls): - return "http://docs.openstack.org/ext/neutron/routerrules/api/v1.0" - - @classmethod - def get_updated(cls): - return "2013-05-23T10:00:00-00:00" - - def get_extended_resources(self, version): - if version == "2.0": - return EXTENDED_ATTRIBUTES_2_0 - else: - return {} - -# Attribute Map -EXTENDED_ATTRIBUTES_2_0 = { - 'routers': { - 'router_rules': {'allow_post': False, 'allow_put': True, - 'convert_to': convert_to_valid_router_rules, - 'is_visible': True, - 'default': attr.ATTR_NOT_SPECIFIED}, - } -} diff --git a/neutron/plugins/bigswitch/l3_router_plugin.py b/neutron/plugins/bigswitch/l3_router_plugin.py index 245fc3e9ac3..2fb1cedd511 100644 --- a/neutron/plugins/bigswitch/l3_router_plugin.py +++ b/neutron/plugins/bigswitch/l3_router_plugin.py @@ -22,285 +22,7 @@ It is intended to be used in conjunction with the Big Switch ML2 driver or the Big Switch core plugin. """ -from oslo_config import cfg -from oslo_utils import excutils - -from neutron.api import extensions as neutron_extensions -from neutron.common import exceptions -from neutron.common import log -from neutron.db import l3_db -from neutron.extensions import l3 -from neutron.i18n import _LE -from neutron.openstack.common import log as logging -from neutron.plugins.bigswitch import extensions -from neutron.plugins.bigswitch import plugin as cplugin -from neutron.plugins.bigswitch import routerrule_db -from neutron.plugins.bigswitch import servermanager -from neutron.plugins.common import constants - -# number of fields in a router rule string -ROUTER_RULE_COMPONENT_COUNT = 5 -LOG = logging.getLogger(__name__) -put_context_in_serverpool = cplugin.put_context_in_serverpool +from bsnstacklib.plugins.bigswitch import l3_router_plugin -class L3RestProxy(cplugin.NeutronRestProxyV2Base, - routerrule_db.RouterRule_db_mixin): - - supported_extension_aliases = ["router", "router_rules"] - - @staticmethod - def get_plugin_type(): - return constants.L3_ROUTER_NAT - - @staticmethod - def get_plugin_description(): - return _("L3 Router Service Plugin for Big Switch fabric") - - def __init__(self): - # Include the Big Switch Extensions path in the api_extensions - neutron_extensions.append_api_extensions_path(extensions.__path__) - super(L3RestProxy, self).__init__() - self.servers = servermanager.ServerPool.get_instance() - - @put_context_in_serverpool - @log.log - def create_router(self, context, router): - self._warn_on_state_status(router['router']) - - tenant_id = self._get_tenant_id_for_create(context, router["router"]) - - # set default router rules - rules = self._get_tenant_default_router_rules(tenant_id) - router['router']['router_rules'] = rules - - with context.session.begin(subtransactions=True): - # create router in DB - new_router = super(L3RestProxy, self).create_router(context, - router) - mapped_router = self._map_state_and_status(new_router) - self.servers.rest_create_router(tenant_id, mapped_router) - - # return created router - return new_router - - @put_context_in_serverpool - @log.log - def update_router(self, context, router_id, router): - self._warn_on_state_status(router['router']) - - orig_router = super(L3RestProxy, self).get_router(context, router_id) - tenant_id = orig_router["tenant_id"] - with context.session.begin(subtransactions=True): - new_router = super(L3RestProxy, - self).update_router(context, router_id, router) - router = self._map_state_and_status(new_router) - # look up the network on this side to save an expensive query on - # the backend controller. - if router and router.get('external_gateway_info'): - router['external_gateway_info']['network'] = self.get_network( - context.elevated(), - router['external_gateway_info']['network_id']) - # update router on network controller - self.servers.rest_update_router(tenant_id, router, router_id) - - # return updated router - return new_router - - @put_context_in_serverpool - @log.log - def delete_router(self, context, router_id): - with context.session.begin(subtransactions=True): - orig_router = self._get_router(context, router_id) - tenant_id = orig_router["tenant_id"] - - # Ensure that the router is not used - router_filter = {'router_id': [router_id]} - fips = self.get_floatingips_count(context.elevated(), - filters=router_filter) - if fips: - raise l3.RouterInUse(router_id=router_id) - - device_owner = l3_db.DEVICE_OWNER_ROUTER_INTF - device_filter = {'device_id': [router_id], - 'device_owner': [device_owner]} - ports = self.get_ports_count(context.elevated(), - filters=device_filter) - if ports: - raise l3.RouterInUse(router_id=router_id) - super(L3RestProxy, self).delete_router(context, router_id) - - # delete from network controller - self.servers.rest_delete_router(tenant_id, router_id) - - @put_context_in_serverpool - @log.log - def add_router_interface(self, context, router_id, interface_info): - # Validate args - router = self._get_router(context, router_id) - tenant_id = router['tenant_id'] - - with context.session.begin(subtransactions=True): - # create interface in DB - new_intf_info = super(L3RestProxy, - self).add_router_interface(context, - router_id, - interface_info) - port = self._get_port(context, new_intf_info['port_id']) - net_id = port['network_id'] - subnet_id = new_intf_info['subnet_id'] - # we will use the port's network id as interface's id - interface_id = net_id - intf_details = self._get_router_intf_details(context, - interface_id, - subnet_id) - - # create interface on the network controller - self.servers.rest_add_router_interface(tenant_id, router_id, - intf_details) - return new_intf_info - - @put_context_in_serverpool - @log.log - def remove_router_interface(self, context, router_id, interface_info): - # Validate args - router = self._get_router(context, router_id) - tenant_id = router['tenant_id'] - - # we will first get the interface identifier before deleting in the DB - if not interface_info: - msg = _("Either subnet_id or port_id must be specified") - raise exceptions.BadRequest(resource='router', msg=msg) - if 'port_id' in interface_info: - port = self._get_port(context, interface_info['port_id']) - interface_id = port['network_id'] - elif 'subnet_id' in interface_info: - subnet = self._get_subnet(context, interface_info['subnet_id']) - interface_id = subnet['network_id'] - else: - msg = _("Either subnet_id or port_id must be specified") - raise exceptions.BadRequest(resource='router', msg=msg) - - with context.session.begin(subtransactions=True): - # remove router in DB - del_ret = super(L3RestProxy, - self).remove_router_interface(context, - router_id, - interface_info) - - # create router on the network controller - self.servers.rest_remove_router_interface(tenant_id, router_id, - interface_id) - return del_ret - - @put_context_in_serverpool - @log.log - def create_floatingip(self, context, floatingip): - with context.session.begin(subtransactions=True): - # create floatingip in DB - new_fl_ip = super(L3RestProxy, - self).create_floatingip(context, floatingip) - - # create floatingip on the network controller - try: - if 'floatingip' in self.servers.get_capabilities(): - self.servers.rest_create_floatingip( - new_fl_ip['tenant_id'], new_fl_ip) - else: - self._send_floatingip_update(context) - except servermanager.RemoteRestError as e: - with excutils.save_and_reraise_exception(): - LOG.error( - _LE("NeutronRestProxyV2: Unable to create remote " - "floating IP: %s"), e) - # return created floating IP - return new_fl_ip - - @put_context_in_serverpool - @log.log - def update_floatingip(self, context, id, floatingip): - with context.session.begin(subtransactions=True): - # update floatingip in DB - new_fl_ip = super(L3RestProxy, - self).update_floatingip(context, id, floatingip) - - # update network on network controller - if 'floatingip' in self.servers.get_capabilities(): - self.servers.rest_update_floatingip(new_fl_ip['tenant_id'], - new_fl_ip, id) - else: - self._send_floatingip_update(context) - return new_fl_ip - - @put_context_in_serverpool - @log.log - def delete_floatingip(self, context, id): - with context.session.begin(subtransactions=True): - # delete floating IP in DB - old_fip = super(L3RestProxy, self).get_floatingip(context, id) - super(L3RestProxy, self).delete_floatingip(context, id) - - # update network on network controller - if 'floatingip' in self.servers.get_capabilities(): - self.servers.rest_delete_floatingip(old_fip['tenant_id'], id) - else: - self._send_floatingip_update(context) - - @put_context_in_serverpool - @log.log - def disassociate_floatingips(self, context, port_id, do_notify=True): - router_ids = super(L3RestProxy, self).disassociate_floatingips( - context, port_id, do_notify=do_notify) - self._send_floatingip_update(context) - return router_ids - - # overriding method from l3_db as original method calls - # self.delete_floatingip() which in turn calls self.delete_port() which - # is locked with 'bsn-port-barrier' - @put_context_in_serverpool - def delete_disassociated_floatingips(self, context, network_id): - query = self._model_query(context, l3_db.FloatingIP) - query = query.filter_by(floating_network_id=network_id, - fixed_port_id=None, - router_id=None) - for fip in query: - context.session.delete(fip) - self._delete_port(context.elevated(), fip['floating_port_id']) - - def _send_floatingip_update(self, context): - try: - ext_net_id = self.get_external_network_id(context) - if ext_net_id: - # Use the elevated state of the context for the ext_net query - admin_context = context.elevated() - ext_net = super(L3RestProxy, - self).get_network(admin_context, ext_net_id) - # update external network on network controller - self._send_update_network(ext_net, admin_context) - except exceptions.TooManyExternalNetworks: - # get_external_network can raise errors when multiple external - # networks are detected, which isn't supported by the Plugin - LOG.error(_LE("NeutronRestProxyV2: too many external networks")) - - def _get_tenant_default_router_rules(self, tenant): - rules = cfg.CONF.ROUTER.tenant_default_router_rule - default_set = [] - tenant_set = [] - for rule in rules: - items = rule.split(':') - # put an empty string on the end if nexthops wasn't specified - if len(items) < ROUTER_RULE_COMPONENT_COUNT: - items.append('') - try: - (tenant_id, source, destination, action, nexthops) = items - except ValueError: - continue - parsed_rule = {'source': source, - 'destination': destination, 'action': action, - 'nexthops': [hop for hop in nexthops.split(',') - if hop]} - if tenant_id == '*': - default_set.append(parsed_rule) - if tenant_id == tenant: - tenant_set.append(parsed_rule) - return tenant_set if tenant_set else default_set +L3RestProxy = l3_router_plugin.L3RestProxy diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index 92f88e0d0c0..b067c21746f 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -16,880 +16,9 @@ """ Neutron REST Proxy Plug-in for Big Switch and FloodLight Controllers. -NeutronRestProxy provides a generic neutron plugin that translates all plugin -function calls to equivalent authenticated REST calls to a set of redundant -external network controllers. It also keeps persistent store for all neutron -state to allow for re-sync of the external controller(s), if required. - -The local state on the plugin also allows for local response and fast-fail -semantics where it can be determined based on the local persistent store. - -Network controller specific code is decoupled from this plugin and expected -to reside on the controller itself (via the REST interface). - -This allows for: - - independent authentication and redundancy schemes between neutron and the - network controller - - independent upgrade/development cycles between neutron and the controller - as it limits the proxy code upgrade requirement to neutron release cycle - and the controller specific code upgrade requirement to controller code - - ability to sync the controller with neutron for independent recovery/reset - -External REST API used by proxy is the same API as defined for neutron (JSON -subset) with some additional parameters (gateway on network-create and macaddr -on port-attach) on an additional PUT to do a bulk dump of all persistent data. +See http://github.com/stackforge/networking-bigswitch for more information """ +from bsnstacklib.plugins.bigswitch import plugin -import copy -import functools -import httplib -import re -import eventlet -from oslo_config import cfg -import oslo_messaging -from oslo_utils import importutils -from sqlalchemy.orm import exc as sqlexc - -from neutron.agent import securitygroups_rpc as sg_rpc -from neutron.api import extensions as neutron_extensions -from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api -from neutron.api.rpc.handlers import dhcp_rpc -from neutron.api.rpc.handlers import metadata_rpc -from neutron.api.rpc.handlers import securitygroups_rpc -from neutron.common import constants as const -from neutron.common import exceptions -from neutron.common import rpc as n_rpc -from neutron.common import topics -from neutron.common import utils -from neutron import context as qcontext -from neutron.db import agents_db -from neutron.db import agentschedulers_db -from neutron.db import allowedaddresspairs_db as addr_pair_db -from neutron.db import api as db -from neutron.db import db_base_plugin_v2 -from neutron.db import external_net_db -from neutron.db import extradhcpopt_db -from neutron.db import l3_db -from neutron.db import models_v2 -from neutron.db import securitygroups_db as sg_db -from neutron.db import securitygroups_rpc_base as sg_db_rpc -from neutron.extensions import allowedaddresspairs as addr_pair -from neutron.extensions import external_net -from neutron.extensions import extra_dhcp_opt as edo_ext -from neutron.extensions import portbindings -from neutron import manager -from neutron.i18n import _LE, _LI, _LW -from neutron.openstack.common import log as logging -from neutron.plugins.bigswitch import config as pl_config -from neutron.plugins.bigswitch.db import porttracker_db -from neutron.plugins.bigswitch import extensions -from neutron.plugins.bigswitch import servermanager -from neutron.plugins.bigswitch import version -from neutron.plugins.common import constants as pconst - -LOG = logging.getLogger(__name__) - -SYNTAX_ERROR_MESSAGE = _('Syntax error in server config file, aborting plugin') -METADATA_SERVER_IP = '169.254.169.254' - - -class AgentNotifierApi(sg_rpc.SecurityGroupAgentRpcApiMixin): - - def __init__(self, topic): - self.topic = topic - target = oslo_messaging.Target(topic=topic, version='1.0') - self.client = n_rpc.get_client(target) - - def port_update(self, context, port): - topic_port_update = topics.get_topic_name(self.client.target.topic, - topics.PORT, topics.UPDATE) - cctxt = self.client.prepare(fanout=True, topic=topic_port_update) - cctxt.cast(context, 'port_update', port=port) - - -class SecurityGroupServerRpcMixin(sg_db_rpc.SecurityGroupServerRpcMixin): - - def get_port_from_device(self, device): - port_id = re.sub(r"^%s" % const.TAP_DEVICE_PREFIX, "", device) - port = self.get_port_and_sgs(port_id) - if port: - port['device'] = device - return port - - def get_port_and_sgs(self, port_id): - """Get port from database with security group info.""" - - LOG.debug("get_port_and_sgs() called for port_id %s", port_id) - session = db.get_session() - sg_binding_port = sg_db.SecurityGroupPortBinding.port_id - - with session.begin(subtransactions=True): - query = session.query( - models_v2.Port, - sg_db.SecurityGroupPortBinding.security_group_id - ) - query = query.outerjoin(sg_db.SecurityGroupPortBinding, - models_v2.Port.id == sg_binding_port) - query = query.filter(models_v2.Port.id.startswith(port_id)) - port_and_sgs = query.all() - if not port_and_sgs: - return - port = port_and_sgs[0][0] - plugin = manager.NeutronManager.get_plugin() - port_dict = plugin._make_port_dict(port) - port_dict['security_groups'] = [ - sg_id for port_, sg_id in port_and_sgs if sg_id] - port_dict['security_group_rules'] = [] - port_dict['security_group_source_groups'] = [] - port_dict['fixed_ips'] = [ip['ip_address'] - for ip in port['fixed_ips']] - return port_dict - - -class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2, - external_net_db.External_net_db_mixin): - - supported_extension_aliases = ["binding"] - servers = None - - @property - def l3_plugin(self): - return manager.NeutronManager.get_service_plugins().get( - pconst.L3_ROUTER_NAT) - - def _get_all_data(self, get_ports=True, get_floating_ips=True, - get_routers=True): - admin_context = qcontext.get_admin_context() - networks = [] - # this method is used by the ML2 driver so it can't directly invoke - # the self.get_(ports|networks) methods - plugin = manager.NeutronManager.get_plugin() - all_networks = plugin.get_networks(admin_context) or [] - for net in all_networks: - mapped_network = self._get_mapped_network_with_subnets(net) - flips_n_ports = mapped_network - if get_floating_ips: - flips_n_ports = self._get_network_with_floatingips( - mapped_network) - - if get_ports: - ports = [] - net_filter = {'network_id': [net.get('id')]} - net_ports = plugin.get_ports(admin_context, - filters=net_filter) or [] - for port in net_ports: - mapped_port = self._map_state_and_status(port) - mapped_port['attachment'] = { - 'id': port.get('device_id'), - 'mac': port.get('mac_address'), - } - mapped_port = self._extend_port_dict_binding(admin_context, - mapped_port) - ports.append(mapped_port) - flips_n_ports['ports'] = ports - - if flips_n_ports: - networks.append(flips_n_ports) - - data = {'networks': networks} - - if get_routers and self.l3_plugin: - routers = [] - all_routers = self.l3_plugin.get_routers(admin_context) or [] - for router in all_routers: - interfaces = [] - mapped_router = self._map_state_and_status(router) - router_filter = { - 'device_owner': [const.DEVICE_OWNER_ROUTER_INTF], - 'device_id': [router.get('id')] - } - router_ports = self.get_ports(admin_context, - filters=router_filter) or [] - for port in router_ports: - net_id = port.get('network_id') - subnet_id = port['fixed_ips'][0]['subnet_id'] - intf_details = self._get_router_intf_details(admin_context, - net_id, - subnet_id) - interfaces.append(intf_details) - mapped_router['interfaces'] = interfaces - - routers.append(mapped_router) - - data.update({'routers': routers}) - return data - - def _send_all_data(self, send_ports=True, send_floating_ips=True, - send_routers=True, timeout=None, - triggered_by_tenant=None): - """Pushes all data to network ctrl (networks/ports, ports/attachments). - - This gives the controller an option to re-sync it's persistent store - with neutron's current view of that data. - """ - data = self._get_all_data(send_ports, send_floating_ips, send_routers) - data['triggered_by_tenant'] = triggered_by_tenant - errstr = _("Unable to update remote topology: %s") - return self.servers.rest_action('PUT', servermanager.TOPOLOGY_PATH, - data, errstr, timeout=timeout) - - def _get_network_with_floatingips(self, network, context=None): - if context is None: - context = qcontext.get_admin_context() - - net_id = network['id'] - net_filter = {'floating_network_id': [net_id]} - if self.l3_plugin: - fl_ips = self.l3_plugin.get_floatingips(context, - filters=net_filter) or [] - network['floatingips'] = fl_ips - - return network - - def _get_all_subnets_json_for_network(self, net_id, context=None): - if context is None: - context = qcontext.get_admin_context() - # start a sub-transaction to avoid breaking parent transactions - with context.session.begin(subtransactions=True): - subnets = self._get_subnets_by_network(context, - net_id) - subnets_details = [] - if subnets: - for subnet in subnets: - subnet_dict = self._make_subnet_dict(subnet) - mapped_subnet = self._map_state_and_status(subnet_dict) - subnets_details.append(mapped_subnet) - - return subnets_details - - def _get_mapped_network_with_subnets(self, network, context=None): - # if context is not provided, admin context is used - if context is None: - context = qcontext.get_admin_context() - network = self._map_state_and_status(network) - subnets = self._get_all_subnets_json_for_network(network['id'], - context) - network['subnets'] = subnets - for subnet in (subnets or []): - if subnet['gateway_ip']: - # FIX: For backward compatibility with wire protocol - network['gateway'] = subnet['gateway_ip'] - break - else: - network['gateway'] = '' - network[external_net.EXTERNAL] = self._network_is_external( - context, network['id']) - # include ML2 segmentation types - network['segmentation_types'] = getattr(self, "segmentation_types", "") - return network - - def _send_create_network(self, network, context=None): - tenant_id = network['tenant_id'] - mapped_network = self._get_mapped_network_with_subnets(network, - context) - self.servers.rest_create_network(tenant_id, mapped_network) - - def _send_update_network(self, network, context=None): - net_id = network['id'] - tenant_id = network['tenant_id'] - mapped_network = self._get_mapped_network_with_subnets(network, - context) - net_fl_ips = self._get_network_with_floatingips(mapped_network, - context) - self.servers.rest_update_network(tenant_id, net_id, net_fl_ips) - - def _send_delete_network(self, network, context=None): - net_id = network['id'] - tenant_id = network['tenant_id'] - self.servers.rest_delete_network(tenant_id, net_id) - - def _map_state_and_status(self, resource): - resource = copy.copy(resource) - - resource['state'] = ('UP' if resource.pop('admin_state_up', - True) else 'DOWN') - resource.pop('status', None) - - return resource - - def _warn_on_state_status(self, resource): - if resource.get('admin_state_up', True) is False: - LOG.warning(_LW("Setting admin_state_up=False is not supported " - "in this plugin version. Ignoring setting for " - "resource: %s"), resource) - - if 'status' in resource: - if resource['status'] != const.NET_STATUS_ACTIVE: - LOG.warning(_LW("Operational status is internally set by the " - "plugin. Ignoring setting status=%s."), - resource['status']) - - def _get_router_intf_details(self, context, intf_id, subnet_id): - - # we will use the network id as interface's id - net_id = intf_id - network = self.get_network(context, net_id) - subnet = self.get_subnet(context, subnet_id) - mapped_network = self._get_mapped_network_with_subnets(network) - mapped_subnet = self._map_state_and_status(subnet) - - data = { - 'id': intf_id, - "network": mapped_network, - "subnet": mapped_subnet - } - - return data - - def _extend_port_dict_binding(self, context, port): - cfg_vif_type = cfg.CONF.NOVA.vif_type.lower() - if cfg_vif_type not in (portbindings.VIF_TYPE_OVS, - portbindings.VIF_TYPE_IVS): - LOG.warning(_LW("Unrecognized vif_type in configuration " - "[%s]. Defaulting to ovs."), - cfg_vif_type) - cfg_vif_type = portbindings.VIF_TYPE_OVS - # In ML2, the host_id is already populated - if portbindings.HOST_ID in port: - hostid = port[portbindings.HOST_ID] - elif 'id' in port: - hostid = porttracker_db.get_port_hostid(context, port['id']) - else: - hostid = None - if hostid: - port[portbindings.HOST_ID] = hostid - override = self._check_hostvif_override(hostid) - if override: - cfg_vif_type = override - port[portbindings.VIF_TYPE] = cfg_vif_type - - sg_enabled = sg_rpc.is_firewall_enabled() - port[portbindings.VIF_DETAILS] = { - # TODO(rkukura): Replace with new VIF security details - portbindings.CAP_PORT_FILTER: - 'security-group' in self.supported_extension_aliases, - portbindings.OVS_HYBRID_PLUG: sg_enabled - } - return port - - def _check_hostvif_override(self, hostid): - for v in cfg.CONF.NOVA.vif_types: - if hostid in getattr(cfg.CONF.NOVA, "node_override_vif_" + v, []): - return v - return False - - def _get_port_net_tenantid(self, context, port): - net = super(NeutronRestProxyV2Base, - self).get_network(context, port["network_id"]) - return net['tenant_id'] - - def async_port_create(self, tenant_id, net_id, port): - try: - self.servers.rest_create_port(tenant_id, net_id, port) - except servermanager.RemoteRestError as e: - # 404 should never be received on a port create unless - # there are inconsistencies between the data in neutron - # and the data in the backend. - # Run a sync to get it consistent. - if (cfg.CONF.RESTPROXY.auto_sync_on_failure and - e.status == httplib.NOT_FOUND and - servermanager.NXNETWORK in e.reason): - LOG.error(_LE("Iconsistency with backend controller " - "triggering full synchronization.")) - # args depend on if we are operating in ML2 driver - # or as the full plugin - topoargs = self.servers.get_topo_function_args - self._send_all_data( - send_ports=topoargs['get_ports'], - send_floating_ips=topoargs['get_floating_ips'], - send_routers=topoargs['get_routers'], - triggered_by_tenant=tenant_id - ) - # If the full sync worked, the port will be created - # on the controller so it can be safely marked as active - else: - # Any errors that don't result in a successful auto-sync - # require that the port be placed into the error state. - LOG.error( - _LE("NeutronRestProxyV2: Unable to create port: %s"), e) - try: - self._set_port_status(port['id'], const.PORT_STATUS_ERROR) - except exceptions.PortNotFound: - # If port is already gone from DB and there was an error - # creating on the backend, everything is already consistent - pass - return - new_status = (const.PORT_STATUS_ACTIVE if port['state'] == 'UP' - else const.PORT_STATUS_DOWN) - try: - self._set_port_status(port['id'], new_status) - except exceptions.PortNotFound: - # This port was deleted before the create made it to the controller - # so it now needs to be deleted since the normal delete request - # would have deleted an non-existent port. - self.servers.rest_delete_port(tenant_id, net_id, port['id']) - - # NOTE(kevinbenton): workaround for eventlet/mysql deadlock - @utils.synchronized('bsn-port-barrier') - def _set_port_status(self, port_id, status): - session = db.get_session() - try: - port = session.query(models_v2.Port).filter_by(id=port_id).one() - port['status'] = status - session.flush() - except sqlexc.NoResultFound: - raise exceptions.PortNotFound(port_id=port_id) - - -def put_context_in_serverpool(f): - @functools.wraps(f) - def wrapper(self, context, *args, **kwargs): - # core plugin: context is top level object - # ml2: keeps context in _plugin_context - self.servers.set_context(getattr(context, '_plugin_context', context)) - return f(self, context, *args, **kwargs) - return wrapper - - -class NeutronRestProxyV2(NeutronRestProxyV2Base, - addr_pair_db.AllowedAddressPairsMixin, - extradhcpopt_db.ExtraDhcpOptMixin, - agentschedulers_db.DhcpAgentSchedulerDbMixin, - SecurityGroupServerRpcMixin): - - _supported_extension_aliases = ["external-net", "binding", - "extra_dhcp_opt", "quotas", - "dhcp_agent_scheduler", "agent", - "security-group", "allowed-address-pairs"] - - @property - def supported_extension_aliases(self): - if not hasattr(self, '_aliases'): - aliases = self._supported_extension_aliases[:] - sg_rpc.disable_security_group_extension_by_config(aliases) - self._aliases = aliases - return self._aliases - - def __init__(self): - super(NeutronRestProxyV2, self).__init__() - LOG.info(_LI('NeutronRestProxy: Starting plugin. Version=%s'), - version.version_string_with_vcs()) - pl_config.register_config() - self.evpool = eventlet.GreenPool(cfg.CONF.RESTPROXY.thread_pool_size) - - # Include the Big Switch Extensions path in the api_extensions - neutron_extensions.append_api_extensions_path(extensions.__path__) - - self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route - - # init network ctrl connections - self.servers = servermanager.ServerPool() - self.servers.get_topo_function = self._get_all_data - self.servers.get_topo_function_args = {'get_ports': True, - 'get_floating_ips': True, - 'get_routers': True} - - self.network_scheduler = importutils.import_object( - cfg.CONF.network_scheduler_driver - ) - - # setup rpc for security and DHCP agents - self._setup_rpc() - - if cfg.CONF.RESTPROXY.sync_data: - self._send_all_data() - - self.start_periodic_dhcp_agent_status_check() - LOG.debug("NeutronRestProxyV2: initialization done") - - def _setup_rpc(self): - self.conn = n_rpc.create_connection(new=True) - self.topic = topics.PLUGIN - self.notifier = AgentNotifierApi(topics.AGENT) - # init dhcp agent support - self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() - self.agent_notifiers[const.AGENT_TYPE_DHCP] = ( - self._dhcp_agent_notifier - ) - self.endpoints = [securitygroups_rpc.SecurityGroupServerRpcCallback(), - dhcp_rpc.DhcpRpcCallback(), - agents_db.AgentExtRpcCallback(), - metadata_rpc.MetadataRpcCallback()] - self.conn.create_consumer(self.topic, self.endpoints, - fanout=False) - # Consume from all consumers in threads - self.conn.consume_in_threads() - - @put_context_in_serverpool - def create_network(self, context, network): - """Create a network. - - Network represents an L2 network segment which can have a set of - subnets and ports associated with it. - - :param context: neutron api request context - :param network: dictionary describing the network - - :returns: a sequence of mappings with the following signature: - { - "id": UUID representing the network. - "name": Human-readable name identifying the network. - "tenant_id": Owner of network. NOTE: only admin user can specify - a tenant_id other than its own. - "admin_state_up": Sets admin state of network. - if down, network does not forward packets. - "status": Indicates whether network is currently operational - (values are "ACTIVE", "DOWN", "BUILD", and "ERROR") - "subnets": Subnets associated with this network. - } - - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2: create_network() called") - - self._warn_on_state_status(network['network']) - - with context.session.begin(subtransactions=True): - self._ensure_default_security_group( - context, - network['network']["tenant_id"] - ) - # create network in DB - new_net = super(NeutronRestProxyV2, self).create_network(context, - network) - self._process_l3_create(context, new_net, network['network']) - # create network on the network controller - self._send_create_network(new_net, context) - - # return created network - return new_net - - @put_context_in_serverpool - def update_network(self, context, net_id, network): - """Updates the properties of a particular Virtual Network. - - :param context: neutron api request context - :param net_id: uuid of the network to update - :param network: dictionary describing the updates - - :returns: a sequence of mappings with the following signature: - { - "id": UUID representing the network. - "name": Human-readable name identifying the network. - "tenant_id": Owner of network. NOTE: only admin user can - specify a tenant_id other than its own. - "admin_state_up": Sets admin state of network. - if down, network does not forward packets. - "status": Indicates whether network is currently operational - (values are "ACTIVE", "DOWN", "BUILD", and "ERROR") - "subnets": Subnets associated with this network. - } - - :raises: exceptions.NetworkNotFound - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2.update_network() called") - - self._warn_on_state_status(network['network']) - - session = context.session - with session.begin(subtransactions=True): - new_net = super(NeutronRestProxyV2, self).update_network( - context, net_id, network) - self._process_l3_update(context, new_net, network['network']) - - # update network on network controller - self._send_update_network(new_net, context) - return new_net - - # NOTE(kevinbenton): workaround for eventlet/mysql deadlock - @utils.synchronized('bsn-port-barrier') - @put_context_in_serverpool - def delete_network(self, context, net_id): - """Delete a network. - :param context: neutron api request context - :param id: UUID representing the network to delete. - - :returns: None - - :raises: exceptions.NetworkInUse - :raises: exceptions.NetworkNotFound - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2: delete_network() called") - - # Validate args - orig_net = super(NeutronRestProxyV2, self).get_network(context, net_id) - with context.session.begin(subtransactions=True): - self._process_l3_delete(context, net_id) - ret_val = super(NeutronRestProxyV2, self).delete_network(context, - net_id) - self._send_delete_network(orig_net, context) - return ret_val - - @put_context_in_serverpool - def create_port(self, context, port): - """Create a port, which is a connection point of a device - (e.g., a VM NIC) to attach an L2 Neutron network. - :param context: neutron api request context - :param port: dictionary describing the port - - :returns: - { - "id": uuid representing the port. - "network_id": uuid of network. - "tenant_id": tenant_id - "mac_address": mac address to use on this port. - "admin_state_up": Sets admin state of port. if down, port - does not forward packets. - "status": dicates whether port is currently operational - (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR") - "fixed_ips": list of subnet IDs and IP addresses to be used on - this port - "device_id": identifies the device (e.g., virtual server) using - this port. - } - - :raises: exceptions.NetworkNotFound - :raises: exceptions.StateInvalid - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2: create_port() called") - - # Update DB in new session so exceptions rollback changes - with context.session.begin(subtransactions=True): - self._ensure_default_security_group_on_port(context, port) - sgids = self._get_security_groups_on_port(context, port) - # non-router port status is set to pending. it is then updated - # after the async rest call completes. router ports are synchronous - if port['port']['device_owner'] == l3_db.DEVICE_OWNER_ROUTER_INTF: - port['port']['status'] = const.PORT_STATUS_ACTIVE - else: - port['port']['status'] = const.PORT_STATUS_BUILD - dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) - new_port = super(NeutronRestProxyV2, self).create_port(context, - port) - self._process_port_create_security_group(context, new_port, sgids) - if (portbindings.HOST_ID in port['port'] - and 'id' in new_port): - host_id = port['port'][portbindings.HOST_ID] - porttracker_db.put_port_hostid(context, new_port['id'], - host_id) - new_port[addr_pair.ADDRESS_PAIRS] = ( - self._process_create_allowed_address_pairs( - context, new_port, - port['port'].get(addr_pair.ADDRESS_PAIRS))) - self._process_port_create_extra_dhcp_opts(context, new_port, - dhcp_opts) - new_port = self._extend_port_dict_binding(context, new_port) - net = super(NeutronRestProxyV2, - self).get_network(context, new_port["network_id"]) - if self.add_meta_server_route: - if new_port['device_owner'] == const.DEVICE_OWNER_DHCP: - destination = METADATA_SERVER_IP + '/32' - self._add_host_route(context, destination, new_port) - - # create on network ctrl - mapped_port = self._map_state_and_status(new_port) - # ports have to be created synchronously when creating a router - # port since adding router interfaces is a multi-call process - if mapped_port['device_owner'] == l3_db.DEVICE_OWNER_ROUTER_INTF: - self.servers.rest_create_port(net["tenant_id"], - new_port["network_id"], - mapped_port) - else: - self.evpool.spawn_n(self.async_port_create, net["tenant_id"], - new_port["network_id"], mapped_port) - self.notify_security_groups_member_updated(context, new_port) - return new_port - - def get_port(self, context, id, fields=None): - with context.session.begin(subtransactions=True): - port = super(NeutronRestProxyV2, self).get_port(context, id, - fields) - self._extend_port_dict_binding(context, port) - return self._fields(port, fields) - - def get_ports(self, context, filters=None, fields=None): - with context.session.begin(subtransactions=True): - ports = super(NeutronRestProxyV2, self).get_ports(context, filters, - fields) - for port in ports: - self._extend_port_dict_binding(context, port) - return [self._fields(port, fields) for port in ports] - - @put_context_in_serverpool - def update_port(self, context, port_id, port): - """Update values of a port. - - :param context: neutron api request context - :param id: UUID representing the port to update. - :param port: dictionary with keys indicating fields to update. - - :returns: a mapping sequence with the following signature: - { - "id": uuid representing the port. - "network_id": uuid of network. - "tenant_id": tenant_id - "mac_address": mac address to use on this port. - "admin_state_up": sets admin state of port. if down, port - does not forward packets. - "status": dicates whether port is currently operational - (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR") - "fixed_ips": list of subnet IDs and IP addresses to be used on - this port - "device_id": identifies the device (e.g., virtual server) using - this port. - } - - :raises: exceptions.StateInvalid - :raises: exceptions.PortNotFound - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2: update_port() called") - - self._warn_on_state_status(port['port']) - - # Validate Args - orig_port = super(NeutronRestProxyV2, self).get_port(context, port_id) - with context.session.begin(subtransactions=True): - # Update DB - new_port = super(NeutronRestProxyV2, - self).update_port(context, port_id, port) - ctrl_update_required = False - if addr_pair.ADDRESS_PAIRS in port['port']: - ctrl_update_required |= ( - self.update_address_pairs_on_port(context, port_id, port, - orig_port, new_port)) - self._update_extra_dhcp_opts_on_port(context, port_id, port, - new_port) - old_host_id = porttracker_db.get_port_hostid(context, - orig_port['id']) - if (portbindings.HOST_ID in port['port'] - and 'id' in new_port): - host_id = port['port'][portbindings.HOST_ID] - porttracker_db.put_port_hostid(context, new_port['id'], - host_id) - if old_host_id != host_id: - ctrl_update_required = True - - if (new_port.get("device_id") != orig_port.get("device_id") and - orig_port.get("device_id")): - ctrl_update_required = True - - if ctrl_update_required: - # tenant_id must come from network in case network is shared - net_tenant_id = self._get_port_net_tenantid(context, new_port) - new_port = self._extend_port_dict_binding(context, new_port) - mapped_port = self._map_state_and_status(new_port) - self.servers.rest_update_port(net_tenant_id, - new_port["network_id"], - mapped_port) - need_port_update_notify = self.update_security_group_on_port( - context, port_id, port, orig_port, new_port) - need_port_update_notify |= self.is_security_group_member_updated( - context, orig_port, new_port) - - if need_port_update_notify: - self.notifier.port_update(context, new_port) - - # return new_port - return new_port - - # NOTE(kevinbenton): workaround for eventlet/mysql deadlock - @utils.synchronized('bsn-port-barrier') - @put_context_in_serverpool - def delete_port(self, context, port_id, l3_port_check=True): - """Delete a port. - :param context: neutron api request context - :param id: UUID representing the port to delete. - - :raises: exceptions.PortInUse - :raises: exceptions.PortNotFound - :raises: exceptions.NetworkNotFound - :raises: RemoteRestError - """ - LOG.debug("NeutronRestProxyV2: delete_port() called") - - # if needed, check to see if this is a port owned by - # and l3-router. If so, we should prevent deletion. - if l3_port_check and self.l3_plugin: - self.l3_plugin.prevent_l3_port_deletion(context, port_id) - with context.session.begin(subtransactions=True): - if self.l3_plugin: - router_ids = self.l3_plugin.disassociate_floatingips( - context, port_id, do_notify=False) - port = super(NeutronRestProxyV2, self).get_port(context, port_id) - # Tenant ID must come from network in case the network is shared - tenid = self._get_port_net_tenantid(context, port) - self._delete_port(context, port_id) - self.servers.rest_delete_port(tenid, port['network_id'], port_id) - - if self.l3_plugin: - # now that we've left db transaction, we are safe to notify - self.l3_plugin.notify_routers_updated(context, router_ids) - - @put_context_in_serverpool - def create_subnet(self, context, subnet): - LOG.debug("NeutronRestProxyV2: create_subnet() called") - - self._warn_on_state_status(subnet['subnet']) - - with context.session.begin(subtransactions=True): - # create subnet in DB - new_subnet = super(NeutronRestProxyV2, - self).create_subnet(context, subnet) - net_id = new_subnet['network_id'] - orig_net = super(NeutronRestProxyV2, - self).get_network(context, net_id) - # update network on network controller - self._send_update_network(orig_net, context) - return new_subnet - - @put_context_in_serverpool - def update_subnet(self, context, id, subnet): - LOG.debug("NeutronRestProxyV2: update_subnet() called") - - self._warn_on_state_status(subnet['subnet']) - - with context.session.begin(subtransactions=True): - # update subnet in DB - new_subnet = super(NeutronRestProxyV2, - self).update_subnet(context, id, subnet) - net_id = new_subnet['network_id'] - orig_net = super(NeutronRestProxyV2, - self).get_network(context, net_id) - # update network on network controller - self._send_update_network(orig_net, context) - return new_subnet - - @put_context_in_serverpool - def delete_subnet(self, context, id): - LOG.debug("NeutronRestProxyV2: delete_subnet() called") - orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id) - net_id = orig_subnet['network_id'] - with context.session.begin(subtransactions=True): - # delete subnet in DB - super(NeutronRestProxyV2, self).delete_subnet(context, id) - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - exception will rollback - self._send_update_network(orig_net, context) - - def _add_host_route(self, context, destination, port): - subnet = {} - for fixed_ip in port['fixed_ips']: - subnet_id = fixed_ip['subnet_id'] - nexthop = fixed_ip['ip_address'] - subnet['host_routes'] = [{'destination': destination, - 'nexthop': nexthop}] - updated_subnet = self.update_subnet(context, - subnet_id, - {'subnet': subnet}) - payload = {'subnet': updated_subnet} - self._dhcp_agent_notifier.notify(context, payload, - 'subnet.update.end') - LOG.debug("Adding host route: ") - LOG.debug("Destination:%(dst)s nexthop:%(next)s", - {'dst': destination, 'next': nexthop}) +NeutronRestProxyV2 = plugin.NeutronRestProxyV2 diff --git a/neutron/plugins/bigswitch/requirements.txt b/neutron/plugins/bigswitch/requirements.txt new file mode 100644 index 00000000000..ecbbce6fe21 --- /dev/null +++ b/neutron/plugins/bigswitch/requirements.txt @@ -0,0 +1 @@ +bsnstacklib>=2015.1,<2015.2 diff --git a/neutron/plugins/bigswitch/routerrule_db.py b/neutron/plugins/bigswitch/routerrule_db.py index df4dbe61034..75835994dc8 100644 --- a/neutron/plugins/bigswitch/routerrule_db.py +++ b/neutron/plugins/bigswitch/routerrule_db.py @@ -13,17 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg import sqlalchemy as sa from sqlalchemy import orm -from neutron.db import l3_db from neutron.db import model_base -from neutron.openstack.common import log as logging -from neutron.plugins.bigswitch.extensions import routerrule - - -LOG = logging.getLogger(__name__) class RouterRule(model_base.BASEV2): @@ -43,104 +36,3 @@ class NextHop(model_base.BASEV2): ondelete="CASCADE"), primary_key=True) nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True) - - -class RouterRule_db_mixin(l3_db.L3_NAT_db_mixin): - """Mixin class to support route rule configuration on a router.""" - def update_router(self, context, id, router): - r = router['router'] - with context.session.begin(subtransactions=True): - router_db = self._get_router(context, id) - if 'router_rules' in r: - self._update_router_rules(context, - router_db, - r['router_rules']) - updated = super(RouterRule_db_mixin, self).update_router( - context, id, router) - updated['router_rules'] = self._get_router_rules_by_router_id( - context, id) - - return updated - - def create_router(self, context, router): - r = router['router'] - with context.session.begin(subtransactions=True): - router_db = super(RouterRule_db_mixin, self).create_router( - context, router) - if 'router_rules' in r: - self._update_router_rules(context, - router_db, - r['router_rules']) - else: - LOG.debug('No rules in router') - router_db['router_rules'] = self._get_router_rules_by_router_id( - context, router_db['id']) - - return router_db - - def _update_router_rules(self, context, router, rules): - if len(rules) > cfg.CONF.ROUTER.max_router_rules: - raise routerrule.RulesExhausted( - router_id=router['id'], - quota=cfg.CONF.ROUTER.max_router_rules) - del_context = context.session.query(RouterRule) - del_context.filter_by(router_id=router['id']).delete() - context.session.expunge_all() - LOG.debug('Updating router rules to %s', rules) - for rule in rules: - router_rule = RouterRule( - router_id=router['id'], - destination=rule['destination'], - source=rule['source'], - action=rule['action']) - router_rule.nexthops = [NextHop(nexthop=hop) - for hop in rule['nexthops']] - context.session.add(router_rule) - context.session.flush() - - def _make_router_rule_list(self, router_rules): - ruleslist = [] - for rule in router_rules: - hops = [hop['nexthop'] for hop in rule['nexthops']] - ruleslist.append({'id': rule['id'], - 'destination': rule['destination'], - 'source': rule['source'], - 'action': rule['action'], - 'nexthops': hops}) - return ruleslist - - def _get_router_rules_by_router_id(self, context, id): - query = context.session.query(RouterRule) - router_rules = query.filter_by(router_id=id).all() - return self._make_router_rule_list(router_rules) - - def get_router(self, context, id, fields=None): - with context.session.begin(subtransactions=True): - router = super(RouterRule_db_mixin, self).get_router( - context, id, fields) - router['router_rules'] = self._get_router_rules_by_router_id( - context, id) - return router - - def get_routers(self, context, filters=None, fields=None, - sorts=None, limit=None, marker=None, - page_reverse=False): - with context.session.begin(subtransactions=True): - routers = super(RouterRule_db_mixin, self).get_routers( - context, filters, fields, sorts=sorts, limit=limit, - marker=marker, page_reverse=page_reverse) - for router in routers: - router['router_rules'] = self._get_router_rules_by_router_id( - context, router['id']) - return routers - - def get_sync_data(self, context, router_ids=None, active=None): - """Query routers and their related floating_ips, interfaces.""" - with context.session.begin(subtransactions=True): - routers = super(RouterRule_db_mixin, - self).get_sync_data(context, router_ids, - active=active) - for router in routers: - router['router_rules'] = self._get_router_rules_by_router_id( - context, router['id']) - return routers diff --git a/neutron/plugins/bigswitch/servermanager.py b/neutron/plugins/bigswitch/servermanager.py deleted file mode 100644 index b624f19b923..00000000000 --- a/neutron/plugins/bigswitch/servermanager.py +++ /dev/null @@ -1,657 +0,0 @@ -# Copyright 2014 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. - -""" -This module manages the HTTP and HTTPS connections to the backend controllers. - -The main class it provides for external use is ServerPool which manages a set -of ServerProxy objects that correspond to individual backend controllers. - -The following functionality is handled by this module: -- Translation of rest_* function calls to HTTP/HTTPS calls to the controllers -- Automatic failover between controllers -- SSL Certificate enforcement -- HTTP Authentication - -""" -import base64 -import httplib -import os -import socket -import ssl -import time -import weakref - -import eventlet -import eventlet.corolocal -from oslo_config import cfg -from oslo_serialization import jsonutils -from oslo_utils import excutils - -from neutron.common import exceptions -from neutron.i18n import _LE, _LI, _LW -from neutron.openstack.common import log as logging -from neutron.plugins.bigswitch.db import consistency_db as cdb - -LOG = logging.getLogger(__name__) - -# The following are used to invoke the API on the external controller -CAPABILITIES_PATH = "/capabilities" -NET_RESOURCE_PATH = "/tenants/%s/networks" -PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports" -ROUTER_RESOURCE_PATH = "/tenants/%s/routers" -ROUTER_INTF_OP_PATH = "/tenants/%s/routers/%s/interfaces" -NETWORKS_PATH = "/tenants/%s/networks/%s" -FLOATINGIPS_PATH = "/tenants/%s/floatingips/%s" -PORTS_PATH = "/tenants/%s/networks/%s/ports/%s" -ATTACHMENT_PATH = "/tenants/%s/networks/%s/ports/%s/attachment" -ROUTERS_PATH = "/tenants/%s/routers/%s" -ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s" -TOPOLOGY_PATH = "/topology" -HEALTH_PATH = "/health" -SWITCHES_PATH = "/switches/%s" -SUCCESS_CODES = range(200, 207) -FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503, - 504, 505] -BASE_URI = '/networkService/v1.1' -ORCHESTRATION_SERVICE_ID = 'Neutron v2.0' -HASH_MATCH_HEADER = 'X-BSN-BVS-HASH-MATCH' -REQ_CONTEXT_HEADER = 'X-REQ-CONTEXT' -# error messages -NXNETWORK = 'NXVNS' -HTTP_SERVICE_UNAVAILABLE_RETRY_COUNT = 3 -HTTP_SERVICE_UNAVAILABLE_RETRY_INTERVAL = 3 - - -class RemoteRestError(exceptions.NeutronException): - message = _("Error in REST call to remote network " - "controller: %(reason)s") - status = None - - def __init__(self, **kwargs): - self.status = kwargs.pop('status', None) - self.reason = kwargs.get('reason') - super(RemoteRestError, self).__init__(**kwargs) - - -class ServerProxy(object): - """REST server proxy to a network controller.""" - - def __init__(self, server, port, ssl, auth, neutron_id, timeout, - base_uri, name, mypool, combined_cert): - self.server = server - self.port = port - self.ssl = ssl - self.base_uri = base_uri - self.timeout = timeout - self.name = name - self.success_codes = SUCCESS_CODES - self.auth = None - self.neutron_id = neutron_id - self.failed = False - self.capabilities = [] - # enable server to reference parent pool - self.mypool = mypool - # cache connection here to avoid a SSL handshake for every connection - self.currentconn = None - if auth: - self.auth = 'Basic ' + base64.encodestring(auth).strip() - self.combined_cert = combined_cert - - def get_capabilities(self): - try: - body = self.rest_call('GET', CAPABILITIES_PATH)[2] - self.capabilities = jsonutils.loads(body) - except Exception: - LOG.exception(_LE("Couldn't retrieve capabilities. " - "Newer API calls won't be supported.")) - LOG.info(_LI("The following capabilities were received " - "for %(server)s: %(cap)s"), {'server': self.server, - 'cap': self.capabilities}) - return self.capabilities - - def rest_call(self, action, resource, data='', headers=None, - timeout=False, reconnect=False, hash_handler=None): - uri = self.base_uri + resource - body = jsonutils.dumps(data) - headers = headers or {} - headers['Content-type'] = 'application/json' - headers['Accept'] = 'application/json' - headers['NeutronProxy-Agent'] = self.name - headers['Instance-ID'] = self.neutron_id - headers['Orchestration-Service-ID'] = ORCHESTRATION_SERVICE_ID - if hash_handler: - # this will be excluded on calls that don't need hashes - # (e.g. topology sync, capability checks) - headers[HASH_MATCH_HEADER] = hash_handler.read_for_update() - else: - hash_handler = cdb.HashHandler() - if 'keep-alive' in self.capabilities: - headers['Connection'] = 'keep-alive' - else: - reconnect = True - if self.auth: - headers['Authorization'] = self.auth - - LOG.debug("ServerProxy: server=%(server)s, port=%(port)d, " - "ssl=%(ssl)r", - {'server': self.server, 'port': self.port, 'ssl': self.ssl}) - LOG.debug("ServerProxy: resource=%(resource)s, data=%(data)r, " - "headers=%(headers)r, action=%(action)s", - {'resource': resource, 'data': data, 'headers': headers, - 'action': action}) - - # unspecified timeout is False because a timeout can be specified as - # None to indicate no timeout. - if timeout is False: - timeout = self.timeout - - if timeout != self.timeout: - # need a new connection if timeout has changed - reconnect = True - - if not self.currentconn or reconnect: - if self.currentconn: - self.currentconn.close() - if self.ssl: - self.currentconn = HTTPSConnectionWithValidation( - self.server, self.port, timeout=timeout) - if self.currentconn is None: - LOG.error(_LE('ServerProxy: Could not establish HTTPS ' - 'connection')) - return 0, None, None, None - self.currentconn.combined_cert = self.combined_cert - else: - self.currentconn = httplib.HTTPConnection( - self.server, self.port, timeout=timeout) - if self.currentconn is None: - LOG.error(_LE('ServerProxy: Could not establish HTTP ' - 'connection')) - return 0, None, None, None - - try: - self.currentconn.request(action, uri, body, headers) - response = self.currentconn.getresponse() - respstr = response.read() - respdata = respstr - if response.status in self.success_codes: - hash_value = response.getheader(HASH_MATCH_HEADER) - # don't clear hash from DB if a hash header wasn't present - if hash_value is not None: - hash_handler.put_hash(hash_value) - else: - hash_handler.clear_lock() - try: - respdata = jsonutils.loads(respstr) - except ValueError: - # response was not JSON, ignore the exception - pass - else: - # release lock so others don't have to wait for timeout - hash_handler.clear_lock() - - ret = (response.status, response.reason, respstr, respdata) - except httplib.HTTPException: - # If we were using a cached connection, try again with a new one. - with excutils.save_and_reraise_exception() as ctxt: - self.currentconn.close() - if reconnect: - # if reconnect is true, this was on a fresh connection so - # reraise since this server seems to be broken - ctxt.reraise = True - else: - # if reconnect is false, it was a cached connection so - # try one more time before re-raising - ctxt.reraise = False - return self.rest_call(action, resource, data, headers, - timeout=timeout, reconnect=True) - except (socket.timeout, socket.error) as e: - self.currentconn.close() - LOG.error(_LE('ServerProxy: %(action)s failure, %(e)r'), - {'action': action, 'e': e}) - ret = 0, None, None, None - LOG.debug("ServerProxy: status=%(status)d, reason=%(reason)r, " - "ret=%(ret)s, data=%(data)r", {'status': ret[0], - 'reason': ret[1], - 'ret': ret[2], - 'data': ret[3]}) - return ret - - -class ServerPool(object): - - _instance = None - - @classmethod - def get_instance(cls): - if cls._instance: - return cls._instance - cls._instance = cls() - return cls._instance - - def __init__(self, timeout=False, - base_uri=BASE_URI, name='NeutronRestProxy'): - LOG.debug("ServerPool: initializing") - # 'servers' is the list of network controller REST end-points - # (used in order specified till one succeeds, and it is sticky - # till next failure). Use 'server_auth' to encode api-key - servers = cfg.CONF.RESTPROXY.servers - self.auth = cfg.CONF.RESTPROXY.server_auth - self.ssl = cfg.CONF.RESTPROXY.server_ssl - self.neutron_id = cfg.CONF.RESTPROXY.neutron_id - self.base_uri = base_uri - self.name = name - self.contexts = {} - self.timeout = cfg.CONF.RESTPROXY.server_timeout - self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections - default_port = 8000 - if timeout is not False: - self.timeout = timeout - - # Function to use to retrieve topology for consistency syncs. - # Needs to be set by module that uses the servermanager. - self.get_topo_function = None - self.get_topo_function_args = {} - - if not servers: - raise cfg.Error(_('Servers not defined. Aborting server manager.')) - servers = [s if len(s.rsplit(':', 1)) == 2 - else "%s:%d" % (s, default_port) - for s in servers] - if any((len(spl) != 2 or not spl[1].isdigit()) - for spl in [sp.rsplit(':', 1) - for sp in servers]): - raise cfg.Error(_('Servers must be defined as :. ' - 'Configuration was %s') % servers) - self.servers = [ - self.server_proxy_for(server, int(port)) - for server, port in (s.rsplit(':', 1) for s in servers) - ] - eventlet.spawn(self._consistency_watchdog, - cfg.CONF.RESTPROXY.consistency_interval) - ServerPool._instance = self - LOG.debug("ServerPool: initialization done") - - def set_context(self, context): - # this context needs to be local to the greenthread - # so concurrent requests don't use the wrong context. - # Use a weakref so the context is garbage collected - # after the plugin is done with it. - ref = weakref.ref(context) - self.contexts[eventlet.corolocal.get_ident()] = ref - - def get_context_ref(self): - # Try to get the context cached for this thread. If one - # doesn't exist or if it's been garbage collected, this will - # just return None. - try: - return self.contexts[eventlet.corolocal.get_ident()]() - except KeyError: - return None - - def get_capabilities(self): - # lookup on first try - try: - return self.capabilities - except AttributeError: - # each server should return a list of capabilities it supports - # e.g. ['floatingip'] - capabilities = [set(server.get_capabilities()) - for server in self.servers] - # Pool only supports what all of the servers support - self.capabilities = set.intersection(*capabilities) - return self.capabilities - - def server_proxy_for(self, server, port): - combined_cert = self._get_combined_cert_for_server(server, port) - return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id, - self.timeout, self.base_uri, self.name, mypool=self, - combined_cert=combined_cert) - - def _get_combined_cert_for_server(self, server, port): - # The ssl library requires a combined file with all trusted certs - # so we make one containing the trusted CAs and the corresponding - # host cert for this server - combined_cert = None - if self.ssl and not cfg.CONF.RESTPROXY.no_ssl_validation: - base_ssl = cfg.CONF.RESTPROXY.ssl_cert_directory - host_dir = os.path.join(base_ssl, 'host_certs') - ca_dir = os.path.join(base_ssl, 'ca_certs') - combined_dir = os.path.join(base_ssl, 'combined') - combined_cert = os.path.join(combined_dir, '%s.pem' % server) - if not os.path.exists(base_ssl): - raise cfg.Error(_('ssl_cert_directory [%s] does not exist. ' - 'Create it or disable ssl.') % base_ssl) - for automake in [combined_dir, ca_dir, host_dir]: - if not os.path.exists(automake): - os.makedirs(automake) - - # get all CA certs - certs = self._get_ca_cert_paths(ca_dir) - - # check for a host specific cert - hcert, exists = self._get_host_cert_path(host_dir, server) - if exists: - certs.append(hcert) - elif cfg.CONF.RESTPROXY.ssl_sticky: - self._fetch_and_store_cert(server, port, hcert) - certs.append(hcert) - if not certs: - raise cfg.Error(_('No certificates were found to verify ' - 'controller %s') % (server)) - self._combine_certs_to_file(certs, combined_cert) - return combined_cert - - def _combine_certs_to_file(self, certs, cfile): - ''' - Concatenates the contents of each certificate in a list of - certificate paths to one combined location for use with ssl - sockets. - ''' - with open(cfile, 'w') as combined: - for c in certs: - with open(c, 'r') as cert_handle: - combined.write(cert_handle.read()) - - def _get_host_cert_path(self, host_dir, server): - ''' - returns full path and boolean indicating existence - ''' - hcert = os.path.join(host_dir, '%s.pem' % server) - if os.path.exists(hcert): - return hcert, True - return hcert, False - - def _get_ca_cert_paths(self, ca_dir): - certs = [os.path.join(root, name) - for name in [ - name for (root, dirs, files) in os.walk(ca_dir) - for name in files - ] - if name.endswith('.pem')] - return certs - - def _fetch_and_store_cert(self, server, port, path): - ''' - Grabs a certificate from a server and writes it to - a given path. - ''' - try: - cert = ssl.get_server_certificate((server, port), - ssl_version=ssl.PROTOCOL_TLSv1) - except Exception as e: - raise cfg.Error(_('Could not retrieve initial ' - 'certificate from controller %(server)s. ' - 'Error details: %(error)s') % - {'server': server, 'error': e}) - - LOG.warning(_LW("Storing to certificate for host %(server)s " - "at %(path)s"), {'server': server, - 'path': path}) - self._file_put_contents(path, cert) - - return cert - - def _file_put_contents(self, path, contents): - # Simple method to write to file. - # Created for easy Mocking - with open(path, 'w') as handle: - handle.write(contents) - - def server_failure(self, resp, ignore_codes=[]): - """Define failure codes as required. - - Note: We assume 301-303 is a failure, and try the next server in - the server pool. - """ - return (resp[0] in FAILURE_CODES and resp[0] not in ignore_codes) - - def action_success(self, resp): - """Defining success codes as required. - - Note: We assume any valid 2xx as being successful response. - """ - return resp[0] in SUCCESS_CODES - - def rest_call(self, action, resource, data, headers, ignore_codes, - timeout=False): - context = self.get_context_ref() - if context: - # include the requesting context information if available - cdict = context.to_dict() - # remove the auth token so it's not present in debug logs on the - # backend controller - cdict.pop('auth_token', None) - headers[REQ_CONTEXT_HEADER] = jsonutils.dumps(cdict) - hash_handler = cdb.HashHandler() - good_first = sorted(self.servers, key=lambda x: x.failed) - first_response = None - for active_server in good_first: - for x in range(HTTP_SERVICE_UNAVAILABLE_RETRY_COUNT + 1): - ret = active_server.rest_call(action, resource, data, headers, - timeout, - reconnect=self.always_reconnect, - hash_handler=hash_handler) - if ret[0] != httplib.SERVICE_UNAVAILABLE: - break - time.sleep(HTTP_SERVICE_UNAVAILABLE_RETRY_INTERVAL) - - # If inconsistent, do a full synchronization - if ret[0] == httplib.CONFLICT: - if not self.get_topo_function: - raise cfg.Error(_('Server requires synchronization, ' - 'but no topology function was defined.')) - data = self.get_topo_function(**self.get_topo_function_args) - active_server.rest_call('PUT', TOPOLOGY_PATH, data, - timeout=None) - # Store the first response as the error to be bubbled up to the - # user since it was a good server. Subsequent servers will most - # likely be cluster slaves and won't have a useful error for the - # user (e.g. 302 redirect to master) - if not first_response: - first_response = ret - if not self.server_failure(ret, ignore_codes): - active_server.failed = False - return ret - else: - LOG.error(_LE('ServerProxy: %(action)s failure for servers: ' - '%(server)r Response: %(response)s'), - {'action': action, - 'server': (active_server.server, - active_server.port), - 'response': ret[3]}) - LOG.error(_LE("ServerProxy: Error details: status=%(status)d, " - "reason=%(reason)r, ret=%(ret)s, data=%(data)r"), - {'status': ret[0], 'reason': ret[1], 'ret': ret[2], - 'data': ret[3]}) - active_server.failed = True - - # A failure on a delete means the object is gone from Neutron but not - # from the controller. Set the consistency hash to a bad value to - # trigger a sync on the next check. - # NOTE: The hash must have a comma in it otherwise it will be ignored - # by the backend. - if action == 'DELETE': - hash_handler.put_hash('INCONSISTENT,INCONSISTENT') - # All servers failed, reset server list and try again next time - LOG.error(_LE('ServerProxy: %(action)s failure for all servers: ' - '%(server)r'), - {'action': action, - 'server': tuple((s.server, - s.port) for s in self.servers)}) - return first_response - - def rest_action(self, action, resource, data='', errstr='%s', - ignore_codes=None, headers=None, timeout=False): - """ - Wrapper for rest_call that verifies success and raises a - RemoteRestError on failure with a provided error string - By default, 404 errors on DELETE calls are ignored because - they already do not exist on the backend. - """ - ignore_codes = ignore_codes or [] - headers = headers or {} - if not ignore_codes and action == 'DELETE': - ignore_codes = [404] - resp = self.rest_call(action, resource, data, headers, ignore_codes, - timeout) - if self.server_failure(resp, ignore_codes): - LOG.error(errstr, resp[2]) - raise RemoteRestError(reason=resp[2], status=resp[0]) - if resp[0] in ignore_codes: - LOG.info(_LI("NeutronRestProxyV2: Received and ignored error " - "code %(code)s on %(action)s action to resource " - "%(resource)s"), - {'code': resp[2], 'action': action, - 'resource': resource}) - return resp - - def rest_create_router(self, tenant_id, router): - resource = ROUTER_RESOURCE_PATH % tenant_id - data = {"router": router} - errstr = _("Unable to create remote router: %s") - self.rest_action('POST', resource, data, errstr) - - def rest_update_router(self, tenant_id, router, router_id): - resource = ROUTERS_PATH % (tenant_id, router_id) - data = {"router": router} - errstr = _("Unable to update remote router: %s") - self.rest_action('PUT', resource, data, errstr) - - def rest_delete_router(self, tenant_id, router_id): - resource = ROUTERS_PATH % (tenant_id, router_id) - errstr = _("Unable to delete remote router: %s") - self.rest_action('DELETE', resource, errstr=errstr) - - def rest_add_router_interface(self, tenant_id, router_id, intf_details): - resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id) - data = {"interface": intf_details} - errstr = _("Unable to add router interface: %s") - self.rest_action('POST', resource, data, errstr) - - def rest_remove_router_interface(self, tenant_id, router_id, interface_id): - resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id) - errstr = _("Unable to delete remote intf: %s") - self.rest_action('DELETE', resource, errstr=errstr) - - def rest_create_network(self, tenant_id, network): - resource = NET_RESOURCE_PATH % tenant_id - data = {"network": network} - errstr = _("Unable to create remote network: %s") - self.rest_action('POST', resource, data, errstr) - - def rest_update_network(self, tenant_id, net_id, network): - resource = NETWORKS_PATH % (tenant_id, net_id) - data = {"network": network} - errstr = _("Unable to update remote network: %s") - self.rest_action('PUT', resource, data, errstr) - - def rest_delete_network(self, tenant_id, net_id): - resource = NETWORKS_PATH % (tenant_id, net_id) - errstr = _("Unable to update remote network: %s") - self.rest_action('DELETE', resource, errstr=errstr) - - def rest_create_port(self, tenant_id, net_id, port): - resource = ATTACHMENT_PATH % (tenant_id, net_id, port["id"]) - data = {"port": port} - device_id = port.get("device_id") - if not port["mac_address"] or not device_id: - # controller only cares about ports attached to devices - LOG.warning(_LW("No device MAC attached to port %s. " - "Skipping notification to controller."), - port["id"]) - return - data["attachment"] = {"id": device_id, - "mac": port["mac_address"]} - errstr = _("Unable to create remote port: %s") - self.rest_action('PUT', resource, data, errstr) - - def rest_delete_port(self, tenant_id, network_id, port_id): - resource = ATTACHMENT_PATH % (tenant_id, network_id, port_id) - errstr = _("Unable to delete remote port: %s") - self.rest_action('DELETE', resource, errstr=errstr) - - def rest_update_port(self, tenant_id, net_id, port): - # Controller has no update operation for the port endpoint - # the create PUT method will replace - self.rest_create_port(tenant_id, net_id, port) - - def rest_create_floatingip(self, tenant_id, floatingip): - resource = FLOATINGIPS_PATH % (tenant_id, floatingip['id']) - errstr = _("Unable to create floating IP: %s") - self.rest_action('PUT', resource, floatingip, errstr=errstr) - - def rest_update_floatingip(self, tenant_id, floatingip, oldid): - resource = FLOATINGIPS_PATH % (tenant_id, oldid) - errstr = _("Unable to update floating IP: %s") - self.rest_action('PUT', resource, floatingip, errstr=errstr) - - def rest_delete_floatingip(self, tenant_id, oldid): - resource = FLOATINGIPS_PATH % (tenant_id, oldid) - errstr = _("Unable to delete floating IP: %s") - self.rest_action('DELETE', resource, errstr=errstr) - - def rest_get_switch(self, switch_id): - resource = SWITCHES_PATH % switch_id - errstr = _("Unable to retrieve switch: %s") - resp = self.rest_action('GET', resource, errstr=errstr, - ignore_codes=[404]) - # return None if switch not found, else return switch info - return None if resp[0] == 404 else resp[3] - - def _consistency_watchdog(self, polling_interval=60): - if 'consistency' not in self.get_capabilities(): - LOG.warning(_LW("Backend server(s) do not support automated " - "consitency checks.")) - return - if not polling_interval: - LOG.warning(_LW("Consistency watchdog disabled by polling " - "interval setting of %s."), polling_interval) - return - while True: - # If consistency is supported, all we have to do is make any - # rest call and the consistency header will be added. If it - # doesn't match, the backend will return a synchronization error - # that will be handled by the rest_action. - eventlet.sleep(polling_interval) - try: - self.rest_action('GET', HEALTH_PATH) - except Exception: - LOG.exception(_LE("Encountered an error checking controller " - "health.")) - - -class HTTPSConnectionWithValidation(httplib.HTTPSConnection): - - # If combined_cert is None, the connection will continue without - # any certificate validation. - combined_cert = None - - def connect(self): - sock = socket.create_connection((self.host, self.port), - self.timeout, self.source_address) - if self._tunnel_host: - self.sock = sock - self._tunnel() - - if self.combined_cert: - self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, - cert_reqs=ssl.CERT_REQUIRED, - ca_certs=self.combined_cert, - ssl_version=ssl.PROTOCOL_TLSv1) - else: - self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, - cert_reqs=ssl.CERT_NONE, - ssl_version=ssl.PROTOCOL_TLSv1) diff --git a/neutron/plugins/bigswitch/tests/__init__.py b/neutron/plugins/bigswitch/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/neutron/plugins/bigswitch/tests/test_server.py b/neutron/plugins/bigswitch/tests/test_server.py deleted file mode 100755 index a8778e13f91..00000000000 --- a/neutron/plugins/bigswitch/tests/test_server.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python -# Copyright 2012, 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. - -"""Test server mocking a REST based network ctrl. - -Used for NeutronRestProxy tests -""" -from __future__ import print_function - -import re - -from oslo_serialization import jsonutils -from six import moves -from wsgiref import simple_server - - -class TestNetworkCtrl(object): - - def __init__(self, host='', port=8000, - default_status='404 Not Found', - default_response='404 Not Found', - debug=False): - self.host = host - self.port = port - self.default_status = default_status - self.default_response = default_response - self.debug = debug - self.debug_env = False - self.debug_resp = False - self.matches = [] - - def match(self, prior, method_regexp, uri_regexp, handler, data=None, - multi=True): - """Add to the list of expected inputs. - - The incoming request is matched in the order of priority. For same - priority, match the oldest match request first. - - :param prior: integer priority of this match (e.g. 100) - :param method_regexp: regexp to match method (e.g. 'PUT|POST') - :param uri_regexp: regexp to match uri (e.g. '/quantum/v?.?/') - :param handler: function with signature: - lambda(method, uri, body, **kwargs) : status, body - where - - method: HTTP method for this request - - uri: URI for this HTTP request - - body: body of this HTTP request - - kwargs are: - - data: data object that was in the match call - - node: TestNetworkCtrl object itself - - id: offset of the matching tuple - and return values is: - (status, body) where: - - status: HTTP resp status (e.g. '200 OK'). - If None, use default_status - - body: HTTP resp body. If None, use '' - """ - assert int(prior) == prior, 'Priority should an integer be >= 0' - assert prior >= 0, 'Priority should an integer be >= 0' - - lo, hi = 0, len(self.matches) - while lo < hi: - mid = (lo + hi) // 2 - if prior < self.matches[mid][0]: - hi = mid - else: - lo = mid + 1 - self.matches.insert(lo, (prior, method_regexp, uri_regexp, handler, - data, multi)) - - def remove_id(self, id_): - assert id_ >= 0, 'remove_id: id < 0' - assert id_ <= len(self.matches), 'remove_id: id > len()' - self.matches.pop(id_) - - def request_handler(self, method, uri, body): - retstatus = self.default_status - retbody = self.default_response - for i in moves.xrange(len(self.matches)): - (unused_prior, method_regexp, uri_regexp, handler, data, - multi) = self.matches[i] - if re.match(method_regexp, method) and re.match(uri_regexp, uri): - kwargs = { - 'data': data, - 'node': self, - 'id': i, - } - retstatus, retbody = handler(method, uri, body, **kwargs) - if multi is False: - self.remove_id(i) - break - if retbody is None: - retbody = '' - return (retstatus, retbody) - - def server(self): - def app(environ, start_response): - uri = environ['PATH_INFO'] - method = environ['REQUEST_METHOD'] - headers = [('Content-type', 'text/json')] - content_len_str = environ['CONTENT_LENGTH'] - - content_len = 0 - request_data = None - if content_len_str: - content_len = int(content_len_str) - request_data = environ.get('wsgi.input').read(content_len) - if request_data: - try: - request_data = jsonutils.loads(request_data) - except Exception: - # OK for it not to be json! Ignore it - pass - - if self.debug: - print('\n') - if self.debug_env: - print('environ:') - for (key, value) in sorted(environ.iteritems()): - print(' %16s : %s' % (key, value)) - - print('%s %s' % (method, uri)) - if request_data: - print('%s' % - jsonutils.dumps( - request_data, sort_keys=True, indent=4)) - - status, body = self.request_handler(method, uri, None) - body_data = None - if body: - try: - body_data = jsonutils.loads(body) - except Exception: - # OK for it not to be json! Ignore it - pass - - start_response(status, headers) - if self.debug: - if self.debug_env: - print('%s: %s' % ('Response', - jsonutils.dumps( - body_data, sort_keys=True, indent=4))) - return body - return simple_server.make_server(self.host, self.port, app) - - def run(self): - print("Serving on port %d ..." % self.port) - try: - self.server().serve_forever() - except KeyboardInterrupt: - pass - - -if __name__ == "__main__": - import sys - - port = 8899 - if len(sys.argv) > 1: - port = int(sys.argv[1]) - - debug = False - if len(sys.argv) > 2: - if sys.argv[2].lower() in ['debug', 'true']: - debug = True - - ctrl = TestNetworkCtrl(port=port, - default_status='200 OK', - default_response='{"status":"200 OK"}', - debug=debug) - ctrl.match(100, 'GET', '/test', - lambda m, u, b, **k: ('200 OK', '["200 OK"]')) - ctrl.run() diff --git a/neutron/plugins/bigswitch/vcsversion.py b/neutron/plugins/bigswitch/vcsversion.py deleted file mode 100644 index febe0e5429b..00000000000 --- a/neutron/plugins/bigswitch/vcsversion.py +++ /dev/null @@ -1,22 +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. -version_info = {'branch_nick': u'neutron/trunk', - 'revision_id': u'1', - 'revno': 0} - - -NEUTRONRESTPROXY_VERSION = ['2013', '1', None] - - -FINAL = False # This becomes true at Release Candidate time diff --git a/neutron/plugins/bigswitch/version.py b/neutron/plugins/bigswitch/version.py deleted file mode 100755 index 35904ae1f3e..00000000000 --- a/neutron/plugins/bigswitch/version.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -# Copyright 2012 OpenStack Foundation -# Copyright 2012, Big Switch Networks, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Determine version of NeutronRestProxy plugin""" -from __future__ import print_function - -from neutron.plugins.bigswitch import vcsversion - - -YEAR, COUNT, REVISION = vcsversion.NEUTRONRESTPROXY_VERSION - - -def canonical_version_string(): - return '.'.join(filter(None, - vcsversion.NEUTRONRESTPROXY_VERSION)) - - -def version_string(): - if vcsversion.FINAL: - return canonical_version_string() - else: - return '%s-dev' % (canonical_version_string(),) - - -def vcs_version_string(): - return "%s:%s" % (vcsversion.version_info['branch_nick'], - vcsversion.version_info['revision_id']) - - -def version_string_with_vcs(): - return "%s-%s" % (canonical_version_string(), vcs_version_string()) - - -if __name__ == "__main__": - print(version_string_with_vcs()) diff --git a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py index dbee04c519e..7a07acdd6e9 100644 --- a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py +++ b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py @@ -12,196 +12,7 @@ # 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 datetime -import httplib - -import eventlet -from oslo_config import cfg -from oslo_utils import excutils -from oslo_utils import timeutils - -from neutron import context as ctx -from neutron.extensions import portbindings -from neutron.i18n import _LE, _LW -from neutron.openstack.common import log -from neutron.plugins.bigswitch import config as pl_config -from neutron.plugins.bigswitch import plugin -from neutron.plugins.bigswitch import servermanager -from neutron.plugins.common import constants as pconst -from neutron.plugins.ml2 import driver_api as api +from bsnstacklib.plugins.ml2.drivers.mech_bigswitch import driver -EXTERNAL_PORT_OWNER = 'neutron:external_port' -LOG = log.getLogger(__name__) -put_context_in_serverpool = plugin.put_context_in_serverpool - -# time in seconds to maintain existence of vswitch response -CACHE_VSWITCH_TIME = 60 - - -class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, - api.MechanismDriver): - - """Mechanism Driver for Big Switch Networks Controller. - - This driver relays the network create, update, delete - operations to the Big Switch Controller. - """ - - def initialize(self): - LOG.debug('Initializing driver') - - # register plugin config opts - pl_config.register_config() - self.evpool = eventlet.GreenPool(cfg.CONF.RESTPROXY.thread_pool_size) - - # init network ctrl connections - self.servers = servermanager.ServerPool() - self.servers.get_topo_function = self._get_all_data - self.servers.get_topo_function_args = {'get_ports': True, - 'get_floating_ips': False, - 'get_routers': False} - self.segmentation_types = ', '.join(cfg.CONF.ml2.type_drivers) - # Track hosts running IVS to avoid excessive calls to the backend - self.ivs_host_cache = {} - - LOG.debug("Initialization done") - - @put_context_in_serverpool - def create_network_postcommit(self, context): - # create network on the network controller - self._send_create_network(context.current) - - @put_context_in_serverpool - def update_network_postcommit(self, context): - # update network on the network controller - self._send_update_network(context.current) - - @put_context_in_serverpool - def delete_network_postcommit(self, context): - # delete network on the network controller - self._send_delete_network(context.current) - - @put_context_in_serverpool - def create_port_postcommit(self, context): - # create port on the network controller - port = self._prepare_port_for_controller(context) - if port: - self.async_port_create(port["network"]["tenant_id"], - port["network"]["id"], port) - - @put_context_in_serverpool - def update_port_postcommit(self, context): - # update port on the network controller - port = self._prepare_port_for_controller(context) - if port: - try: - self.async_port_create(port["network"]["tenant_id"], - port["network"]["id"], port) - except servermanager.RemoteRestError as e: - with excutils.save_and_reraise_exception() as ctxt: - if (cfg.CONF.RESTPROXY.auto_sync_on_failure and - e.status == httplib.NOT_FOUND and - servermanager.NXNETWORK in e.reason): - ctxt.reraise = False - LOG.error(_LE("Inconsistency with backend controller " - "triggering full synchronization.")) - topoargs = self.servers.get_topo_function_args - self._send_all_data( - send_ports=topoargs['get_ports'], - send_floating_ips=topoargs['get_floating_ips'], - send_routers=topoargs['get_routers'], - triggered_by_tenant=port["network"]["tenant_id"] - ) - - @put_context_in_serverpool - def delete_port_postcommit(self, context): - # delete port on the network controller - port = context.current - net = context.network.current - self.servers.rest_delete_port(net["tenant_id"], net["id"], port['id']) - - def _prepare_port_for_controller(self, context): - # make a copy so the context isn't changed for other drivers - port = copy.deepcopy(context.current) - net = context.network.current - port['network'] = net - port['bound_segment'] = context.top_bound_segment - actx = ctx.get_admin_context() - prepped_port = self._extend_port_dict_binding(actx, port) - prepped_port = self._map_state_and_status(prepped_port) - if (portbindings.HOST_ID not in prepped_port or - prepped_port[portbindings.HOST_ID] == ''): - LOG.warning(_LW("Ignoring port notification to controller because " - "of missing host ID.")) - # in ML2, controller doesn't care about ports without - # the host_id set - return False - return prepped_port - - def bind_port(self, context): - """Marks ports as bound. - - Binds external ports and IVS ports. - Fabric configuration will occur on the subsequent port update. - Currently only vlan segments are supported. - """ - if context.current['device_owner'] == EXTERNAL_PORT_OWNER: - # TODO(kevinbenton): check controller to see if the port exists - # so this driver can be run in parallel with others that add - # support for external port bindings - for segment in context.segments_to_bind: - if segment[api.NETWORK_TYPE] == pconst.TYPE_VLAN: - context.set_binding( - segment[api.ID], portbindings.VIF_TYPE_BRIDGE, - {portbindings.CAP_PORT_FILTER: False, - portbindings.OVS_HYBRID_PLUG: False}) - return - - # IVS hosts will have a vswitch with the same name as the hostname - if self.does_vswitch_exist(context.host): - for segment in context.segments_to_bind: - if segment[api.NETWORK_TYPE] == pconst.TYPE_VLAN: - context.set_binding( - segment[api.ID], portbindings.VIF_TYPE_IVS, - {portbindings.CAP_PORT_FILTER: True, - portbindings.OVS_HYBRID_PLUG: False}) - - def does_vswitch_exist(self, host): - """Check if Indigo vswitch exists with the given hostname. - - Returns True if switch exists on backend. - Returns False if switch does not exist. - Returns None if backend could not be reached. - Caches response from backend. - """ - try: - return self._get_cached_vswitch_existence(host) - except ValueError: - # cache was empty for that switch or expired - pass - - try: - exists = bool(self.servers.rest_get_switch(host)) - except servermanager.RemoteRestError: - # Connectivity or internal server error. Skip cache to try again on - # next binding attempt - return - self.ivs_host_cache[host] = { - 'timestamp': datetime.datetime.now(), - 'exists': exists - } - return exists - - def _get_cached_vswitch_existence(self, host): - """Returns cached existence. Old and non-cached raise ValueError.""" - entry = self.ivs_host_cache.get(host) - if not entry: - raise ValueError(_('No cache entry for host %s') % host) - diff = timeutils.delta_seconds(entry['timestamp'], - datetime.datetime.now()) - if diff > CACHE_VSWITCH_TIME: - self.ivs_host_cache.pop(host) - raise ValueError(_('Expired cache entry for host %s') % host) - return entry['exists'] +BigSwitchMechanismDriver = driver.BigSwitchMechanismDriver diff --git a/neutron/plugins/ml2/drivers/mech_bigswitch/requirements.txt b/neutron/plugins/ml2/drivers/mech_bigswitch/requirements.txt new file mode 100644 index 00000000000..ecbbce6fe21 --- /dev/null +++ b/neutron/plugins/ml2/drivers/mech_bigswitch/requirements.txt @@ -0,0 +1 @@ +bsnstacklib>=2015.1,<2015.2 diff --git a/neutron/tests/unit/bigswitch/__init__.py b/neutron/tests/unit/bigswitch/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/neutron/tests/unit/bigswitch/etc/restproxy.ini.test b/neutron/tests/unit/bigswitch/etc/restproxy.ini.test deleted file mode 100644 index b6ac26e7e06..00000000000 --- a/neutron/tests/unit/bigswitch/etc/restproxy.ini.test +++ /dev/null @@ -1,46 +0,0 @@ -# Test config file for quantum-proxy-plugin. -[DEFAULT] -service_plugins = bigswitch_l3 - -[database] -# This line MUST be changed to actually run the plugin. -# Example: -# connection = mysql://root:pass@127.0.0.1:3306/restproxy_quantum -# Replace 127.0.0.1 above with the IP address of the database used by the -# main quantum server. (Leave it as is if the database runs on this host.) -connection = sqlite:// -# Database reconnection retry times - in event connectivity is lost -# set to -1 implies an infinite retry count -# max_retries = 10 -# Database reconnection interval in seconds - in event connectivity is lost -retry_interval = 2 - -[restproxy] -# All configuration for this plugin is in section '[restproxy]' -# -# The following parameters are supported: -# servers : [,]* (Error if not set) -# serverauth : (default: no auth) -# serverssl : True | False (default: False) -# -servers=localhost:9000,localhost:8899 -serverssl=False -#serverauth=username:password - -[nova] -# Specify the VIF_TYPE that will be controlled on the Nova compute instances -# options: ivs or ovs -# default: ovs -vif_type = ovs -# Overrides for vif types based on nova compute node host IDs -# Comma separated list of host IDs to fix to a specific VIF type -node_override_vif_ivs = ivshost - -[router] -# Specify the default router rules installed in newly created tenant routers -# Specify multiple times for multiple rules -# Use an * to specify default for all tenants -# Default is any any allow for all tenants -#tenant_default_router_rule=*:any:any:permit -# Maximum number of rules that a single router may have -max_router_rules=200 diff --git a/neutron/tests/unit/bigswitch/etc/ssl/ca_certs/README b/neutron/tests/unit/bigswitch/etc/ssl/ca_certs/README deleted file mode 100644 index 91779e39df5..00000000000 --- a/neutron/tests/unit/bigswitch/etc/ssl/ca_certs/README +++ /dev/null @@ -1,2 +0,0 @@ -ca_certs directory for SSL unit tests -No files will be generated here, but it should exist for the tests diff --git a/neutron/tests/unit/bigswitch/etc/ssl/combined/README b/neutron/tests/unit/bigswitch/etc/ssl/combined/README deleted file mode 100644 index 9f9922fd50a..00000000000 --- a/neutron/tests/unit/bigswitch/etc/ssl/combined/README +++ /dev/null @@ -1,2 +0,0 @@ -combined certificates directory for SSL unit tests -No files will be created here, but it should exist for the tests diff --git a/neutron/tests/unit/bigswitch/etc/ssl/host_certs/README b/neutron/tests/unit/bigswitch/etc/ssl/host_certs/README deleted file mode 100644 index 0eaec67ce51..00000000000 --- a/neutron/tests/unit/bigswitch/etc/ssl/host_certs/README +++ /dev/null @@ -1,2 +0,0 @@ -host_certs directory for SSL unit tests -No files will be created here, but it should exist for the tests diff --git a/neutron/tests/unit/bigswitch/fake_server.py b/neutron/tests/unit/bigswitch/fake_server.py deleted file mode 100644 index 40a7de846c1..00000000000 --- a/neutron/tests/unit/bigswitch/fake_server.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright 2013 Big Switch Networks, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_serialization import jsonutils - -from neutron.openstack.common import log as logging -from neutron.plugins.bigswitch import servermanager - -LOG = logging.getLogger(__name__) - - -class HTTPResponseMock(object): - status = 200 - reason = 'OK' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False): - pass - - def read(self): - return "{'status': '200 OK'}" - - def getheader(self, header): - return None - - -class HTTPResponseMock404(HTTPResponseMock): - status = 404 - reason = 'Not Found' - - def read(self): - return "{'status': '%s 404 Not Found'}" % servermanager.NXNETWORK - - -class HTTPResponseMock500(HTTPResponseMock): - status = 500 - reason = 'Internal Server Error' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False, errmsg='500 Internal Server Error'): - self.errmsg = errmsg - - def read(self): - return "{'status': '%s'}" % self.errmsg - - -class HTTPConnectionMock(object): - - def __init__(self, server, port, timeout): - self.response = None - self.broken = False - # Port 9000 is the broken server - if port == 9000: - self.broken = True - errmsg = "This server is broken, please try another" - self.response = HTTPResponseMock500(None, errmsg=errmsg) - - def request(self, action, uri, body, headers): - LOG.debug("Request: action=%(action)s, uri=%(uri)r, " - "body=%(body)s, headers=%(headers)s", - {'action': action, 'uri': uri, - 'body': body, 'headers': headers}) - if self.broken and "ExceptOnBadServer" in uri: - raise Exception("Broken server got an unexpected request") - if self.response: - return - - # detachment may return 404 and plugin shouldn't die - if uri.endswith('attachment') and action == 'DELETE': - self.response = HTTPResponseMock404(None) - else: - self.response = HTTPResponseMock(None) - - # Port creations/updates must contain binding information - if ('port' in uri and 'attachment' not in uri - and 'binding' not in body and action in ('POST', 'PUT')): - errmsg = "Port binding info missing in port request '%s'" % body - self.response = HTTPResponseMock500(None, errmsg=errmsg) - return - - return - - def getresponse(self): - return self.response - - def close(self): - pass - - -class HTTPConnectionMock404(HTTPConnectionMock): - - def __init__(self, server, port, timeout): - self.response = HTTPResponseMock404(None) - self.broken = True - - -class HTTPConnectionMock500(HTTPConnectionMock): - - def __init__(self, server, port, timeout): - self.response = HTTPResponseMock500(None) - self.broken = True - - -class VerifyMultiTenantFloatingIP(HTTPConnectionMock): - - def request(self, action, uri, body, headers): - # Only handle network update requests - if 'network' in uri and 'tenant' in uri and 'ports' not in uri: - req = jsonutils.loads(body) - if 'network' not in req or 'floatingips' not in req['network']: - msg = _("No floating IPs in request" - "uri=%(uri)s, body=%(body)s") % {'uri': uri, - 'body': body} - raise Exception(msg) - distinct_tenants = [] - for flip in req['network']['floatingips']: - if flip['tenant_id'] not in distinct_tenants: - distinct_tenants.append(flip['tenant_id']) - if len(distinct_tenants) < 2: - msg = _("Expected floating IPs from multiple tenants." - "uri=%(uri)s, body=%(body)s") % {'uri': uri, - 'body': body} - raise Exception(msg) - super(VerifyMultiTenantFloatingIP, - self).request(action, uri, body, headers) - - -class HTTPSMockBase(HTTPConnectionMock): - expected_cert = '' - combined_cert = None - - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=None, source_address=None): - self.host = host - super(HTTPSMockBase, self).__init__(host, port, timeout) - - def request(self, method, url, body=None, headers={}): - self.connect() - super(HTTPSMockBase, self).request(method, url, body, headers) - - -class HTTPSNoValidation(HTTPSMockBase): - - def connect(self): - if self.combined_cert: - raise Exception('combined_cert set on NoValidation') - - -class HTTPSCAValidation(HTTPSMockBase): - expected_cert = 'DUMMYCERTIFICATEAUTHORITY' - - def connect(self): - contents = get_cert_contents(self.combined_cert) - if self.expected_cert not in contents: - raise Exception('No dummy CA cert in cert_file') - - -class HTTPSHostValidation(HTTPSMockBase): - expected_cert = 'DUMMYCERTFORHOST%s' - - def connect(self): - contents = get_cert_contents(self.combined_cert) - expected = self.expected_cert % self.host - if expected not in contents: - raise Exception(_('No host cert for %(server)s in cert %(cert)s'), - {'server': self.host, 'cert': contents}) - - -def get_cert_contents(path): - raise Exception('METHOD MUST BE MOCKED FOR TEST') diff --git a/neutron/tests/unit/bigswitch/test_agent_scheduler.py b/neutron/tests/unit/bigswitch/test_agent_scheduler.py deleted file mode 100644 index 6e775c69f5b..00000000000 --- a/neutron/tests/unit/bigswitch/test_agent_scheduler.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2013 Big Switch Networks, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from neutron.tests.unit.bigswitch import test_base -from neutron.tests.unit.openvswitch import test_agent_scheduler - - -class BigSwitchDhcpAgentNotifierTestCase( - test_agent_scheduler.OvsDhcpAgentNotifierTestCase, - test_base.BigSwitchTestBase): - - plugin_str = ('%s.NeutronRestProxyV2' % - test_base.RESTPROXY_PKG_PATH) - - def setUp(self): - self.setup_config_files() - self.setup_patches() - super(BigSwitchDhcpAgentNotifierTestCase, self).setUp() - self.setup_db() - self.startHttpPatch() diff --git a/neutron/tests/unit/bigswitch/test_base.py b/neutron/tests/unit/bigswitch/test_base.py deleted file mode 100644 index a1afb97fc18..00000000000 --- a/neutron/tests/unit/bigswitch/test_base.py +++ /dev/null @@ -1,84 +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 os - -import mock -from oslo_config import cfg - -import neutron.common.test_lib as test_lib -from neutron.plugins.bigswitch import config -from neutron.plugins.bigswitch.db import consistency_db -from neutron.tests.unit.bigswitch import fake_server - - -RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin' -L3_RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.l3_router_plugin' -NOTIFIER = 'neutron.plugins.bigswitch.plugin.AgentNotifierApi' -CERTFETCH = 'neutron.plugins.bigswitch.servermanager.ServerPool._fetch_cert' -SERVER_MANAGER = 'neutron.plugins.bigswitch.servermanager' -HTTPCON = 'neutron.plugins.bigswitch.servermanager.httplib.HTTPConnection' -SPAWN = 'neutron.plugins.bigswitch.plugin.eventlet.GreenPool.spawn_n' -CWATCH = SERVER_MANAGER + '.ServerPool._consistency_watchdog' - - -class BigSwitchTestBase(object): - - _plugin_name = ('%s.NeutronRestProxyV2' % RESTPROXY_PKG_PATH) - _l3_plugin_name = ('%s.L3RestProxy' % L3_RESTPROXY_PKG_PATH) - - def setup_config_files(self): - etc_path = os.path.join(os.path.dirname(__file__), 'etc') - test_lib.test_config['config_files'] = [os.path.join(etc_path, - 'restproxy.ini.test')] - self.addCleanup(cfg.CONF.reset) - self.addCleanup(consistency_db.clear_db) - config.register_config() - # Only try SSL on SSL tests - cfg.CONF.set_override('server_ssl', False, 'RESTPROXY') - cfg.CONF.set_override('ssl_cert_directory', - os.path.join(etc_path, 'ssl'), 'RESTPROXY') - # The mock interferes with HTTP(S) connection caching - cfg.CONF.set_override('cache_connections', False, 'RESTPROXY') - cfg.CONF.set_override('service_plugins', ['bigswitch_l3']) - cfg.CONF.set_override('add_meta_server_route', False, 'RESTPROXY') - - def setup_patches(self): - self.dhcp_periodic_p = mock.patch( - 'neutron.db.agentschedulers_db.DhcpAgentSchedulerDbMixin.' - 'start_periodic_dhcp_agent_status_check') - self.patched_dhcp_periodic = self.dhcp_periodic_p.start() - self.plugin_notifier_p = mock.patch(NOTIFIER) - # prevent any greenthreads from spawning - self.spawn_p = mock.patch(SPAWN, new=lambda *args, **kwargs: None) - # prevent the consistency watchdog from starting - self.watch_p = mock.patch(CWATCH, new=lambda *args, **kwargs: None) - # disable exception log to prevent json parse error from showing - self.log_exc_p = mock.patch(SERVER_MANAGER + ".LOG.exception", - new=lambda *args, **kwargs: None) - self.log_exc_p.start() - self.plugin_notifier_p.start() - self.spawn_p.start() - self.watch_p.start() - - def startHttpPatch(self): - self.httpPatch = mock.patch(HTTPCON, - new=fake_server.HTTPConnectionMock) - self.httpPatch.start() - - def setup_db(self): - # setup the db engine and models for the consistency db - consistency_db.setup_db() diff --git a/neutron/tests/unit/bigswitch/test_capabilities.py b/neutron/tests/unit/bigswitch/test_capabilities.py deleted file mode 100644 index a164b3da60f..00000000000 --- a/neutron/tests/unit/bigswitch/test_capabilities.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2014 Big Switch Networks, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import contextlib -import mock - -from neutron.tests.unit.bigswitch import test_router_db - -PLUGIN = 'neutron.plugins.bigswitch.plugin' -SERVERMANAGER = PLUGIN + '.servermanager' -SERVERPOOL = SERVERMANAGER + '.ServerPool' -SERVERRESTCALL = SERVERMANAGER + '.ServerProxy.rest_call' -HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection' - - -class CapabilitiesTests(test_router_db.RouterDBTestBase): - - def test_floating_ip_capability(self): - with contextlib.nested( - mock.patch(SERVERRESTCALL, - return_value=(200, None, '["floatingip"]', None)), - mock.patch(SERVERPOOL + '.rest_create_floatingip', - return_value=(200, None, None, None)), - mock.patch(SERVERPOOL + '.rest_delete_floatingip', - return_value=(200, None, None, None)) - ) as (mock_rest, mock_create, mock_delete): - with self.floatingip_with_assoc() as fip: - pass - mock_create.assert_has_calls( - [mock.call(fip['floatingip']['tenant_id'], fip['floatingip'])] - ) - mock_delete.assert_has_calls( - [mock.call(fip['floatingip']['tenant_id'], - fip['floatingip']['id'])] - ) - - def test_floating_ip_capability_neg(self): - with contextlib.nested( - mock.patch(SERVERRESTCALL, - return_value=(200, None, '[""]', None)), - mock.patch(SERVERPOOL + '.rest_update_network', - return_value=(200, None, None, None)) - ) as (mock_rest, mock_netupdate): - with self.floatingip_with_assoc() as fip: - pass - updates = [call[0][2]['floatingips'] - for call in mock_netupdate.call_args_list] - all_floats = [f['floating_ip_address'] - for floats in updates for f in floats] - self.assertIn(fip['floatingip']['floating_ip_address'], all_floats) - - def test_keep_alive_capability(self): - with mock.patch( - SERVERRESTCALL, return_value=(200, None, '["keep-alive"]', None) - ): - # perform a task to cause capabilities to be retrieved - with self.floatingip_with_assoc(): - pass - # stop default HTTP patch since we need a magicmock - self.httpPatch.stop() - # now mock HTTP class instead of REST so we can see headers - conmock = mock.patch(HTTPCON).start() - instance = conmock.return_value - instance.getresponse.return_value.getheader.return_value = 'HASHHEADER' - with self.network(): - callheaders = instance.request.mock_calls[0][1][3] - self.assertIn('Connection', callheaders) - self.assertEqual(callheaders['Connection'], 'keep-alive') diff --git a/neutron/tests/unit/bigswitch/test_restproxy_agent.py b/neutron/tests/unit/bigswitch/test_restproxy_agent.py deleted file mode 100644 index 665d0f089cb..00000000000 --- a/neutron/tests/unit/bigswitch/test_restproxy_agent.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright 2014 Big Switch Networks, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import contextlib - -import mock -from oslo_utils import importutils - -from neutron.tests import base - -OVSBRIDGE = 'neutron.agent.linux.ovs_lib.OVSBridge' -PLUGINAPI = 'neutron.agent.rpc.PluginApi' -CONTEXT = 'neutron.context' -CONSUMERCREATE = 'neutron.agent.rpc.create_consumers' -SGRPC = 'neutron.agent.securitygroups_rpc' -SGAGENT = 'neutron.agent.securitygroups_rpc.SecurityGroupAgentRpc' -AGENTMOD = 'neutron.plugins.bigswitch.agent.restproxy_agent' -NEUTRONCFG = 'neutron.common.config' -PLCONFIG = 'neutron.plugins.bigswitch.config' - - -class BaseAgentTestCase(base.BaseTestCase): - - def setUp(self): - super(BaseAgentTestCase, self).setUp() - self.mod_agent = importutils.import_module(AGENTMOD) - - -class TestRestProxyAgentOVS(BaseAgentTestCase): - def setUp(self): - super(TestRestProxyAgentOVS, self).setUp() - self.plapi = mock.patch(PLUGINAPI).start() - self.ovsbridge = mock.patch(OVSBRIDGE).start() - self.context = mock.patch(CONTEXT).start() - self.rpc = mock.patch(CONSUMERCREATE).start() - self.sg_agent = mock.patch(SGAGENT).start() - self.sg_rpc = mock.patch(SGRPC).start() - - def mock_agent(self): - mock_context = mock.Mock(return_value='abc') - self.context.get_admin_context_without_session = mock_context - return self.mod_agent.RestProxyAgent('int-br', 2) - - def mock_port_update(self, **kwargs): - agent = self.mock_agent() - agent.port_update(mock.Mock(), **kwargs) - - def test_port_update(self): - port = {'id': 1, 'security_groups': 'default'} - - with mock.patch.object(self.ovsbridge.return_value, - 'get_vif_port_by_id', - return_value=1) as get_vif: - self.mock_port_update(port=port) - - get_vif.assert_called_once_with(1) - self.sg_agent.assert_has_calls([ - mock.call().refresh_firewall() - ]) - - def test_port_update_not_vifport(self): - port = {'id': 1, 'security_groups': 'default'} - - with mock.patch.object(self.ovsbridge.return_value, - 'get_vif_port_by_id', - return_value=0) as get_vif: - self.mock_port_update(port=port) - - get_vif.assert_called_once_with(1) - self.assertFalse(self.sg_agent.return_value.refresh_firewall.called) - - def test_port_update_without_secgroup(self): - port = {'id': 1} - - with mock.patch.object(self.ovsbridge.return_value, - 'get_vif_port_by_id', - return_value=1) as get_vif: - self.mock_port_update(port=port) - - get_vif.assert_called_once_with(1) - self.assertFalse(self.sg_agent.return_value.refresh_firewall.called) - - def mock_update_ports(self, vif_port_set=None, registered_ports=None): - with mock.patch.object(self.ovsbridge.return_value, - 'get_vif_port_set', - return_value=vif_port_set): - agent = self.mock_agent() - return agent._update_ports(registered_ports) - - def test_update_ports_unchanged(self): - self.assertIsNone(self.mock_update_ports()) - - def test_update_ports_changed(self): - vif_port_set = set([1, 3]) - registered_ports = set([1, 2]) - expected = dict(current=vif_port_set, - added=set([3]), - removed=set([2])) - - actual = self.mock_update_ports(vif_port_set, registered_ports) - - self.assertEqual(expected, actual) - - def mock_process_devices_filter(self, port_info): - agent = self.mock_agent() - agent._process_devices_filter(port_info) - - def test_process_devices_filter_add(self): - port_info = {'added': 1} - - self.mock_process_devices_filter(port_info) - - self.sg_agent.assert_has_calls([ - mock.call().prepare_devices_filter(1) - ]) - - def test_process_devices_filter_remove(self): - port_info = {'removed': 2} - - self.mock_process_devices_filter(port_info) - - self.sg_agent.assert_has_calls([ - mock.call().remove_devices_filter(2) - ]) - - def test_process_devices_filter_both(self): - port_info = {'added': 1, 'removed': 2} - - self.mock_process_devices_filter(port_info) - - self.sg_agent.assert_has_calls([ - mock.call().prepare_devices_filter(1), - mock.call().remove_devices_filter(2) - ]) - - def test_process_devices_filter_none(self): - port_info = {} - - self.mock_process_devices_filter(port_info) - - self.assertFalse( - self.sg_agent.return_value.prepare_devices_filter.called) - self.assertFalse( - self.sg_agent.return_value.remove_devices_filter.called) - - -class TestRestProxyAgent(BaseAgentTestCase): - def mock_main(self): - cfg_attrs = {'CONF.RESTPROXYAGENT.integration_bridge': 'integ_br', - 'CONF.RESTPROXYAGENT.polling_interval': 5, - 'CONF.RESTPROXYAGENT.virtual_switch_type': 'ovs'} - with contextlib.nested( - mock.patch(AGENTMOD + '.cfg', **cfg_attrs), - mock.patch(AGENTMOD + '.config.init'), - mock.patch(NEUTRONCFG), - mock.patch(PLCONFIG), - ) as (mock_conf, mock_init, mock_log_conf, mock_pluginconf): - self.mod_agent.main() - - mock_log_conf.assert_has_calls([ - mock.call(mock_conf), - ]) - - def test_main(self): - agent_attrs = {'daemon_loop.side_effect': SystemExit(0)} - with mock.patch(AGENTMOD + '.RestProxyAgent', - **agent_attrs) as mock_agent: - self.assertRaises(SystemExit, self.mock_main) - - mock_agent.assert_has_calls([ - mock.call('integ_br', 5, 'ovs'), - mock.call().daemon_loop() - ]) diff --git a/neutron/tests/unit/bigswitch/test_restproxy_plugin.py b/neutron/tests/unit/bigswitch/test_restproxy_plugin.py deleted file mode 100644 index 9f32e79ac50..00000000000 --- a/neutron/tests/unit/bigswitch/test_restproxy_plugin.py +++ /dev/null @@ -1,328 +0,0 @@ -# Copyright 2012 Big Switch Networks, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import contextlib -import mock -from oslo_config import cfg -import webob.exc - -from neutron.common import constants -from neutron import context -from neutron.extensions import portbindings -from neutron import manager -from neutron.tests.unit import _test_extension_portbindings as test_bindings -from neutron.tests.unit.bigswitch import fake_server -from neutron.tests.unit.bigswitch import test_base -from neutron.tests.unit import test_api_v2 -import neutron.tests.unit.test_db_plugin as test_plugin -import neutron.tests.unit.test_extension_allowedaddresspairs as test_addr_pair - -patch = mock.patch -HTTPCON = 'neutron.plugins.bigswitch.servermanager.httplib.HTTPConnection' - - -class BigSwitchProxyPluginV2TestCase(test_base.BigSwitchTestBase, - test_plugin.NeutronDbPluginV2TestCase): - - def setUp(self, plugin_name=None): - if hasattr(self, 'HAS_PORT_FILTER'): - cfg.CONF.set_override( - 'enable_security_group', self.HAS_PORT_FILTER, 'SECURITYGROUP') - self.setup_config_files() - self.setup_patches() - if plugin_name: - self._plugin_name = plugin_name - service_plugins = {'L3_ROUTER_NAT': self._l3_plugin_name} - super(BigSwitchProxyPluginV2TestCase, - self).setUp(self._plugin_name, service_plugins=service_plugins) - self.setup_db() - self.port_create_status = 'BUILD' - self.startHttpPatch() - - -class TestBigSwitchProxyBasicGet(test_plugin.TestBasicGet, - BigSwitchProxyPluginV2TestCase): - - pass - - -class TestBigSwitchProxyV2HTTPResponse(test_plugin.TestV2HTTPResponse, - BigSwitchProxyPluginV2TestCase): - - def test_failover_memory(self): - # first request causes failover so next shouldn't hit bad server - with self.network() as net: - kwargs = {'tenant_id': 'ExceptOnBadServer'} - with self.network(**kwargs) as net: - req = self.new_show_request('networks', net['network']['id']) - res = req.get_response(self.api) - self.assertEqual(res.status_int, 200) - - -class TestBigSwitchProxyPortsV2(test_plugin.TestPortsV2, - BigSwitchProxyPluginV2TestCase, - test_bindings.PortBindingsTestCase): - - VIF_TYPE = portbindings.VIF_TYPE_OVS - HAS_PORT_FILTER = False - - def setUp(self, plugin_name=None): - super(TestBigSwitchProxyPortsV2, - self).setUp(self._plugin_name) - - def test_get_ports_no_id(self): - with self.port(name='test'): - ports = manager.NeutronManager.get_plugin().get_ports( - context.get_admin_context(), fields=['name']) - self.assertEqual(['name'], ports[0].keys()) - - def test_router_port_status_active(self): - # router ports screw up port auto-deletion so it has to be - # disabled for this test - with self.network() as net: - with self.subnet(network=net) as sub: - with self.port( - subnet=sub, - device_owner=constants.DEVICE_OWNER_ROUTER_INTF - ) as port: - # router ports should be immediately active - self.assertEqual(port['port']['status'], 'ACTIVE') - - def test_update_port_status_build(self): - # normal ports go into the pending build state for async creation - with self.port() as port: - self.assertEqual(port['port']['status'], 'BUILD') - self.assertEqual(self.port_create_status, 'BUILD') - - def _get_ports(self, netid): - return self.deserialize('json', - self._list_ports('json', netid=netid))['ports'] - - def test_rollback_for_port_create(self): - plugin = manager.NeutronManager.get_plugin() - with self.subnet() as s: - # stop normal patch - self.httpPatch.stop() - # allow thread spawns for this test - self.spawn_p.stop() - kwargs = {'device_id': 'somedevid'} - # put in a broken 'server' - httpPatch = patch(HTTPCON, new=fake_server.HTTPConnectionMock500) - httpPatch.start() - with self.port(subnet=s, **kwargs): - # wait for async port create request to finish - plugin.evpool.waitall() - # put good 'server' back in - httpPatch.stop() - self.httpPatch.start() - ports = self._get_ports(s['subnet']['network_id']) - #failure to create should result in port in error state - self.assertEqual(ports[0]['status'], 'ERROR') - - def test_rollback_for_port_update(self): - with self.network() as n: - with self.port(network_id=n['network']['id'], - device_id='66') as port: - port = self._get_ports(n['network']['id'])[0] - data = {'port': {'name': 'aNewName', 'device_id': '99'}} - # stop normal patch - self.httpPatch.stop() - with patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self.new_update_request( - 'ports', data, port['id']).get_response(self.api) - self.httpPatch.start() - uport = self._get_ports(n['network']['id'])[0] - # name should have stayed the same - self.assertEqual(port['name'], uport['name']) - - def test_rollback_for_port_delete(self): - with self.network() as n: - with self.port(network_id=n['network']['id'], - device_id='somedevid') as port: - # stop normal patch - self.httpPatch.stop() - with patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self._delete( - 'ports', - port['port']['id'], - expected_code=webob.exc.HTTPInternalServerError.code) - self.httpPatch.start() - port = self._get_ports(n['network']['id'])[0] - self.assertEqual('BUILD', port['status']) - - def test_correct_shared_net_tenant_id(self): - # tenant_id in port requests should match network tenant_id instead - # of port tenant_id - def rest_port_op(self, ten_id, netid, port): - if ten_id != 'SHARED': - raise Exception('expecting tenant_id SHARED. got %s' % ten_id) - with self.network(tenant_id='SHARED', shared=True) as net: - with self.subnet(network=net) as sub: - pref = 'neutron.plugins.bigswitch.servermanager.ServerPool.%s' - tomock = [pref % 'rest_create_port', - pref % 'rest_update_port', - pref % 'rest_delete_port'] - patches = [patch(f, create=True, new=rest_port_op) - for f in tomock] - for restp in patches: - restp.start() - with self.port(subnet=sub, tenant_id='port-owner') as port: - data = {'port': {'binding:host_id': 'someotherhost', - 'device_id': 'override_dev'}} - req = self.new_update_request('ports', data, - port['port']['id']) - res = req.get_response(self.api) - self.assertEqual(res.status_int, 200) - - def test_create404_triggers_sync(self): - # allow async port thread for this patch - self.spawn_p.stop() - with contextlib.nested( - self.subnet(), - patch(HTTPCON, create=True, - new=fake_server.HTTPConnectionMock404), - patch(test_base.RESTPROXY_PKG_PATH - + '.NeutronRestProxyV2._send_all_data') - ) as (s, mock_http, mock_send_all): - with self.port(subnet=s, device_id='somedevid') as p: - # wait for the async port thread to finish - plugin = manager.NeutronManager.get_plugin() - plugin.evpool.waitall() - call = mock.call( - send_routers=True, send_ports=True, send_floating_ips=True, - triggered_by_tenant=p['port']['tenant_id'] - ) - mock_send_all.assert_has_calls([call]) - self.spawn_p.start() - - def test_port_vif_details_default(self): - kwargs = {'name': 'name', 'device_id': 'override_dev'} - with self.port(**kwargs) as port: - self.assertEqual(port['port']['binding:vif_type'], - portbindings.VIF_TYPE_OVS) - - def test_port_vif_details_override(self): - # ivshost is in the test config to override to IVS - kwargs = {'name': 'name', 'binding:host_id': 'ivshost', - 'device_id': 'override_dev', - 'arg_list': ('binding:host_id',)} - with self.port(**kwargs) as port: - self.assertEqual(port['port']['binding:vif_type'], - portbindings.VIF_TYPE_IVS) - self._delete('ports', port['port']['id']) - self._delete('networks', port['port']['network_id']) - kwargs = {'name': 'name2', 'binding:host_id': 'someotherhost', - 'device_id': 'other_dev'} - with self.port(**kwargs) as port: - self.assertEqual(port['port']['binding:vif_type'], self.VIF_TYPE) - - def test_port_move(self): - # ivshost is in the test config to override to IVS - kwargs = {'name': 'name', 'binding:host_id': 'ivshost', - 'device_id': 'override_dev'} - with self.port(**kwargs) as port: - data = {'port': {'binding:host_id': 'someotherhost', - 'device_id': 'override_dev'}} - req = self.new_update_request('ports', data, port['port']['id']) - res = self.deserialize(self.fmt, req.get_response(self.api)) - self.assertEqual(res['port']['binding:vif_type'], self.VIF_TYPE) - - -class TestVifDifferentDefault(BigSwitchProxyPluginV2TestCase): - - def setup_config_files(self): - super(TestVifDifferentDefault, self).setup_config_files() - cfg.CONF.set_override('vif_type', 'ivs', 'NOVA') - - def test_default_viftype(self): - with self.port() as port: - self.assertEqual(port['port']['binding:vif_type'], 'ivs') - - -class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2, - BigSwitchProxyPluginV2TestCase): - - def _get_networks(self, tenant_id): - ctx = context.Context('', tenant_id) - return manager.NeutronManager.get_plugin().get_networks(ctx) - - def test_rollback_on_network_create(self): - tid = test_api_v2._uuid() - kwargs = {'tenant_id': tid} - self.httpPatch.stop() - with patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self._create_network('json', 'netname', True, **kwargs) - self.httpPatch.start() - self.assertFalse(self._get_networks(tid)) - - def test_rollback_on_network_update(self): - with self.network() as n: - data = {'network': {'name': 'aNewName'}} - self.httpPatch.stop() - with patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self.new_update_request( - 'networks', data, n['network']['id'] - ).get_response(self.api) - self.httpPatch.start() - updatedn = self._get_networks(n['network']['tenant_id'])[0] - # name should have stayed the same due to failure - self.assertEqual(n['network']['name'], updatedn['name']) - - def test_rollback_on_network_delete(self): - with self.network() as n: - self.httpPatch.stop() - with patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self._delete( - 'networks', n['network']['id'], - expected_code=webob.exc.HTTPInternalServerError.code) - self.httpPatch.start() - # network should still exist in db - self.assertEqual(n['network']['id'], - self._get_networks(n['network']['tenant_id'] - )[0]['id']) - - def test_notify_on_security_group_change(self): - plugin = manager.NeutronManager.get_plugin() - with self.port() as p: - with contextlib.nested( - mock.patch.object(plugin, 'notifier'), - mock.patch.object(plugin, 'is_security_group_member_updated', - return_value=True) - ) as (n_mock, s_mock): - # any port update should trigger a notification due to s_mock - data = {'port': {'name': 'aNewName'}} - self.new_update_request( - 'ports', data, p['port']['id']).get_response(self.api) - self.assertTrue(n_mock.port_update.called) - - -class TestBigSwitchProxySubnetsV2(test_plugin.TestSubnetsV2, - BigSwitchProxyPluginV2TestCase): - - pass - - -class TestBigSwitchProxySync(BigSwitchProxyPluginV2TestCase): - - def test_send_data(self): - plugin_obj = manager.NeutronManager.get_plugin() - result = plugin_obj._send_all_data() - self.assertEqual(result[0], 200) - - -class TestBigSwitchAddressPairs(test_addr_pair.TestAllowedAddressPairs, - BigSwitchProxyPluginV2TestCase): - pass diff --git a/neutron/tests/unit/bigswitch/test_router_db.py b/neutron/tests/unit/bigswitch/test_router_db.py deleted file mode 100644 index 4ec9419df65..00000000000 --- a/neutron/tests/unit/bigswitch/test_router_db.py +++ /dev/null @@ -1,574 +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. -# -# Adapted from neutron.tests.unit.test_l3_plugin - -import contextlib -import copy - -import mock -from oslo_config import cfg -from six import moves -from webob import exc - -from neutron.common import test_lib -from neutron import context -from neutron.extensions import l3 -from neutron import manager -from neutron.openstack.common import uuidutils -from neutron.plugins.bigswitch.extensions import routerrule -from neutron.tests.unit.bigswitch import fake_server -from neutron.tests.unit.bigswitch import test_base -from neutron.tests.unit import test_api_v2 -from neutron.tests.unit import test_extension_extradhcpopts as test_extradhcp -from neutron.tests.unit import test_l3_plugin - - -HTTPCON = 'neutron.plugins.bigswitch.servermanager.httplib.HTTPConnection' -_uuid = uuidutils.generate_uuid - - -class RouterRulesTestExtensionManager(object): - - def get_resources(self): - l3.RESOURCE_ATTRIBUTE_MAP['routers'].update( - routerrule.EXTENDED_ATTRIBUTES_2_0['routers']) - return l3.L3.get_resources() - - def get_actions(self): - return [] - - def get_request_extensions(self): - return [] - - -class DHCPOptsTestCase(test_base.BigSwitchTestBase, - test_extradhcp.TestExtraDhcpOpt): - - def setUp(self, plugin=None): - self.setup_patches() - self.setup_config_files() - super(test_extradhcp.ExtraDhcpOptDBTestCase, - self).setUp(plugin=self._plugin_name) - self.setup_db() - self.startHttpPatch() - - -class RouterDBTestBase(test_base.BigSwitchTestBase, - test_l3_plugin.L3BaseForIntTests, - test_l3_plugin.L3NatTestCaseMixin): - - mock_rescheduling = False - - def setUp(self): - self.setup_patches() - self.setup_config_files() - ext_mgr = RouterRulesTestExtensionManager() - service_plugins = {'L3_ROUTER_NAT': self._l3_plugin_name} - super(RouterDBTestBase, self).setUp(plugin=self._plugin_name, - ext_mgr=ext_mgr, - service_plugins=service_plugins) - self.setup_db() - cfg.CONF.set_default('allow_overlapping_ips', False) - self.plugin_obj = manager.NeutronManager.get_service_plugins().get( - 'L3_ROUTER_NAT') - self.startHttpPatch() - - def tearDown(self): - super(RouterDBTestBase, self).tearDown() - del test_lib.test_config['config_files'] - - -class RouterDBTestCase(RouterDBTestBase, - test_l3_plugin.L3NatDBIntTestCase): - - def test_router_remove_router_interface_wrong_subnet_returns_400(self): - with self.router() as r: - with self.subnet() as s: - with self.subnet(cidr='10.0.10.0/24') as s1: - with self.port(subnet=s1) as p: - self._router_interface_action('add', - r['router']['id'], - None, - p['port']['id']) - self._router_interface_action('remove', - r['router']['id'], - s['subnet']['id'], - p['port']['id'], - exc.HTTPBadRequest.code) - #remove properly to clean-up - self._router_interface_action('remove', - r['router']['id'], - None, - p['port']['id']) - - def test_router_remove_router_interface_wrong_port_returns_404(self): - with self.router() as r: - with self.subnet() as s: - with self.port(subnet=s) as p: - self._router_interface_action('add', - r['router']['id'], - None, - p['port']['id']) - # create another port for testing failure case - res = self._create_port('json', p['port']['network_id']) - p2 = self.deserialize('json', res) - self._router_interface_action('remove', - r['router']['id'], - None, - p2['port']['id'], - exc.HTTPNotFound.code) - # remove correct interface to cleanup - self._router_interface_action('remove', - r['router']['id'], - None, - p['port']['id']) - # remove extra port created - self._delete('ports', p2['port']['id']) - - def test_add_network_to_ext_gw_backend_body(self): - plugin_obj = manager.NeutronManager.get_plugin() - with contextlib.nested( - self.network(), self.router() - ) as (n1, r1): - with self.subnet(network=n1, cidr='10.10.10.10/24') as s1: - self._set_net_external(s1['subnet']['network_id']) - with mock.patch.object(plugin_obj.servers, - 'rest_update_router') as upmock: - self._add_external_gateway_to_router(r1['router']['id'], - n1['network']['id']) - router_body = upmock.mock_calls[0][1][1] - self.assertEqual( - plugin_obj.get_network(context.get_admin_context(), - n1['network']['id']), - router_body['external_gateway_info']['network']) - - def test_multi_tenant_flip_alllocation(self): - tenant1_id = _uuid() - tenant2_id = _uuid() - with contextlib.nested( - self.network(tenant_id=tenant1_id), - self.network(tenant_id=tenant2_id)) as (n1, n2): - with contextlib.nested( - self.subnet(network=n1, cidr='11.0.0.0/24'), - self.subnet(network=n2, cidr='12.0.0.0/24'), - self.subnet(cidr='13.0.0.0/24')) as (s1, s2, psub): - with contextlib.nested( - self.router(tenant_id=tenant1_id), - self.router(tenant_id=tenant2_id), - self.port(subnet=s1, tenant_id=tenant1_id), - self.port(subnet=s2, tenant_id=tenant2_id)) as (r1, r2, - p1, p2): - self._set_net_external(psub['subnet']['network_id']) - s1id = p1['port']['fixed_ips'][0]['subnet_id'] - s2id = p2['port']['fixed_ips'][0]['subnet_id'] - s1 = {'subnet': {'id': s1id}} - s2 = {'subnet': {'id': s2id}} - self._add_external_gateway_to_router( - r1['router']['id'], - psub['subnet']['network_id']) - self._add_external_gateway_to_router( - r2['router']['id'], - psub['subnet']['network_id']) - self._router_interface_action( - 'add', r1['router']['id'], - s1['subnet']['id'], None) - self._router_interface_action( - 'add', r2['router']['id'], - s2['subnet']['id'], None) - fl1 = self._make_floatingip_for_tenant_port( - net_id=psub['subnet']['network_id'], - port_id=p1['port']['id'], - tenant_id=tenant1_id) - self.httpPatch.stop() - multiFloatPatch = mock.patch( - HTTPCON, - new=fake_server.VerifyMultiTenantFloatingIP) - multiFloatPatch.start() - fl2 = self._make_floatingip_for_tenant_port( - net_id=psub['subnet']['network_id'], - port_id=p2['port']['id'], - tenant_id=tenant2_id) - multiFloatPatch.stop() - self.httpPatch.start() - self._delete('floatingips', fl1['floatingip']['id']) - self._delete('floatingips', fl2['floatingip']['id']) - self._router_interface_action( - 'remove', r1['router']['id'], - s1['subnet']['id'], None) - self._router_interface_action( - 'remove', r2['router']['id'], - s2['subnet']['id'], None) - - def _make_floatingip_for_tenant_port(self, net_id, port_id, tenant_id): - data = {'floatingip': {'floating_network_id': net_id, - 'tenant_id': tenant_id, - 'port_id': port_id}} - floatingip_req = self.new_create_request('floatingips', data, self.fmt) - res = floatingip_req.get_response(self.ext_api) - return self.deserialize(self.fmt, res) - - def test_floatingip_with_invalid_create_port(self): - self._test_floatingip_with_invalid_create_port( - 'neutron.plugins.bigswitch.plugin.NeutronRestProxyV2') - - def test_create_floatingip_no_ext_gateway_return_404(self): - with self.subnet(cidr='10.0.10.0/24') as public_sub: - self._set_net_external(public_sub['subnet']['network_id']) - with self.port() as private_port: - with self.router(): - res = self._create_floatingip( - 'json', - public_sub['subnet']['network_id'], - port_id=private_port['port']['id']) - self.assertEqual(res.status_int, exc.HTTPNotFound.code) - - def test_router_update_gateway(self): - with self.router() as r: - with self.subnet() as s1: - with self.subnet(cidr='10.0.10.0/24') as s2: - self._set_net_external(s1['subnet']['network_id']) - self._add_external_gateway_to_router( - r['router']['id'], - s1['subnet']['network_id']) - body = self._show('routers', r['router']['id']) - net_id = (body['router'] - ['external_gateway_info']['network_id']) - self.assertEqual(net_id, s1['subnet']['network_id']) - self._set_net_external(s2['subnet']['network_id']) - self._add_external_gateway_to_router( - r['router']['id'], - s2['subnet']['network_id']) - body = self._show('routers', r['router']['id']) - net_id = (body['router'] - ['external_gateway_info']['network_id']) - self.assertEqual(net_id, s2['subnet']['network_id']) - self._remove_external_gateway_from_router( - r['router']['id'], - s2['subnet']['network_id']) - - def test_router_add_interface_overlapped_cidr(self): - self.skipTest("Plugin does not support") - - def test_router_add_interface_overlapped_cidr_returns_400(self): - self.skipTest("Plugin does not support") - - def test_list_nets_external(self): - self.skipTest("Plugin does not support") - - def test_router_update_gateway_with_existed_floatingip(self): - with self.subnet(cidr='10.0.10.0/24') as subnet: - self._set_net_external(subnet['subnet']['network_id']) - with self.floatingip_with_assoc() as fip: - self._add_external_gateway_to_router( - fip['floatingip']['router_id'], - subnet['subnet']['network_id'], - expected_code=exc.HTTPConflict.code) - - def test_router_remove_interface_wrong_subnet_returns_400(self): - with self.router() as r: - with self.subnet(cidr='10.0.10.0/24') as s: - with self.port() as p: - self._router_interface_action('add', - r['router']['id'], - None, - p['port']['id']) - self._router_interface_action('remove', - r['router']['id'], - s['subnet']['id'], - p['port']['id'], - exc.HTTPBadRequest.code) - #remove properly to clean-up - self._router_interface_action('remove', - r['router']['id'], - None, - p['port']['id']) - - def test_router_remove_interface_wrong_port_returns_404(self): - with self.router() as r: - with self.subnet(cidr='10.0.10.0/24'): - with self.port() as p: - self._router_interface_action('add', - r['router']['id'], - None, - p['port']['id']) - # create another port for testing failure case - res = self._create_port('json', p['port']['network_id']) - p2 = self.deserialize('json', res) - self._router_interface_action('remove', - r['router']['id'], - None, - p2['port']['id'], - exc.HTTPNotFound.code) - # remove correct interface to cleanup - self._router_interface_action('remove', - r['router']['id'], - None, - p['port']['id']) - # remove extra port created - self._delete('ports', p2['port']['id']) - - def test_send_data(self): - fmt = 'json' - plugin_obj = manager.NeutronManager.get_plugin() - - with self.router() as r: - r_id = r['router']['id'] - - with self.subnet(cidr='10.0.10.0/24') as s: - s_id = s['subnet']['id'] - - with self.router() as r1: - r1_id = r1['router']['id'] - body = self._router_interface_action('add', r_id, s_id, - None) - self.assertIn('port_id', body) - r_port_id = body['port_id'] - body = self._show('ports', r_port_id) - self.assertEqual(body['port']['device_id'], r_id) - - with self.subnet(cidr='10.0.20.0/24') as s1: - s1_id = s1['subnet']['id'] - body = self._router_interface_action('add', r1_id, - s1_id, None) - self.assertIn('port_id', body) - r1_port_id = body['port_id'] - body = self._show('ports', r1_port_id) - self.assertEqual(body['port']['device_id'], r1_id) - - with self.subnet(cidr='11.0.0.0/24') as public_sub: - public_net_id = public_sub['subnet']['network_id'] - self._set_net_external(public_net_id) - - with self.port() as prv_port: - prv_fixed_ip = prv_port['port']['fixed_ips'][0] - priv_sub_id = prv_fixed_ip['subnet_id'] - self._add_external_gateway_to_router( - r_id, public_net_id) - self._router_interface_action('add', r_id, - priv_sub_id, - None) - - priv_port_id = prv_port['port']['id'] - res = self._create_floatingip( - fmt, public_net_id, - port_id=priv_port_id) - self.assertEqual(res.status_int, - exc.HTTPCreated.code) - floatingip = self.deserialize(fmt, res) - - result = plugin_obj._send_all_data() - self.assertEqual(result[0], 200) - - self._delete('floatingips', - floatingip['floatingip']['id']) - self._remove_external_gateway_from_router( - r_id, public_net_id) - self._router_interface_action('remove', r_id, - priv_sub_id, - None) - self._router_interface_action('remove', r_id, s_id, - None) - self._show('ports', r_port_id, - expected_code=exc.HTTPNotFound.code) - self._router_interface_action('remove', r1_id, s1_id, - None) - self._show('ports', r1_port_id, - expected_code=exc.HTTPNotFound.code) - - def test_router_rules_update(self): - with self.router() as r: - r_id = r['router']['id'] - router_rules = [{'destination': '1.2.3.4/32', - 'source': '4.3.2.1/32', - 'action': 'permit', - 'nexthops': ['4.4.4.4', '4.4.4.5']}] - body = self._update('routers', r_id, - {'router': {'router_rules': router_rules}}) - - body = self._show('routers', r['router']['id']) - self.assertIn('router_rules', body['router']) - rules = body['router']['router_rules'] - self.assertEqual(_strip_rule_ids(rules), router_rules) - # Try after adding another rule - router_rules.append({'source': 'external', - 'destination': '8.8.8.8/32', - 'action': 'permit', 'nexthops': []}) - body = self._update('routers', r['router']['id'], - {'router': {'router_rules': router_rules}}) - - body = self._show('routers', r['router']['id']) - self.assertIn('router_rules', body['router']) - rules = body['router']['router_rules'] - self.assertEqual(_strip_rule_ids(rules), router_rules) - - def test_router_rules_separation(self): - with self.router() as r1: - with self.router() as r2: - r1_id = r1['router']['id'] - r2_id = r2['router']['id'] - router1_rules = [{'destination': '5.6.7.8/32', - 'source': '8.7.6.5/32', - 'action': 'permit', - 'nexthops': ['8.8.8.8', '9.9.9.9']}] - router2_rules = [{'destination': '1.2.3.4/32', - 'source': '4.3.2.1/32', - 'action': 'permit', - 'nexthops': ['4.4.4.4', '4.4.4.5']}] - body1 = self._update('routers', r1_id, - {'router': - {'router_rules': router1_rules}}) - body2 = self._update('routers', r2_id, - {'router': - {'router_rules': router2_rules}}) - - body1 = self._show('routers', r1_id) - body2 = self._show('routers', r2_id) - rules1 = body1['router']['router_rules'] - rules2 = body2['router']['router_rules'] - self.assertEqual(_strip_rule_ids(rules1), router1_rules) - self.assertEqual(_strip_rule_ids(rules2), router2_rules) - - def test_router_rules_validation(self): - with self.router() as r: - r_id = r['router']['id'] - good_rules = [{'destination': '1.2.3.4/32', - 'source': '4.3.2.1/32', - 'action': 'permit', - 'nexthops': ['4.4.4.4', '4.4.4.5']}] - - body = self._update('routers', r_id, - {'router': {'router_rules': good_rules}}) - body = self._show('routers', r_id) - self.assertIn('router_rules', body['router']) - self.assertEqual(good_rules, - _strip_rule_ids(body['router']['router_rules'])) - - # Missing nexthops should be populated with an empty list - light_rules = copy.deepcopy(good_rules) - del light_rules[0]['nexthops'] - body = self._update('routers', r_id, - {'router': {'router_rules': light_rules}}) - body = self._show('routers', r_id) - self.assertIn('router_rules', body['router']) - light_rules[0]['nexthops'] = [] - self.assertEqual(light_rules, - _strip_rule_ids(body['router']['router_rules'])) - # bad CIDR - bad_rules = copy.deepcopy(good_rules) - bad_rules[0]['destination'] = '1.1.1.1' - body = self._update('routers', r_id, - {'router': {'router_rules': bad_rules}}, - expected_code=exc.HTTPBadRequest.code) - # bad next hop - bad_rules = copy.deepcopy(good_rules) - bad_rules[0]['nexthops'] = ['1.1.1.1', 'f2'] - body = self._update('routers', r_id, - {'router': {'router_rules': bad_rules}}, - expected_code=exc.HTTPBadRequest.code) - # bad action - bad_rules = copy.deepcopy(good_rules) - bad_rules[0]['action'] = 'dance' - body = self._update('routers', r_id, - {'router': {'router_rules': bad_rules}}, - expected_code=exc.HTTPBadRequest.code) - # duplicate rule with opposite action - bad_rules = copy.deepcopy(good_rules) - bad_rules.append(copy.deepcopy(bad_rules[0])) - bad_rules.append(copy.deepcopy(bad_rules[0])) - bad_rules[1]['source'] = 'any' - bad_rules[2]['action'] = 'deny' - body = self._update('routers', r_id, - {'router': {'router_rules': bad_rules}}, - expected_code=exc.HTTPBadRequest.code) - # duplicate nexthop - bad_rules = copy.deepcopy(good_rules) - bad_rules[0]['nexthops'] = ['1.1.1.1', '1.1.1.1'] - body = self._update('routers', r_id, - {'router': {'router_rules': bad_rules}}, - expected_code=exc.HTTPBadRequest.code) - # make sure light rules persisted during bad updates - body = self._show('routers', r_id) - self.assertIn('router_rules', body['router']) - self.assertEqual(light_rules, - _strip_rule_ids(body['router']['router_rules'])) - - def test_router_rules_config_change(self): - cfg.CONF.set_override('tenant_default_router_rule', - ['*:any:any:deny', - '*:8.8.8.8/32:any:permit:1.2.3.4'], - 'ROUTER') - with self.router() as r: - body = self._show('routers', r['router']['id']) - expected_rules = [{'source': 'any', 'destination': 'any', - 'nexthops': [], 'action': 'deny'}, - {'source': '8.8.8.8/32', 'destination': 'any', - 'nexthops': ['1.2.3.4'], 'action': 'permit'}] - self.assertEqual(expected_rules, - _strip_rule_ids(body['router']['router_rules'])) - - def test_rule_exhaustion(self): - cfg.CONF.set_override('max_router_rules', 10, 'ROUTER') - with self.router() as r: - rules = [] - for i in moves.xrange(1, 12): - rule = {'source': 'any', 'nexthops': [], - 'destination': '1.1.1.' + str(i) + '/32', - 'action': 'permit'} - rules.append(rule) - self._update('routers', r['router']['id'], - {'router': {'router_rules': rules}}, - expected_code=exc.HTTPBadRequest.code) - - def test_rollback_on_router_create(self): - tid = test_api_v2._uuid() - self.httpPatch.stop() - with mock.patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self._create_router('json', tid) - self.assertTrue(len(self._get_routers(tid)) == 0) - - def test_rollback_on_router_update(self): - with self.router() as r: - data = {'router': {'name': 'aNewName'}} - self.httpPatch.stop() - with mock.patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self.new_update_request( - 'routers', data, r['router']['id']).get_response(self.api) - self.httpPatch.start() - updatedr = self._get_routers(r['router']['tenant_id'])[0] - # name should have stayed the same due to failure - self.assertEqual(r['router']['name'], updatedr['name']) - - def test_rollback_on_router_delete(self): - with self.router() as r: - self.httpPatch.stop() - with mock.patch(HTTPCON, new=fake_server.HTTPConnectionMock500): - self._delete('routers', r['router']['id'], - expected_code=exc.HTTPInternalServerError.code) - self.httpPatch.start() - self.assertEqual(r['router']['id'], - self._get_routers(r['router']['tenant_id'] - )[0]['id']) - - def _get_routers(self, tenant_id): - ctx = context.Context('', tenant_id) - return self.plugin_obj.get_routers(ctx) - - -def _strip_rule_ids(rules): - cleaned = [] - for rule in rules: - del rule['id'] - cleaned.append(rule) - return cleaned diff --git a/neutron/tests/unit/bigswitch/test_security_groups.py b/neutron/tests/unit/bigswitch/test_security_groups.py deleted file mode 100644 index 95a371c583b..00000000000 --- a/neutron/tests/unit/bigswitch/test_security_groups.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2014, Big Switch Networks -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from neutron import manager -from neutron.tests.unit.bigswitch import test_base -from neutron.tests.unit import test_extension_security_group as test_sg -from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc - - -class RestProxySecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase, - test_base.BigSwitchTestBase): - plugin_str = ('%s.NeutronRestProxyV2' % - test_base.RESTPROXY_PKG_PATH) - - def setUp(self, plugin=None): - test_sg_rpc.set_firewall_driver(test_sg_rpc.FIREWALL_HYBRID_DRIVER) - self.setup_config_files() - self.setup_patches() - self._attribute_map_bk_ = {} - super(RestProxySecurityGroupsTestCase, self).setUp(self.plugin_str) - self.setup_db() - plugin = manager.NeutronManager.get_plugin() - self.notifier = plugin.notifier - self.rpc = plugin.endpoints[0] - self.startHttpPatch() - - -class TestSecServerRpcCallBack(test_sg_rpc.SGServerRpcCallBackTestCase, - RestProxySecurityGroupsTestCase): - pass - - -class TestSecurityGroupsMixin(test_sg.TestSecurityGroups, - test_sg_rpc.SGNotificationTestMixin, - RestProxySecurityGroupsTestCase): - pass diff --git a/neutron/tests/unit/bigswitch/test_servermanager.py b/neutron/tests/unit/bigswitch/test_servermanager.py deleted file mode 100644 index 3b562f164b1..00000000000 --- a/neutron/tests/unit/bigswitch/test_servermanager.py +++ /dev/null @@ -1,668 +0,0 @@ -# Copyright 2014 Big Switch Networks, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import contextlib -import httplib -import socket -import ssl - -import mock -from oslo_config import cfg -from oslo_db import exception as db_exc -from oslo_serialization import jsonutils -from oslo_utils import importutils - -from neutron import context -from neutron import manager -from neutron.plugins.bigswitch.db import consistency_db -from neutron.plugins.bigswitch import servermanager -from neutron.tests.unit.bigswitch import test_restproxy_plugin as test_rp - -SERVERMANAGER = 'neutron.plugins.bigswitch.servermanager' -HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection' -HTTPSCON = SERVERMANAGER + '.HTTPSConnectionWithValidation' - - -class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase): - - def setUp(self): - self.socket_mock = mock.patch( - SERVERMANAGER + '.socket.create_connection').start() - self.wrap_mock = mock.patch(SERVERMANAGER + '.ssl.wrap_socket').start() - super(ServerManagerTests, self).setUp() - # http patch must not be running or it will mangle the servermanager - # import where the https connection classes are defined - self.httpPatch.stop() - self.sm = importutils.import_module(SERVERMANAGER) - - def test_no_servers(self): - cfg.CONF.set_override('servers', [], 'RESTPROXY') - self.assertRaises(cfg.Error, servermanager.ServerPool) - - def test_malformed_servers(self): - cfg.CONF.set_override('servers', ['1.2.3.4', '1.1.1.1:a'], 'RESTPROXY') - self.assertRaises(cfg.Error, servermanager.ServerPool) - - def test_ipv6_server_address(self): - cfg.CONF.set_override( - 'servers', ['[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:80'], - 'RESTPROXY') - s = servermanager.ServerPool() - self.assertEqual(s.servers[0].server, - '[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]') - - def test_sticky_cert_fetch_fail(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.ssl = True - with mock.patch( - 'ssl.get_server_certificate', - side_effect=Exception('There is no more entropy in the universe') - ) as sslgetmock: - self.assertRaises( - cfg.Error, - pl.servers._get_combined_cert_for_server, - *('example.org', 443) - ) - sslgetmock.assert_has_calls([mock.call( - ('example.org', 443), ssl_version=ssl.PROTOCOL_TLSv1)]) - - def test_consistency_watchdog_stops_with_0_polling_interval(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.capabilities = ['consistency'] - self.watch_p.stop() - with mock.patch('eventlet.sleep') as smock: - # should return immediately a polling interval of 0 - pl.servers._consistency_watchdog(0) - self.assertFalse(smock.called) - - def test_consistency_watchdog(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.capabilities = [] - self.watch_p.stop() - with contextlib.nested( - mock.patch('eventlet.sleep'), - mock.patch( - SERVERMANAGER + '.ServerPool.rest_call', - side_effect=servermanager.RemoteRestError( - reason='Failure to trigger except clause.' - ) - ), - mock.patch( - SERVERMANAGER + '.LOG.exception', - side_effect=KeyError('Failure to break loop') - ) - ) as (smock, rmock, lmock): - # should return immediately without consistency capability - pl.servers._consistency_watchdog() - self.assertFalse(smock.called) - pl.servers.capabilities = ['consistency'] - self.assertRaises(KeyError, - pl.servers._consistency_watchdog) - rmock.assert_called_with('GET', '/health', '', {}, [], False) - self.assertEqual(1, len(lmock.mock_calls)) - - def test_consistency_hash_header(self): - # mock HTTP class instead of rest_call so we can see headers - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - rv.getresponse.return_value.status = 200 - rv.getresponse.return_value.read.return_value = '' - with self.network() as network: - callheaders = rv.request.mock_calls[0][1][3] - self.assertIn('X-BSN-BVS-HASH-MATCH', callheaders) - # first call will be empty to indicate no previous state hash - self.assertEqual(callheaders['X-BSN-BVS-HASH-MATCH'], '') - # change the header that will be received on delete call - rv.getresponse.return_value.getheader.return_value = 'HASH2' - self._delete('networks', network['network']['id']) - # net delete should have used header received on create - callheaders = rv.request.mock_calls[1][1][3] - self.assertEqual(callheaders['X-BSN-BVS-HASH-MATCH'], 'HASHHEADER') - - # create again should now use header received from prev delete - with self.network(): - callheaders = rv.request.mock_calls[2][1][3] - self.assertIn('X-BSN-BVS-HASH-MATCH', callheaders) - self.assertEqual(callheaders['X-BSN-BVS-HASH-MATCH'], - 'HASH2') - - def test_consistency_hash_header_no_update_on_bad_response(self): - # mock HTTP class instead of rest_call so we can see headers - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - rv.getresponse.return_value.status = 200 - rv.getresponse.return_value.read.return_value = '' - with self.network() as net: - # change the header that will be received on delete call - rv.getresponse.return_value.getheader.return_value = 'EVIL' - rv.getresponse.return_value.status = 'GARBAGE' - self._delete('networks', net['network']['id']) - - # create again should not use header from delete call - with self.network(): - callheaders = rv.request.mock_calls[2][1][3] - self.assertIn('X-BSN-BVS-HASH-MATCH', callheaders) - self.assertEqual(callheaders['X-BSN-BVS-HASH-MATCH'], - 'HASHHEADER') - - def test_file_put_contents(self): - pl = manager.NeutronManager.get_plugin() - with mock.patch(SERVERMANAGER + '.open', create=True) as omock: - pl.servers._file_put_contents('somepath', 'contents') - omock.assert_has_calls([mock.call('somepath', 'w')]) - omock.return_value.__enter__.return_value.assert_has_calls([ - mock.call.write('contents') - ]) - - def test_combine_certs_to_file(self): - pl = manager.NeutronManager.get_plugin() - with mock.patch(SERVERMANAGER + '.open', create=True) as omock: - omock.return_value.__enter__().read.return_value = 'certdata' - pl.servers._combine_certs_to_file(['cert1.pem', 'cert2.pem'], - 'combined.pem') - # mock shared between read and write file handles so the calls - # are mixed together - omock.assert_has_calls([ - mock.call('combined.pem', 'w'), - mock.call('cert1.pem', 'r'), - mock.call('cert2.pem', 'r'), - ], any_order=True) - omock.return_value.__enter__.return_value.assert_has_calls([ - mock.call.read(), - mock.call.write('certdata'), - mock.call.read(), - mock.call.write('certdata') - ]) - - def test_auth_header(self): - cfg.CONF.set_override('server_auth', 'username:pass', 'RESTPROXY') - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - sp.rest_create_network('tenant', 'network') - callheaders = rv.request.mock_calls[0][1][3] - self.assertIn('Authorization', callheaders) - self.assertEqual(callheaders['Authorization'], - 'Basic dXNlcm5hbWU6cGFzcw==') - - def test_header_add(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - sp.servers[0].rest_call('GET', '/', headers={'EXTRA-HEADER': 'HI'}) - callheaders = rv.request.mock_calls[0][1][3] - # verify normal headers weren't mangled - self.assertIn('Content-type', callheaders) - self.assertEqual(callheaders['Content-type'], - 'application/json') - # verify new header made it in - self.assertIn('EXTRA-HEADER', callheaders) - self.assertEqual(callheaders['EXTRA-HEADER'], 'HI') - - def test_req_context_header(self): - sp = manager.NeutronManager.get_plugin().servers - ncontext = context.Context('uid', 'tid') - sp.set_context(ncontext) - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - sp.rest_action('GET', '/') - callheaders = rv.request.mock_calls[0][1][3] - self.assertIn(servermanager.REQ_CONTEXT_HEADER, callheaders) - ctxdct = ncontext.to_dict() - # auth token is not included - ctxdct.pop('auth_token') - self.assertEqual( - ctxdct, jsonutils.loads( - callheaders[servermanager.REQ_CONTEXT_HEADER])) - - def test_capabilities_retrieval(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value.getresponse.return_value - rv.getheader.return_value = 'HASHHEADER' - - # each server will get different capabilities - rv.read.side_effect = ['["a","b","c"]', '["b","c","d"]'] - # pool capabilities is intersection between both - self.assertEqual(set(['b', 'c']), sp.get_capabilities()) - self.assertEqual(2, rv.read.call_count) - - # the pool should cache after the first call so no more - # HTTP calls should be made - rv.read.side_effect = ['["w","x","y"]', '["x","y","z"]'] - self.assertEqual(set(['b', 'c']), sp.get_capabilities()) - self.assertEqual(2, rv.read.call_count) - - def test_capabilities_retrieval_failure(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value.getresponse.return_value - rv.getheader.return_value = 'HASHHEADER' - # a failure to parse should result in an empty capability set - rv.read.return_value = 'XXXXX' - self.assertEqual([], sp.servers[0].get_capabilities()) - - # One broken server should affect all capabilities - rv.read.side_effect = ['{"a": "b"}', '["b","c","d"]'] - self.assertEqual(set(), sp.get_capabilities()) - - def test_reconnect_on_timeout_change(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' - sp.servers[0].capabilities = ['keep-alive'] - sp.servers[0].rest_call('GET', '/', timeout=10) - # even with keep-alive enabled, a change in timeout will trigger - # a reconnect - sp.servers[0].rest_call('GET', '/', timeout=75) - conmock.assert_has_calls([ - mock.call('localhost', 9000, timeout=10), - mock.call('localhost', 9000, timeout=75), - ], any_order=True) - - def test_connect_failures(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON, return_value=None): - resp = sp.servers[0].rest_call('GET', '/') - self.assertEqual(resp, (0, None, None, None)) - # verify same behavior on ssl class - sp.servers[0].currentcon = False - sp.servers[0].ssl = True - with mock.patch(HTTPSCON, return_value=None): - resp = sp.servers[0].rest_call('GET', '/') - self.assertEqual(resp, (0, None, None, None)) - - def test_reconnect_cached_connection(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - rv.getresponse.return_value.getheader.return_value = 'HASH' - sp.servers[0].capabilities = ['keep-alive'] - sp.servers[0].rest_call('GET', '/first') - # raise an error on re-use to verify reconnect - # return okay the second time so the reconnect works - rv.request.side_effect = [httplib.ImproperConnectionState(), - mock.MagicMock()] - sp.servers[0].rest_call('GET', '/second') - uris = [c[1][1] for c in rv.request.mock_calls] - expected = [ - sp.base_uri + '/first', - sp.base_uri + '/second', - sp.base_uri + '/second', - ] - self.assertEqual(uris, expected) - - def test_no_reconnect_recurse_to_infinity(self): - # retry uses recursion when a reconnect is necessary - # this test makes sure it stops after 1 recursive call - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - rv = conmock.return_value - # hash header must be string instead of mock object - rv.getresponse.return_value.getheader.return_value = 'HASH' - sp.servers[0].capabilities = ['keep-alive'] - sp.servers[0].rest_call('GET', '/first') - # after retrying once, the rest call should raise the - # exception up - rv.request.side_effect = httplib.ImproperConnectionState() - self.assertRaises(httplib.ImproperConnectionState, - sp.servers[0].rest_call, - *('GET', '/second')) - # 1 for the first call, 2 for the second with retry - self.assertEqual(rv.request.call_count, 3) - - def test_socket_error(self): - sp = servermanager.ServerPool() - with mock.patch(HTTPCON) as conmock: - conmock.return_value.request.side_effect = socket.timeout() - resp = sp.servers[0].rest_call('GET', '/') - self.assertEqual(resp, (0, None, None, None)) - - def test_cert_get_fail(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.ssl = True - with mock.patch('os.path.exists', return_value=False): - self.assertRaises(cfg.Error, - pl.servers._get_combined_cert_for_server, - *('example.org', 443)) - - def test_cert_make_dirs(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.ssl = True - cfg.CONF.set_override('ssl_sticky', False, 'RESTPROXY') - # pretend base dir exists, 3 children don't, and host cert does - with contextlib.nested( - mock.patch('os.path.exists', side_effect=[True, False, False, - False, True]), - mock.patch('os.makedirs'), - mock.patch(SERVERMANAGER + '.ServerPool._combine_certs_to_file') - ) as (exmock, makemock, combmock): - # will raise error because no certs found - self.assertIn( - 'example.org', - pl.servers._get_combined_cert_for_server('example.org', 443) - ) - base = cfg.CONF.RESTPROXY.ssl_cert_directory - hpath = base + '/host_certs/example.org.pem' - combpath = base + '/combined/example.org.pem' - combmock.assert_has_calls([mock.call([hpath], combpath)]) - self.assertEqual(exmock.call_count, 5) - self.assertEqual(makemock.call_count, 3) - - def test_no_cert_error(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.ssl = True - cfg.CONF.set_override('ssl_sticky', False, 'RESTPROXY') - # pretend base dir exists and 3 children do, but host cert doesn't - with mock.patch( - 'os.path.exists', - side_effect=[True, True, True, True, False] - ) as exmock: - # will raise error because no certs found - self.assertRaises( - cfg.Error, - pl.servers._get_combined_cert_for_server, - *('example.org', 443) - ) - self.assertEqual(exmock.call_count, 5) - - def test_action_success(self): - pl = manager.NeutronManager.get_plugin() - self.assertTrue(pl.servers.action_success((200,))) - - def test_server_failure(self): - pl = manager.NeutronManager.get_plugin() - self.assertTrue(pl.servers.server_failure((404,))) - # server failure has an ignore codes option - self.assertFalse(pl.servers.server_failure((404,), - ignore_codes=[404])) - - def test_retry_on_unavailable(self): - pl = manager.NeutronManager.get_plugin() - with contextlib.nested( - mock.patch(SERVERMANAGER + '.ServerProxy.rest_call', - return_value=(httplib.SERVICE_UNAVAILABLE, 0, 0, 0)), - mock.patch(SERVERMANAGER + '.time.sleep') - ) as (srestmock, tmock): - # making a call should trigger retries with sleeps in between - pl.servers.rest_call('GET', '/', '', None, []) - rest_call = [mock.call('GET', '/', '', None, False, reconnect=True, - hash_handler=mock.ANY)] - rest_call_count = ( - servermanager.HTTP_SERVICE_UNAVAILABLE_RETRY_COUNT + 1) - srestmock.assert_has_calls(rest_call * rest_call_count) - sleep_call = [mock.call( - servermanager.HTTP_SERVICE_UNAVAILABLE_RETRY_INTERVAL)] - # should sleep 1 less time than the number of calls - sleep_call_count = rest_call_count - 1 - tmock.assert_has_calls(sleep_call * sleep_call_count) - - def test_delete_failure_sets_bad_hash(self): - pl = manager.NeutronManager.get_plugin() - hash_handler = consistency_db.HashHandler() - with mock.patch( - SERVERMANAGER + '.ServerProxy.rest_call', - return_value=(httplib.INTERNAL_SERVER_ERROR, 0, 0, 0) - ): - # a failed delete call should put a bad hash in the DB - pl.servers.rest_call('DELETE', '/', '', None, []) - self.assertEqual('INCONSISTENT,INCONSISTENT', - hash_handler.read_for_update()) - - def test_conflict_triggers_sync(self): - pl = manager.NeutronManager.get_plugin() - with mock.patch( - SERVERMANAGER + '.ServerProxy.rest_call', - return_value=(httplib.CONFLICT, 0, 0, 0) - ) as srestmock: - # making a call should trigger a conflict sync - pl.servers.rest_call('GET', '/', '', None, []) - srestmock.assert_has_calls([ - mock.call('GET', '/', '', None, False, reconnect=True, - hash_handler=mock.ANY), - mock.call('PUT', '/topology', - {'routers': [], 'networks': []}, - timeout=None) - ]) - - def test_conflict_sync_raises_error_without_topology(self): - pl = manager.NeutronManager.get_plugin() - pl.servers.get_topo_function = None - with mock.patch( - SERVERMANAGER + '.ServerProxy.rest_call', - return_value=(httplib.CONFLICT, 0, 0, 0) - ): - # making a call should trigger a conflict sync that will - # error without the topology function set - self.assertRaises( - cfg.Error, - pl.servers.rest_call, - *('GET', '/', '', None, []) - ) - - def test_floating_calls(self): - pl = manager.NeutronManager.get_plugin() - with mock.patch(SERVERMANAGER + '.ServerPool.rest_action') as ramock: - body1 = {'id': 'somefloat'} - body2 = {'name': 'myfl'} - pl.servers.rest_create_floatingip('tenant', body1) - pl.servers.rest_update_floatingip('tenant', body2, 'id') - pl.servers.rest_delete_floatingip('tenant', 'oldid') - ramock.assert_has_calls([ - mock.call('PUT', '/tenants/tenant/floatingips/somefloat', - body1, - errstr=u'Unable to create floating IP: %s'), - mock.call('PUT', '/tenants/tenant/floatingips/id', - body2, - errstr=u'Unable to update floating IP: %s'), - mock.call('DELETE', '/tenants/tenant/floatingips/oldid', - errstr=u'Unable to delete floating IP: %s') - ]) - - def test_HTTPSConnectionWithValidation_without_cert(self): - con = self.sm.HTTPSConnectionWithValidation( - 'www.example.org', 443, timeout=90) - con.source_address = '127.0.0.1' - con.request("GET", "/") - self.socket_mock.assert_has_calls([mock.call( - ('www.example.org', 443), 90, '127.0.0.1' - )]) - self.wrap_mock.assert_has_calls([mock.call( - self.socket_mock(), None, None, cert_reqs=ssl.CERT_NONE, - ssl_version=ssl.PROTOCOL_TLSv1 - )]) - self.assertEqual(con.sock, self.wrap_mock()) - - def test_HTTPSConnectionWithValidation_with_cert(self): - con = self.sm.HTTPSConnectionWithValidation( - 'www.example.org', 443, timeout=90) - con.combined_cert = 'SOMECERTS.pem' - con.source_address = '127.0.0.1' - con.request("GET", "/") - self.socket_mock.assert_has_calls([mock.call( - ('www.example.org', 443), 90, '127.0.0.1' - )]) - self.wrap_mock.assert_has_calls([mock.call( - self.socket_mock(), None, None, ca_certs='SOMECERTS.pem', - cert_reqs=ssl.CERT_REQUIRED, - ssl_version=ssl.PROTOCOL_TLSv1 - )]) - self.assertEqual(con.sock, self.wrap_mock()) - - def test_HTTPSConnectionWithValidation_tunnel(self): - tunnel_mock = mock.patch.object( - self.sm.HTTPSConnectionWithValidation, - '_tunnel').start() - con = self.sm.HTTPSConnectionWithValidation( - 'www.example.org', 443, timeout=90) - con.source_address = '127.0.0.1' - con.set_tunnel('myproxy.local', 3128) - con.request("GET", "/") - self.socket_mock.assert_has_calls([mock.call( - ('www.example.org', 443), 90, '127.0.0.1' - )]) - self.wrap_mock.assert_has_calls([mock.call( - self.socket_mock(), None, None, cert_reqs=ssl.CERT_NONE, - ssl_version=ssl.PROTOCOL_TLSv1 - )]) - # _tunnel() doesn't take any args - tunnel_mock.assert_has_calls([mock.call()]) - self.assertEqual(con._tunnel_host, 'myproxy.local') - self.assertEqual(con._tunnel_port, 3128) - self.assertEqual(con.sock, self.wrap_mock()) - - -class TestSockets(test_rp.BigSwitchProxyPluginV2TestCase): - - def setUp(self): - super(TestSockets, self).setUp() - # http patch must not be running or it will mangle the servermanager - # import where the https connection classes are defined - self.httpPatch.stop() - self.sm = importutils.import_module(SERVERMANAGER) - - def test_socket_create_attempt(self): - # exercise the socket creation to make sure it works on both python - # versions - con = self.sm.HTTPSConnectionWithValidation('127.0.0.1', 0, timeout=1) - # if httpcon was created, a connect attempt should raise a socket error - self.assertRaises(socket.error, con.connect) - - -class HashLockingTests(test_rp.BigSwitchProxyPluginV2TestCase): - - def _get_hash_from_handler_db(self, handler): - with handler.session.begin(subtransactions=True): - res = (handler.session.query(consistency_db.ConsistencyHash). - filter_by(hash_id=handler.hash_id).first()) - return res.hash - - def test_hash_handle_lock_no_initial_record(self): - handler = consistency_db.HashHandler() - h1 = handler.read_for_update() - # return to caller should be empty even with lock in DB - self.assertFalse(h1) - # db should have a lock marker - self.assertEqual(handler.lock_marker, - self._get_hash_from_handler_db(handler)) - # an entry should clear the lock - handler.put_hash('DIGEST') - self.assertEqual('DIGEST', self._get_hash_from_handler_db(handler)) - - def test_hash_handle_lock_existing_record(self): - handler = consistency_db.HashHandler() - handler.put_hash('DIGEST') # set initial hash - - h1 = handler.read_for_update() - self.assertEqual('DIGEST', h1) - self.assertEqual(handler.lock_marker + 'DIGEST', - self._get_hash_from_handler_db(handler)) - - # make sure update works - handler.put_hash('DIGEST2') - self.assertEqual('DIGEST2', self._get_hash_from_handler_db(handler)) - - def test_db_duplicate_on_insert(self): - handler = consistency_db.HashHandler() - with mock.patch.object( - handler.session, 'add', side_effect=[db_exc.DBDuplicateEntry, ''] - ) as add_mock: - handler.read_for_update() - # duplicate insert failure should result in retry - self.assertEqual(2, add_mock.call_count) - - def test_update_hit_no_records(self): - handler = consistency_db.HashHandler() - # set initial hash so update will be required - handler.put_hash('DIGEST') - with mock.patch.object(handler._FACADE, 'get_engine') as ge: - conn = ge.return_value.begin.return_value.__enter__.return_value - firstresult = mock.Mock() - # a rowcount of 0 simulates the effect of another db client - # updating the same record the handler was trying to update - firstresult.rowcount = 0 - secondresult = mock.Mock() - secondresult.rowcount = 1 - conn.execute.side_effect = [firstresult, secondresult] - handler.read_for_update() - # update should have been called again after the failure - self.assertEqual(2, conn.execute.call_count) - - def test_handler_already_holding_lock(self): - handler = consistency_db.HashHandler() - handler.read_for_update() # lock the table - with mock.patch.object(handler._FACADE, 'get_engine') as ge: - handler.read_for_update() - # get engine should not have been called because no update - # should have been made - self.assertFalse(ge.called) - - def test_clear_lock(self): - handler = consistency_db.HashHandler() - handler.put_hash('SOMEHASH') - handler.read_for_update() # lock the table - self.assertEqual(handler.lock_marker + 'SOMEHASH', - self._get_hash_from_handler_db(handler)) - handler.clear_lock() - self.assertEqual('SOMEHASH', - self._get_hash_from_handler_db(handler)) - - def test_clear_lock_skip_after_steal(self): - handler1 = consistency_db.HashHandler() - handler1.read_for_update() # lock the table - handler2 = consistency_db.HashHandler() - with mock.patch.object(consistency_db, 'MAX_LOCK_WAIT_TIME', new=0): - handler2.read_for_update() - before = self._get_hash_from_handler_db(handler1) - # handler1 should not clear handler2's lock - handler1.clear_lock() - self.assertEqual(before, self._get_hash_from_handler_db(handler1)) - - def test_take_lock_from_other(self): - handler1 = consistency_db.HashHandler() - handler1.read_for_update() # lock the table - handler2 = consistency_db.HashHandler() - with mock.patch.object(consistency_db, 'MAX_LOCK_WAIT_TIME') as mlock: - # make handler2 wait for only one iteration - mlock.__lt__.side_effect = [False, True] - handler2.read_for_update() - # once MAX LOCK exceeded, comparisons should stop due to lock steal - self.assertEqual(2, mlock.__lt__.call_count) - dbentry = self._get_hash_from_handler_db(handler1) - # handler2 should have the lock - self.assertIn(handler2.lock_marker, dbentry) - self.assertNotIn(handler1.lock_marker, dbentry) - # lock protection only blocks read_for_update, anyone can change - handler1.put_hash('H1') - - def test_failure_to_steal_lock(self): - handler1 = consistency_db.HashHandler() - handler1.read_for_update() # lock the table - handler2 = consistency_db.HashHandler() - with contextlib.nested( - mock.patch.object(consistency_db, 'MAX_LOCK_WAIT_TIME'), - mock.patch.object(handler2, '_optimistic_update_hash_record', - side_effect=[False, True]) - ) as (mlock, oplock): - # handler2 will go through 2 iterations since the lock will fail on - # the first attempt - mlock.__lt__.side_effect = [False, True, False, True] - handler2.read_for_update() - self.assertEqual(4, mlock.__lt__.call_count) - self.assertEqual(2, oplock.call_count) diff --git a/neutron/tests/unit/bigswitch/test_ssl.py b/neutron/tests/unit/bigswitch/test_ssl.py deleted file mode 100644 index f8c51b1fe50..00000000000 --- a/neutron/tests/unit/bigswitch/test_ssl.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2014 Big Switch Networks, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import contextlib -import os -import ssl - -import mock -from oslo_config import cfg -import webob.exc - -from neutron.openstack.common import log as logging -from neutron.tests.unit.bigswitch import fake_server -from neutron.tests.unit.bigswitch import test_base -from neutron.tests.unit import test_api_v2 -from neutron.tests.unit import test_db_plugin as test_plugin - -LOG = logging.getLogger(__name__) - -SERVERMANAGER = 'neutron.plugins.bigswitch.servermanager' -HTTPS = SERVERMANAGER + '.HTTPSConnectionWithValidation' -CERTCOMBINER = SERVERMANAGER + '.ServerPool._combine_certs_to_file' -FILEPUT = SERVERMANAGER + '.ServerPool._file_put_contents' -GETCACERTS = SERVERMANAGER + '.ServerPool._get_ca_cert_paths' -GETHOSTCERT = SERVERMANAGER + '.ServerPool._get_host_cert_path' -SSLGETCERT = SERVERMANAGER + '.ssl.get_server_certificate' -FAKECERTGET = 'neutron.tests.unit.bigswitch.fake_server.get_cert_contents' - - -class test_ssl_certificate_base(test_plugin.NeutronDbPluginV2TestCase, - test_base.BigSwitchTestBase): - - plugin_str = ('%s.NeutronRestProxyV2' % - test_base.RESTPROXY_PKG_PATH) - servername = None - cert_base = None - - def _setUp(self): - self.servername = test_api_v2._uuid() - self.cert_base = cfg.CONF.RESTPROXY.ssl_cert_directory - self.host_cert_val = 'DUMMYCERTFORHOST%s' % self.servername - self.host_cert_path = os.path.join( - self.cert_base, - 'host_certs', - '%s.pem' % self.servername - ) - self.comb_cert_path = os.path.join( - self.cert_base, - 'combined', - '%s.pem' % self.servername - ) - self.ca_certs_path = os.path.join( - self.cert_base, - 'ca_certs' - ) - cfg.CONF.set_override('servers', ["%s:443" % self.servername], - 'RESTPROXY') - self.setup_patches() - - # Mock method SSL lib uses to grab cert from server - self.sslgetcert_m = mock.patch(SSLGETCERT, create=True).start() - self.sslgetcert_m.return_value = self.host_cert_val - - # Mock methods that write and read certs from the file-system - self.fileput_m = mock.patch(FILEPUT, create=True).start() - self.certcomb_m = mock.patch(CERTCOMBINER, create=True).start() - self.getcacerts_m = mock.patch(GETCACERTS, create=True).start() - - # this is used to configure what certificate contents the fake HTTPS - # lib should expect to receive - self.fake_certget_m = mock.patch(FAKECERTGET, create=True).start() - - def setUp(self): - super(test_ssl_certificate_base, self).setUp(self.plugin_str) - self.setup_db() - - -class TestSslSticky(test_ssl_certificate_base): - - def setUp(self): - self.setup_config_files() - cfg.CONF.set_override('server_ssl', True, 'RESTPROXY') - cfg.CONF.set_override('ssl_sticky', True, 'RESTPROXY') - self._setUp() - # Set fake HTTPS connection's expectation - self.fake_certget_m.return_value = self.host_cert_val - # No CA certs for this test - self.getcacerts_m.return_value = [] - super(TestSslSticky, self).setUp() - - def test_sticky_cert(self): - # SSL connection should be successful and cert should be cached - with contextlib.nested( - mock.patch(HTTPS, new=fake_server.HTTPSHostValidation), - self.network() - ): - # CA certs should have been checked for - self.getcacerts_m.assert_has_calls([mock.call(self.ca_certs_path)]) - # cert should have been fetched via SSL lib - self.sslgetcert_m.assert_has_calls( - [mock.call((self.servername, 443), - ssl_version=ssl.PROTOCOL_TLSv1)] - ) - - # cert should have been recorded - self.fileput_m.assert_has_calls([mock.call(self.host_cert_path, - self.host_cert_val)]) - # no ca certs, so host cert only for this combined cert - self.certcomb_m.assert_has_calls([mock.call([self.host_cert_path], - self.comb_cert_path)]) - - -class TestSslHostCert(test_ssl_certificate_base): - - def setUp(self): - self.setup_config_files() - cfg.CONF.set_override('server_ssl', True, 'RESTPROXY') - cfg.CONF.set_override('ssl_sticky', False, 'RESTPROXY') - self.httpsPatch = mock.patch(HTTPS, create=True, - new=fake_server.HTTPSHostValidation) - self.httpsPatch.start() - self._setUp() - # Set fake HTTPS connection's expectation - self.fake_certget_m.return_value = self.host_cert_val - # No CA certs for this test - self.getcacerts_m.return_value = [] - # Pretend host cert exists - self.hcertpath_p = mock.patch(GETHOSTCERT, - return_value=(self.host_cert_path, True), - create=True).start() - super(TestSslHostCert, self).setUp() - - def test_host_cert(self): - # SSL connection should be successful because of pre-configured cert - with self.network(): - self.hcertpath_p.assert_has_calls([ - mock.call(os.path.join(self.cert_base, 'host_certs'), - self.servername) - ]) - # sticky is disabled, no fetching allowed - self.assertFalse(self.sslgetcert_m.call_count) - # no ca certs, so host cert is only for this combined cert - self.certcomb_m.assert_has_calls([mock.call([self.host_cert_path], - self.comb_cert_path)]) - - -class TestSslCaCert(test_ssl_certificate_base): - - def setUp(self): - self.setup_config_files() - cfg.CONF.set_override('server_ssl', True, 'RESTPROXY') - cfg.CONF.set_override('ssl_sticky', False, 'RESTPROXY') - self.httpsPatch = mock.patch(HTTPS, create=True, - new=fake_server.HTTPSCAValidation) - self.httpsPatch.start() - self._setUp() - - # pretend to have a few ca certs - self.getcacerts_m.return_value = ['ca1.pem', 'ca2.pem'] - - # Set fake HTTPS connection's expectation - self.fake_certget_m.return_value = 'DUMMYCERTIFICATEAUTHORITY' - - super(TestSslCaCert, self).setUp() - - def test_ca_cert(self): - # SSL connection should be successful because CA cert was present - # If not, attempting to create a network would raise an exception - with self.network(): - # sticky is disabled, no fetching allowed - self.assertFalse(self.sslgetcert_m.call_count) - # 2 CAs and no host cert so combined should only contain both CAs - self.certcomb_m.assert_has_calls([mock.call(['ca1.pem', 'ca2.pem'], - self.comb_cert_path)]) - - -class TestSslWrongHostCert(test_ssl_certificate_base): - - def setUp(self): - self.setup_config_files() - cfg.CONF.set_override('server_ssl', True, 'RESTPROXY') - cfg.CONF.set_override('ssl_sticky', True, 'RESTPROXY') - self._setUp() - - # Set fake HTTPS connection's expectation to something wrong - self.fake_certget_m.return_value = 'OTHERCERT' - - # No CA certs for this test - self.getcacerts_m.return_value = [] - - # Pretend host cert exists - self.hcertpath_p = mock.patch(GETHOSTCERT, - return_value=(self.host_cert_path, True), - create=True).start() - super(TestSslWrongHostCert, self).setUp() - - def test_error_no_cert(self): - # since there will already be a host cert, sticky should not take - # effect and there will be an error because the host cert's contents - # will be incorrect - tid = test_api_v2._uuid() - data = {} - data['network'] = {'tenant_id': tid, 'name': 'name', - 'admin_state_up': True} - with mock.patch(HTTPS, new=fake_server.HTTPSHostValidation): - req = self.new_create_request('networks', data, 'json') - res = req.get_response(self.api) - self.assertEqual(res.status_int, - webob.exc.HTTPInternalServerError.code) - self.hcertpath_p.assert_has_calls([ - mock.call(os.path.join(self.cert_base, 'host_certs'), - self.servername) - ]) - # sticky is enabled, but a host cert already exists so it shant fetch - self.assertFalse(self.sslgetcert_m.call_count) - # no ca certs, so host cert only for this combined cert - self.certcomb_m.assert_has_calls([mock.call([self.host_cert_path], - self.comb_cert_path)]) - - -class TestSslNoValidation(test_ssl_certificate_base): - - def setUp(self): - self.setup_config_files() - cfg.CONF.set_override('server_ssl', True, 'RESTPROXY') - cfg.CONF.set_override('ssl_sticky', False, 'RESTPROXY') - cfg.CONF.set_override('no_ssl_validation', True, 'RESTPROXY') - self._setUp() - super(TestSslNoValidation, self).setUp() - - def test_validation_disabled(self): - # SSL connection should be successful without any certificates - # If not, attempting to create a network will raise an exception - with contextlib.nested( - mock.patch(HTTPS, new=fake_server.HTTPSNoValidation), - self.network() - ): - # no sticky grabbing and no cert combining with no enforcement - self.assertFalse(self.sslgetcert_m.call_count) - self.assertFalse(self.certcomb_m.call_count) diff --git a/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py b/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py deleted file mode 100644 index fe49b81e115..00000000000 --- a/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright 2014 Big Switch Networks, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import contextlib -import functools - -import mock -from oslo_serialization import jsonutils - -from neutron import context as neutron_context -from neutron.extensions import portbindings -from neutron import manager -from neutron.plugins.bigswitch import servermanager -from neutron.plugins.ml2 import config as ml2_config -from neutron.plugins.ml2.drivers.mech_bigswitch import driver as bsn_driver -from neutron.plugins.ml2.drivers import type_vlan as vlan_config -import neutron.tests.unit.bigswitch.test_restproxy_plugin as trp -from neutron.tests.unit.ml2 import test_ml2_plugin -from neutron.tests.unit import test_db_plugin - -PHYS_NET = 'physnet1' -VLAN_START = 1000 -VLAN_END = 1100 -SERVER_MANAGER = 'neutron.plugins.bigswitch.servermanager' -SERVER_POOL = SERVER_MANAGER + '.ServerPool' -DRIVER_MOD = 'neutron.plugins.ml2.drivers.mech_bigswitch.driver' -DRIVER = DRIVER_MOD + '.BigSwitchMechanismDriver' - - -class TestBigSwitchMechDriverBase(trp.BigSwitchProxyPluginV2TestCase): - - def setUp(self): - # Configure the ML2 mechanism drivers and network types - ml2_opts = { - 'mechanism_drivers': ['bigswitch'], - 'tenant_network_types': ['vlan'], - } - for opt, val in ml2_opts.items(): - ml2_config.cfg.CONF.set_override(opt, val, 'ml2') - - # Configure the ML2 VLAN parameters - phys_vrange = ':'.join([PHYS_NET, str(VLAN_START), str(VLAN_END)]) - vlan_config.cfg.CONF.set_override('network_vlan_ranges', - [phys_vrange], - 'ml2_type_vlan') - super(TestBigSwitchMechDriverBase, - self).setUp(test_ml2_plugin.PLUGIN_NAME) - - -class TestBigSwitchMechDriverNetworksV2(test_db_plugin.TestNetworksV2, - TestBigSwitchMechDriverBase): - pass - - -class TestBigSwitchMechDriverPortsV2(test_db_plugin.TestPortsV2, - TestBigSwitchMechDriverBase): - - VIF_TYPE = portbindings.VIF_TYPE_OVS - - def setUp(self): - super(TestBigSwitchMechDriverPortsV2, self).setUp() - self.port_create_status = 'DOWN' - - def test_update_port_status_build(self): - with self.port() as port: - self.assertEqual(port['port']['status'], 'DOWN') - self.assertEqual(self.port_create_status, 'DOWN') - - def test_bind_ivs_port(self): - host_arg = {portbindings.HOST_ID: 'hostname'} - with contextlib.nested( - mock.patch(SERVER_POOL + '.rest_get_switch', return_value=True), - self.port(arg_list=(portbindings.HOST_ID,), **host_arg) - ) as (rmock, port): - rmock.assert_called_once_with('hostname') - p = port['port'] - self.assertEqual('ACTIVE', p['status']) - self.assertEqual('hostname', p[portbindings.HOST_ID]) - self.assertEqual(portbindings.VIF_TYPE_IVS, - p[portbindings.VIF_TYPE]) - - def test_dont_bind_non_ivs_port(self): - host_arg = {portbindings.HOST_ID: 'hostname'} - with contextlib.nested( - mock.patch(SERVER_POOL + '.rest_get_switch', - side_effect=servermanager.RemoteRestError( - reason='No such switch', status=404)), - self.port(arg_list=(portbindings.HOST_ID,), **host_arg) - ) as (rmock, port): - rmock.assert_called_once_with('hostname') - p = port['port'] - self.assertNotEqual(portbindings.VIF_TYPE_IVS, - p[portbindings.VIF_TYPE]) - - def test_bind_port_cache(self): - with contextlib.nested( - self.subnet(), - mock.patch(SERVER_POOL + '.rest_get_switch', return_value=True) - ) as (sub, rmock): - makeport = functools.partial(self.port, **{ - 'subnet': sub, 'arg_list': (portbindings.HOST_ID,), - portbindings.HOST_ID: 'hostname'}) - - with contextlib.nested(makeport(), makeport(), - makeport()) as ports: - # response from first should be cached - rmock.assert_called_once_with('hostname') - for port in ports: - self.assertEqual(portbindings.VIF_TYPE_IVS, - port['port'][portbindings.VIF_TYPE]) - rmock.reset_mock() - # expired cache should result in new calls - mock.patch(DRIVER_MOD + '.CACHE_VSWITCH_TIME', new=0).start() - with contextlib.nested(makeport(), makeport(), - makeport()) as ports: - self.assertEqual(3, rmock.call_count) - for port in ports: - self.assertEqual(portbindings.VIF_TYPE_IVS, - port['port'][portbindings.VIF_TYPE]) - - def test_create404_triggers_background_sync(self): - # allow the async background thread to run for this test - self.spawn_p.stop() - with contextlib.nested( - mock.patch(SERVER_POOL + '.rest_create_port', - side_effect=servermanager.RemoteRestError( - reason=servermanager.NXNETWORK, status=404)), - mock.patch(DRIVER + '._send_all_data'), - self.port(**{'device_id': 'devid', 'binding:host_id': 'host', - 'arg_list': ('binding:host_id',)}) - ) as (mock_http, mock_send_all, p): - # wait for thread to finish - mm = manager.NeutronManager.get_plugin().mechanism_manager - bigdriver = mm.mech_drivers['bigswitch'].obj - bigdriver.evpool.waitall() - mock_send_all.assert_has_calls([ - mock.call( - send_routers=False, send_ports=True, - send_floating_ips=False, - triggered_by_tenant=p['port']['tenant_id'] - ) - ]) - self.spawn_p.start() - - def test_udpate404_triggers_background_sync(self): - with contextlib.nested( - mock.patch(DRIVER + '.async_port_create', - side_effect=servermanager.RemoteRestError( - reason=servermanager.NXNETWORK, status=404)), - mock.patch(DRIVER + '._send_all_data'), - self.port() - ) as (mock_update, mock_send_all, p): - plugin = manager.NeutronManager.get_plugin() - context = neutron_context.get_admin_context() - plugin.update_port(context, p['port']['id'], - {'port': {'device_id': 'devid', - 'binding:host_id': 'host'}}) - mock_send_all.assert_has_calls([ - mock.call( - send_routers=False, send_ports=True, - send_floating_ips=False, - triggered_by_tenant=p['port']['tenant_id'] - ) - ]) - - def test_backend_request_contents(self): - with contextlib.nested( - mock.patch(SERVER_POOL + '.rest_create_port'), - self.port(**{'device_id': 'devid', 'binding:host_id': 'host', - 'arg_list': ('binding:host_id',)}) - ) as (mock_rest, p): - # make sure basic expected keys are present in the port body - pb = mock_rest.mock_calls[0][1][2] - self.assertEqual('host', pb['binding:host_id']) - self.assertIn('bound_segment', pb) - self.assertIn('network', pb) - - def test_bind_external_port(self): - ext_id = jsonutils.dumps({'type': 'vlan', 'chassis_id': 'FF', - 'port_id': '1'}) - port_kwargs = { - portbindings.HOST_ID: ext_id, - 'device_owner': bsn_driver.EXTERNAL_PORT_OWNER - } - with contextlib.nested( - mock.patch(SERVER_POOL + '.rest_create_port'), - self.port(arg_list=(portbindings.HOST_ID,), **port_kwargs) - ) as (rmock, port): - create_body = rmock.mock_calls[-1][1][2] - self.assertIsNotNone(create_body['bound_segment']) - self.assertEqual(create_body[portbindings.HOST_ID], ext_id) - - def test_req_context_header_present(self): - with contextlib.nested( - mock.patch(SERVER_MANAGER + '.ServerProxy.rest_call'), - self.port(**{'device_id': 'devid', 'binding:host_id': 'host'}) - ) as (mock_rest, p): - headers = mock_rest.mock_calls[0][1][3] - self.assertIn('X-REQ-CONTEXT', headers)