Merge "BigSwitch: Move config and REST to diff modules"
This commit is contained in:
commit
c009212cb6
@ -4,13 +4,13 @@
|
|||||||
# All configuration for this plugin is in section '[restproxy]'
|
# All configuration for this plugin is in section '[restproxy]'
|
||||||
#
|
#
|
||||||
# The following parameters are supported:
|
# The following parameters are supported:
|
||||||
# servers : <host:port>[,<host:port>]* (Error if not set)
|
# servers : <host:port>[,<host:port>]* (Error if not set)
|
||||||
# server_auth : <username:password> (default: no auth)
|
# server_auth : <username:password> (default: no auth)
|
||||||
# server_ssl : True | False (default: False)
|
# server_ssl : True | False (default: False)
|
||||||
# sync_data : True | False (default: False)
|
# sync_data : True | False (default: False)
|
||||||
# server_timeout : 10 (default: 10 seconds)
|
# server_timeout : 10 (default: 10 seconds)
|
||||||
# neutron_id: <string> (default: neutron-<hostname>)
|
# neutron_id : <string> (default: neutron-<hostname>)
|
||||||
# add_meta_server_route: True | False (default: True)
|
# add_meta_server_route : True | False (default: True)
|
||||||
#
|
#
|
||||||
|
|
||||||
# A comma separated list of BigSwitch or Floodlight servers and port numbers. The plugin proxies the requests to the BigSwitch/Floodlight server, which performs the networking configuration. Note that only one server is needed per deployment, but you may wish to deploy multiple servers to support failover.
|
# A comma separated list of BigSwitch or Floodlight servers and port numbers. The plugin proxies the requests to the BigSwitch/Floodlight server, which performs the networking configuration. Note that only one server is needed per deployment, but you may wish to deploy multiple servers to support failover.
|
||||||
@ -19,11 +19,11 @@ servers=localhost:8080
|
|||||||
# The username and password for authenticating against the BigSwitch or Floodlight controller.
|
# The username and password for authenticating against the BigSwitch or Floodlight controller.
|
||||||
# server_auth=username:password
|
# server_auth=username:password
|
||||||
|
|
||||||
# If True, Use SSL when connecting to the BigSwitch or Floodlight controller.
|
# Use SSL when connecting to the BigSwitch or Floodlight controller.
|
||||||
# server_ssl=True
|
# server_ssl=False
|
||||||
|
|
||||||
# Sync data on connect
|
# Sync data on connect
|
||||||
# sync_data=True
|
# sync_data=False
|
||||||
|
|
||||||
# Maximum number of seconds to wait for proxy request to connect and complete.
|
# Maximum number of seconds to wait for proxy request to connect and complete.
|
||||||
# server_timeout=10
|
# server_timeout=10
|
||||||
|
87
neutron/plugins/bigswitch/config.py
Normal file
87
neutron/plugins/bigswitch/config.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
||||||
|
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||||
|
# @author: Kevin Benton, Big Switch Networks, Inc.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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 BigSwitch or Floodlight "
|
||||||
|
"servers and port numbers. The plugin proxies the "
|
||||||
|
"requests to the BigSwitch/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', default=None, secret=True,
|
||||||
|
help=_("The username and password for authenticating against "
|
||||||
|
" the BigSwitch or Floodlight controller.")),
|
||||||
|
cfg.BoolOpt('server_ssl', default=False,
|
||||||
|
help=_("If True, Use SSL when connecting to the BigSwitch or "
|
||||||
|
"Floodlight controller.")),
|
||||||
|
cfg.BoolOpt('sync_data', default=False,
|
||||||
|
help=_("Sync data on connect")),
|
||||||
|
cfg.IntOpt('server_timeout', default=10,
|
||||||
|
help=_("Maximum number of seconds to wait for proxy request "
|
||||||
|
"to connect and complete.")),
|
||||||
|
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 <tenant>:<source>:<destination>:<action>"
|
||||||
|
" 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.')))
|
||||||
|
|
||||||
|
|
||||||
|
def register_config():
|
||||||
|
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
|
||||||
|
cfg.CONF.register_opts(router_opts, "ROUTER")
|
||||||
|
cfg.CONF.register_opts(nova_opts, "NOVA")
|
@ -44,11 +44,7 @@ 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.
|
on port-attach) on an additional PUT to do a bulk dump of all persistent data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
|
||||||
import copy
|
import copy
|
||||||
import httplib
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
@ -58,7 +54,6 @@ from neutron.common import constants as const
|
|||||||
from neutron.common import exceptions
|
from neutron.common import exceptions
|
||||||
from neutron.common import rpc as q_rpc
|
from neutron.common import rpc as q_rpc
|
||||||
from neutron.common import topics
|
from neutron.common import topics
|
||||||
from neutron.common import utils
|
|
||||||
from neutron import context as qcontext
|
from neutron import context as qcontext
|
||||||
from neutron.db import agents_db
|
from neutron.db import agents_db
|
||||||
from neutron.db import agentschedulers_db
|
from neutron.db import agentschedulers_db
|
||||||
@ -76,349 +71,20 @@ from neutron.openstack.common import excutils
|
|||||||
from neutron.openstack.common import importutils
|
from neutron.openstack.common import importutils
|
||||||
from neutron.openstack.common import log as logging
|
from neutron.openstack.common import log as logging
|
||||||
from neutron.openstack.common import rpc
|
from neutron.openstack.common import rpc
|
||||||
|
from neutron.plugins.bigswitch import config as pl_config
|
||||||
from neutron.plugins.bigswitch.db import porttracker_db
|
from neutron.plugins.bigswitch.db import porttracker_db
|
||||||
from neutron.plugins.bigswitch import extensions
|
from neutron.plugins.bigswitch import extensions
|
||||||
from neutron.plugins.bigswitch import routerrule_db
|
from neutron.plugins.bigswitch import routerrule_db
|
||||||
|
from neutron.plugins.bigswitch import servermanager
|
||||||
from neutron.plugins.bigswitch.version import version_string_with_vcs
|
from neutron.plugins.bigswitch.version import version_string_with_vcs
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
restproxy_opts = [
|
|
||||||
cfg.StrOpt('servers', default='localhost:8800',
|
|
||||||
help=_("A comma separated list of BigSwitch or Floodlight "
|
|
||||||
"servers and port numbers. The plugin proxies the "
|
|
||||||
"requests to the BigSwitch/Floodlight server, "
|
|
||||||
"which performs the networking configuration. Note that "
|
|
||||||
"only one server is needed per deployment, but you may "
|
|
||||||
"wish to deploy multiple servers to support failover.")),
|
|
||||||
cfg.StrOpt('server_auth', default=None, secret=True,
|
|
||||||
help=_("The username and password for authenticating against "
|
|
||||||
" the BigSwitch or Floodlight controller.")),
|
|
||||||
cfg.BoolOpt('server_ssl', default=False,
|
|
||||||
help=_("If True, Use SSL when connecting to the BigSwitch or "
|
|
||||||
"Floodlight controller.")),
|
|
||||||
cfg.BoolOpt('sync_data', default=False,
|
|
||||||
help=_("Sync data on connect")),
|
|
||||||
cfg.IntOpt('server_timeout', default=10,
|
|
||||||
help=_("Maximum number of seconds to wait for proxy request "
|
|
||||||
"to connect and complete.")),
|
|
||||||
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")),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
|
|
||||||
|
|
||||||
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 <tenant>:<source>:<destination>:<action>"
|
|
||||||
" Use an * to specify default for all tenants.")),
|
|
||||||
cfg.IntOpt('max_router_rules', default=200,
|
|
||||||
help=_("Maximum number of router rules")),
|
|
||||||
]
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(router_opts, "ROUTER")
|
|
||||||
|
|
||||||
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.')))
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(nova_opts, "NOVA")
|
|
||||||
|
|
||||||
|
|
||||||
# The following are used to invoke the API on the external controller
|
|
||||||
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"
|
|
||||||
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"
|
|
||||||
SUCCESS_CODES = range(200, 207)
|
|
||||||
FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
|
|
||||||
504, 505]
|
|
||||||
SYNTAX_ERROR_MESSAGE = _('Syntax error in server config file, aborting plugin')
|
SYNTAX_ERROR_MESSAGE = _('Syntax error in server config file, aborting plugin')
|
||||||
BASE_URI = '/networkService/v1.1'
|
|
||||||
ORCHESTRATION_SERVICE_ID = 'Neutron v2.0'
|
|
||||||
METADATA_SERVER_IP = '169.254.169.254'
|
METADATA_SERVER_IP = '169.254.169.254'
|
||||||
|
|
||||||
|
|
||||||
class RemoteRestError(exceptions.NeutronException):
|
|
||||||
message = _("Error in REST call to remote network "
|
|
||||||
"controller: %(reason)s")
|
|
||||||
|
|
||||||
|
|
||||||
class ServerProxy(object):
|
|
||||||
"""REST server proxy to a network controller."""
|
|
||||||
|
|
||||||
def __init__(self, server, port, ssl, auth, neutron_id, timeout,
|
|
||||||
base_uri, name):
|
|
||||||
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
|
|
||||||
if auth:
|
|
||||||
self.auth = 'Basic ' + base64.encodestring(auth).strip()
|
|
||||||
|
|
||||||
def rest_call(self, action, resource, data, headers):
|
|
||||||
uri = self.base_uri + resource
|
|
||||||
body = json.dumps(data)
|
|
||||||
if not headers:
|
|
||||||
headers = {}
|
|
||||||
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 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, action=%(action)s, "
|
|
||||||
"data=%(data)r, headers=%(headers)r"),
|
|
||||||
{'resource': resource, 'data': data, 'headers': headers,
|
|
||||||
'action': action})
|
|
||||||
|
|
||||||
conn = None
|
|
||||||
if self.ssl:
|
|
||||||
conn = httplib.HTTPSConnection(
|
|
||||||
self.server, self.port, timeout=self.timeout)
|
|
||||||
if conn is None:
|
|
||||||
LOG.error(_('ServerProxy: Could not establish HTTPS '
|
|
||||||
'connection'))
|
|
||||||
return 0, None, None, None
|
|
||||||
else:
|
|
||||||
conn = httplib.HTTPConnection(
|
|
||||||
self.server, self.port, timeout=self.timeout)
|
|
||||||
if conn is None:
|
|
||||||
LOG.error(_('ServerProxy: Could not establish HTTP '
|
|
||||||
'connection'))
|
|
||||||
return 0, None, None, None
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn.request(action, uri, body, headers)
|
|
||||||
response = conn.getresponse()
|
|
||||||
respstr = response.read()
|
|
||||||
respdata = respstr
|
|
||||||
if response.status in self.success_codes:
|
|
||||||
try:
|
|
||||||
respdata = json.loads(respstr)
|
|
||||||
except ValueError:
|
|
||||||
# response was not JSON, ignore the exception
|
|
||||||
pass
|
|
||||||
ret = (response.status, response.reason, respstr, respdata)
|
|
||||||
except (socket.timeout, socket.error) as e:
|
|
||||||
LOG.error(_('ServerProxy: %(action)s failure, %(e)r'),
|
|
||||||
{'action': action, 'e': e})
|
|
||||||
ret = 0, None, None, None
|
|
||||||
conn.close()
|
|
||||||
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):
|
|
||||||
|
|
||||||
def __init__(self, timeout=10,
|
|
||||||
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
|
|
||||||
timeout = cfg.CONF.RESTPROXY.server_timeout
|
|
||||||
if timeout is not None:
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
# validate config
|
|
||||||
if not servers:
|
|
||||||
raise cfg.Error(_('Servers not defined. Aborting plugin'))
|
|
||||||
if any((len(spl) != 2) for spl in [sp.split(':', 1)
|
|
||||||
for sp in servers.split(',')]):
|
|
||||||
raise cfg.Error(_('Servers must be defined as <ip>:<port>'))
|
|
||||||
self.servers = [
|
|
||||||
self.server_proxy_for(server, int(port))
|
|
||||||
for server, port in (s.rsplit(':', 1) for s in servers.split(','))
|
|
||||||
]
|
|
||||||
LOG.debug(_("ServerPool: initialization done"))
|
|
||||||
|
|
||||||
def server_proxy_for(self, server, port):
|
|
||||||
return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
|
|
||||||
self.timeout, self.base_uri, self.name)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@utils.synchronized('bsn-rest-call', external=True)
|
|
||||||
def rest_call(self, action, resource, data, headers, ignore_codes):
|
|
||||||
good_first = sorted(self.servers, key=lambda x: x.failed)
|
|
||||||
for active_server in good_first:
|
|
||||||
ret = active_server.rest_call(action, resource, data, headers)
|
|
||||||
if not self.server_failure(ret, ignore_codes):
|
|
||||||
active_server.failed = False
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
LOG.error(_('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(_("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
|
|
||||||
|
|
||||||
# All servers failed, reset server list and try again next time
|
|
||||||
LOG.error(_('ServerProxy: %(action)s failure for all servers: '
|
|
||||||
'%(server)r'),
|
|
||||||
{'action': action,
|
|
||||||
'server': tuple((s.server,
|
|
||||||
s.port) for s in self.servers)})
|
|
||||||
return (0, None, None, None)
|
|
||||||
|
|
||||||
def rest_action(self, action, resource, data='', errstr='%s',
|
|
||||||
ignore_codes=[], headers=None):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
if not ignore_codes and action == 'DELETE':
|
|
||||||
ignore_codes = [404]
|
|
||||||
resp = self.rest_call(action, resource, data, headers, ignore_codes)
|
|
||||||
if self.server_failure(resp, ignore_codes):
|
|
||||||
LOG.error(errstr, resp[2])
|
|
||||||
raise RemoteRestError(reason=resp[2])
|
|
||||||
if resp[0] in ignore_codes:
|
|
||||||
LOG.warning(_("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(_("No device 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
|
|
||||||
self.rest_create_port(tenant_id, net_id, port)
|
|
||||||
|
|
||||||
|
|
||||||
class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
||||||
|
|
||||||
RPC_API_VERSION = '1.1'
|
RPC_API_VERSION = '1.1'
|
||||||
@ -585,9 +251,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
|
|
||||||
resource['state'] = ('UP' if resource.pop('admin_state_up',
|
resource['state'] = ('UP' if resource.pop('admin_state_up',
|
||||||
True) else 'DOWN')
|
True) else 'DOWN')
|
||||||
|
resource.pop('status', None)
|
||||||
if 'status' in resource:
|
|
||||||
del resource['status']
|
|
||||||
|
|
||||||
return resource
|
return resource
|
||||||
|
|
||||||
@ -659,7 +323,7 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
|||||||
def __init__(self, server_timeout=None):
|
def __init__(self, server_timeout=None):
|
||||||
LOG.info(_('NeutronRestProxy: Starting plugin. Version=%s'),
|
LOG.info(_('NeutronRestProxy: Starting plugin. Version=%s'),
|
||||||
version_string_with_vcs())
|
version_string_with_vcs())
|
||||||
|
pl_config.register_config()
|
||||||
# init DB, proxy's persistent store defaults to in-memory sql-lite DB
|
# init DB, proxy's persistent store defaults to in-memory sql-lite DB
|
||||||
db.configure_db()
|
db.configure_db()
|
||||||
|
|
||||||
@ -669,7 +333,7 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
|||||||
self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route
|
self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route
|
||||||
|
|
||||||
# init network ctrl connections
|
# init network ctrl connections
|
||||||
self.servers = ServerPool(server_timeout, BASE_URI)
|
self.servers = servermanager.ServerPool(server_timeout)
|
||||||
|
|
||||||
# init dhcp support
|
# init dhcp support
|
||||||
self.topic = topics.PLUGIN
|
self.topic = topics.PLUGIN
|
||||||
@ -1180,7 +844,7 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
|
|||||||
# create floatingip on the network controller
|
# create floatingip on the network controller
|
||||||
try:
|
try:
|
||||||
self._send_floatingip_update(context)
|
self._send_floatingip_update(context)
|
||||||
except RemoteRestError as e:
|
except servermanager.RemoteRestError as e:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.error(
|
LOG.error(
|
||||||
_("NeutronRestProxyV2: Unable to create remote "
|
_("NeutronRestProxyV2: Unable to create remote "
|
||||||
|
320
neutron/plugins/bigswitch/servermanager.py
Normal file
320
neutron/plugins/bigswitch/servermanager.py
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
||||||
|
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||||
|
# @author: Kevin Benton, Big Switch Networks, Inc.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
- HTTP Authentication
|
||||||
|
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
import httplib
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from neutron.common import exceptions
|
||||||
|
from neutron.common import utils
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# The following are used to invoke the API on the external controller
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteRestError(exceptions.NeutronException):
|
||||||
|
message = _("Error in REST call to remote network "
|
||||||
|
"controller: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ServerProxy(object):
|
||||||
|
"""REST server proxy to a network controller."""
|
||||||
|
|
||||||
|
def __init__(self, server, port, ssl, auth, neutron_id, timeout,
|
||||||
|
base_uri, name):
|
||||||
|
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
|
||||||
|
if auth:
|
||||||
|
self.auth = 'Basic ' + base64.encodestring(auth).strip()
|
||||||
|
|
||||||
|
def rest_call(self, action, resource, data, headers):
|
||||||
|
uri = self.base_uri + resource
|
||||||
|
body = json.dumps(data)
|
||||||
|
if not headers:
|
||||||
|
headers = {}
|
||||||
|
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 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})
|
||||||
|
|
||||||
|
conn = None
|
||||||
|
if self.ssl:
|
||||||
|
conn = httplib.HTTPSConnection(
|
||||||
|
self.server, self.port, timeout=self.timeout)
|
||||||
|
if conn is None:
|
||||||
|
LOG.error(_('ServerProxy: Could not establish HTTPS '
|
||||||
|
'connection'))
|
||||||
|
return 0, None, None, None
|
||||||
|
else:
|
||||||
|
conn = httplib.HTTPConnection(
|
||||||
|
self.server, self.port, timeout=self.timeout)
|
||||||
|
if conn is None:
|
||||||
|
LOG.error(_('ServerProxy: Could not establish HTTP '
|
||||||
|
'connection'))
|
||||||
|
return 0, None, None, None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.request(action, uri, body, headers)
|
||||||
|
response = conn.getresponse()
|
||||||
|
respstr = response.read()
|
||||||
|
respdata = respstr
|
||||||
|
if response.status in self.success_codes:
|
||||||
|
try:
|
||||||
|
respdata = json.loads(respstr)
|
||||||
|
except ValueError:
|
||||||
|
# response was not JSON, ignore the exception
|
||||||
|
pass
|
||||||
|
ret = (response.status, response.reason, respstr, respdata)
|
||||||
|
except (socket.timeout, socket.error) as e:
|
||||||
|
LOG.error(_('ServerProxy: %(action)s failure, %(e)r'),
|
||||||
|
{'action': action, 'e': e})
|
||||||
|
ret = 0, None, None, None
|
||||||
|
conn.close()
|
||||||
|
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):
|
||||||
|
|
||||||
|
def __init__(self, timeout=10,
|
||||||
|
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.timeout = cfg.CONF.RESTPROXY.server_timeout
|
||||||
|
default_port = 8000
|
||||||
|
if timeout is not None:
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
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)for spl in [sp.rsplit(':', 1)
|
||||||
|
for sp in servers]):
|
||||||
|
raise cfg.Error(_('Servers must be defined as <ip>:<port>. '
|
||||||
|
'Configuration was %s') % servers)
|
||||||
|
self.servers = [
|
||||||
|
self.server_proxy_for(server, int(port))
|
||||||
|
for server, port in (s.rsplit(':', 1) for s in servers)
|
||||||
|
]
|
||||||
|
LOG.debug(_("ServerPool: initialization done"))
|
||||||
|
|
||||||
|
def server_proxy_for(self, server, port):
|
||||||
|
return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
|
||||||
|
self.timeout, self.base_uri, self.name)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@utils.synchronized('bsn-rest-call', external=True)
|
||||||
|
def rest_call(self, action, resource, data, headers, ignore_codes):
|
||||||
|
good_first = sorted(self.servers, key=lambda x: x.failed)
|
||||||
|
for active_server in good_first:
|
||||||
|
ret = active_server.rest_call(action, resource, data, headers)
|
||||||
|
if not self.server_failure(ret, ignore_codes):
|
||||||
|
active_server.failed = False
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
LOG.error(_('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(_("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
|
||||||
|
|
||||||
|
# All servers failed, reset server list and try again next time
|
||||||
|
LOG.error(_('ServerProxy: %(action)s failure for all servers: '
|
||||||
|
'%(server)r'),
|
||||||
|
{'action': action,
|
||||||
|
'server': tuple((s.server,
|
||||||
|
s.port) for s in self.servers)})
|
||||||
|
return (0, None, None, None)
|
||||||
|
|
||||||
|
def rest_action(self, action, resource, data='', errstr='%s',
|
||||||
|
ignore_codes=[], headers=None):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if not ignore_codes and action == 'DELETE':
|
||||||
|
ignore_codes = [404]
|
||||||
|
resp = self.rest_call(action, resource, data, headers, ignore_codes)
|
||||||
|
if self.server_failure(resp, ignore_codes):
|
||||||
|
LOG.error(errstr, resp[2])
|
||||||
|
raise RemoteRestError(reason=resp[2])
|
||||||
|
if resp[0] in ignore_codes:
|
||||||
|
LOG.warning(_("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(_("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)
|
@ -22,9 +22,10 @@ from oslo.config import cfg
|
|||||||
from neutron import context as ctx
|
from neutron import context as ctx
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.openstack.common import log
|
from neutron.openstack.common import log
|
||||||
|
from neutron.plugins.bigswitch import config as pl_config
|
||||||
from neutron.plugins.bigswitch.db import porttracker_db
|
from neutron.plugins.bigswitch.db import porttracker_db
|
||||||
from neutron.plugins.bigswitch.plugin import NeutronRestProxyV2Base
|
from neutron.plugins.bigswitch.plugin import NeutronRestProxyV2Base
|
||||||
from neutron.plugins.bigswitch.plugin import ServerPool
|
from neutron.plugins.bigswitch.servermanager import ServerPool
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
|
||||||
|
|
||||||
@ -43,6 +44,8 @@ class BigSwitchMechanismDriver(NeutronRestProxyV2Base,
|
|||||||
def initialize(self, server_timeout=None):
|
def initialize(self, server_timeout=None):
|
||||||
LOG.debug(_('Initializing driver'))
|
LOG.debug(_('Initializing driver'))
|
||||||
|
|
||||||
|
# register plugin config opts
|
||||||
|
pl_config.register_config()
|
||||||
# backend doesn't support bulk operations yet
|
# backend doesn't support bulk operations yet
|
||||||
self.native_bulk_support = False
|
self.native_bulk_support = False
|
||||||
|
|
||||||
|
@ -17,15 +17,17 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from mock import patch
|
import mock
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
import neutron.common.test_lib as test_lib
|
import neutron.common.test_lib as test_lib
|
||||||
from neutron.db import api as db
|
from neutron.db import api as db
|
||||||
|
from neutron.plugins.bigswitch import config
|
||||||
from neutron.tests.unit.bigswitch import fake_server
|
from neutron.tests.unit.bigswitch import fake_server
|
||||||
|
|
||||||
RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin'
|
RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin'
|
||||||
NOTIFIER = 'neutron.plugins.bigswitch.plugin.RpcProxy'
|
NOTIFIER = 'neutron.plugins.bigswitch.plugin.RpcProxy'
|
||||||
|
HTTPCON = 'httplib.HTTPConnection'
|
||||||
|
|
||||||
|
|
||||||
class BigSwitchTestBase(object):
|
class BigSwitchTestBase(object):
|
||||||
@ -37,13 +39,13 @@ class BigSwitchTestBase(object):
|
|||||||
test_lib.test_config['config_files'] = [os.path.join(etc_path,
|
test_lib.test_config['config_files'] = [os.path.join(etc_path,
|
||||||
'restproxy.ini.test')]
|
'restproxy.ini.test')]
|
||||||
self.addCleanup(cfg.CONF.reset)
|
self.addCleanup(cfg.CONF.reset)
|
||||||
|
config.register_config()
|
||||||
|
|
||||||
def setup_patches(self):
|
def setup_patches(self):
|
||||||
self.httpPatch = patch('httplib.HTTPConnection', create=True,
|
self.httpPatch = mock.patch(HTTPCON, create=True,
|
||||||
new=fake_server.HTTPConnectionMock)
|
new=fake_server.HTTPConnectionMock)
|
||||||
self.plugin_notifier_p = patch(NOTIFIER)
|
self.plugin_notifier_p = mock.patch(NOTIFIER)
|
||||||
self.addCleanup(self.plugin_notifier_p.stop)
|
self.addCleanup(mock.patch.stopall)
|
||||||
self.addCleanup(self.httpPatch.stop)
|
|
||||||
self.addCleanup(db.clear_db)
|
self.addCleanup(db.clear_db)
|
||||||
self.plugin_notifier_p.start()
|
self.plugin_notifier_p.start()
|
||||||
self.httpPatch.start()
|
self.httpPatch.start()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user