Merge Brocade Vyatta L3 Plugin Code

Change-Id: I8d7d93d906f275782ef33c37c4ce9b202b72a4c6
changes/78/153378/2
Sripriya 8 years ago
parent 382050e73c
commit b361a94ddb

1
.gitignore vendored

@ -27,3 +27,4 @@ setuptools*.egg/
!/.mailmap
!/.pylintrc
!/.testr.conf
networking_brocade.egg-info/

@ -0,0 +1,51 @@
# Copyright 2015 Brocade Communications System, Inc..
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
cfg.CONF.register_opts([
cfg.StrOpt('tenant_admin_name', help=_('Name of tenant admin user.')),
cfg.StrOpt('tenant_admin_password', secret=True,
help=_('Tenant admin password.')),
cfg.StrOpt('tenant_id',
help=_('UUID of tenant that holds Vyatta vRouter instances.')),
cfg.StrOpt('image_id',
help=_('Nova image id for instances of Vyatta vRouter.')),
cfg.StrOpt('flavor', default=2,
help=_('Nova VM flavor for instances of Vyatta vRouter.')),
cfg.StrOpt('management_network_id',
help=_('Vyatta vRouter management network id.')),
cfg.StrOpt('vrouter_credentials', default="vyatta:vyatta",
help=_('Vyatta vRouter login credentials')),
cfg.IntOpt('nova_poll_interval', default=5,
help=_('Number of seconds between consecutive Nova queries '
'when waiting for router instance status change.')),
cfg.IntOpt('nova_spawn_timeout', default=300,
help=_('Number of seconds to wait for Nova to activate '
'instance before setting resource to error state.')),
cfg.IntOpt('vrouter_poll_interval', default=5,
help=_('Number of seconds between consecutive Vyatta vRouter '
'queries when waiting for router instance boot.')),
cfg.IntOpt('vrouter_boot_timeout', default=300,
help=_('Number of seconds to wait for Vyatta vRouter to boot '
'before setting resource to error state.')),
cfg.StrOpt('keystone_url', help=_('Keystone URL.')),
], "VROUTER")
# setup shortcuts
CONF = cfg.CONF
VROUTER = cfg.CONF.VROUTER

@ -0,0 +1,80 @@
# Copyright 2015 Brocade Communications System, 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.common import exceptions
class CorruptedSystemError(exceptions.NeutronException):
message = _('System contain conflicts: %(description)s.')
class VRouterConnectFailure(exceptions.NeutronException):
"""Couldn't connect to instance."""
message = _("Couldn't connect to Vyatta vRouter [%(ip_address)s].")
class VRouterOperationError(exceptions.NeutronException):
"""Internal Vyatta vRouter exception."""
message = _("Internal Vyatta vRouter exception [%(ip_address)s]:"
"%(reason)s.")
class InvalidVRouterInstance(exceptions.NeutronException):
"""Couldn't find the vrouter instance."""
message = _("Couldn't find Vyatta vRouter instance %(router_id)s.")
class InvalidInstanceConfiguration(exceptions.NeutronException):
"""Invalid vRouter VM instance configuration."""
message = _("Invalid Vyatta vRouter configuration: %(cause)s.")
class InvalidParameter(exceptions.NeutronException):
"""Invalid configuration parameter."""
message = _("Invalid Parameter: %(cause)s.")
class InvalidResponseFormat(exceptions.NeutronException):
message = _('Unparsable vRouter API response: %(details)s')
class WaitTimeoutError(exceptions.NeutronException):
"""Timeout error after waiting for Vyatta vRouter VM creation."""
message = _("Timeout waiting for Vyatta vRouter instance creation.")
class InstanceSpawnError(exceptions.NeutronException):
"""vRouter VM instance spawning error."""
message = _("Failed to spawn Vyatta vRouter VM instance.")
class InvalidL3AgentStateError(exceptions.NeutronException):
message = _('Invalid L3 agent state: %(description)s')
class InvalidVPNServiceError(exceptions.NeutronException):
message = _('Invalid or incomplete VPN service data')
class VPNResourceMappingNotFound(exceptions.NotFound):
message = _('There is no VPN resource mapping for %(kind)s key=%(key)s')
class ResourceNotFound(exceptions.NotFound):
message = _('Resource not found.')
class TableCellNotFound(exceptions.NotFound):
message = _('There is no cell in vRouter status table.')

@ -0,0 +1,65 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
from sqlalchemy.orm import exc as orm_exc
from neutron.db import model_base
from neutron.db import models_v2
from vyatta.common import config as vyatta_config
from vyatta.common import exceptions as v_exc
@six.add_metaclass(abc.ABCMeta)
class FetcherAbstract(object):
value = _unset = object()
def __call__(self, context):
if self.value is self._unset:
self.value = self._lookup(context)
if isinstance(self.value, model_base.BASEV2):
context.session.expunge(self.value)
return self.value
def reset(self):
try:
del self.value
except AttributeError:
pass
@abc.abstractmethod
def _lookup(self, context):
pass
class ManageNetworkFetcher(FetcherAbstract):
def _lookup(self, context):
q = context.session.query(models_v2.Network)
q = q.filter_by(
id=vyatta_config.VROUTER.management_network_id)
try:
value = q.one()
except orm_exc.NoResultFound:
raise v_exc.InvalidParameter(
cause=_('Management network {0} specified in vRouter plugin '
'configuration file does not exist').format(
vyatta_config.VROUTER.management_network_id))
return value
get_management_network = ManageNetworkFetcher()

@ -0,0 +1,133 @@
# Copyright 2015 Brocade Communications System, 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 novaclient.v1_1 import client as novaclient
from neutron.agent.l3 import agent as l3_agent
from neutron.agent.l3 import router_info
from neutron.agent import l3_agent as entry
from neutron.common import constants as l3_constants
from vyatta.common import config as vyatta_config
from vyatta.common import exceptions as v_exc
from vyatta.vrouter import client as vyatta_client
_KEY_VYATTA_EXTRA_DATA = '_vyatta'
_KEY_MANAGEMENT_IP_ADDRESS = 'management_ip_address'
class L3AgentMiddleware(l3_agent.L3NATAgentWithStateReport):
def __init__(self, host, conf=None):
super(L3AgentMiddleware, self).__init__(host, conf)
compute_client = novaclient.Client(
vyatta_config.VROUTER.tenant_admin_name,
vyatta_config.VROUTER.tenant_admin_password,
auth_url=vyatta_config.CONF.nova_admin_auth_url,
service_type="compute",
tenant_id=vyatta_config.VROUTER.tenant_id)
self._vyatta_clients_pool = vyatta_client.ClientsPool(compute_client)
def get_router(self, router_id):
try:
router = self.router_info[router_id]
except KeyError:
raise v_exc.InvalidL3AgentStateError(description=_(
'L3 agent have no info about reouter id={0}').format(
router_id))
return router.router
def get_router_client(self, router_id):
router = self.get_router(router_id)
try:
address = router[_KEY_VYATTA_EXTRA_DATA]
address = address[_KEY_MANAGEMENT_IP_ADDRESS]
except KeyError:
raise v_exc.CorruptedSystemError(
description=('router {0} does not contain vyatta vrouter '
'management ip address').format(router_id))
return self._vyatta_clients_pool.get_by_address(router_id, address)
def _router_added(self, router_id, router):
ri = router_info.RouterInfo(
router_id, router, self.root_helper, self.conf, self.driver)
self.router_info[router_id] = ri
self.process_router_add(ri)
def _router_removed(self, router_id):
ri = self.router_info[router_id]
if ri:
ri.router['gw_port'] = None
ri.router[l3_constants.INTERFACE_KEY] = []
ri.router[l3_constants.FLOATINGIP_KEY] = []
self.process_router(ri)
del self.router_info[router_id]
def _create_router_namespace(self, ri):
"""terminate super call to avoid calls to iptables"""
pass
def _destroy_router_namespace(self, namespace):
"""terminate super call to avoid calls to iptables"""
pass
def _spawn_metadata_proxy(self, router_id, ns_name):
"""terminate super call to avoid calls to iptables"""
pass
def _destroy_metadata_proxy(self, router_id, ns_name):
"""terminate super call to avoid calls to iptables"""
pass
def _handle_router_snat_rules(self, ri, ex_gw_port,
interface_name, action):
"""terminate super call to avoid calls to iptables"""
pass
def _send_gratuitous_arp_packet(self, ns_name, interface_name, ip_address,
distributed=False):
"""terminate super call to avoid calls to iptables"""
pass
def _update_routing_table(self, ri, operation, route):
"""terminate super call to avoid calls to iptables"""
pass
def process_router(self, ri):
pass
def _get_router_info_list_for_tenant(self, routers, tenant_id):
"""Returns the list of router info objects on which to apply the fw."""
router_ids = [
router['id']
for router in routers
if router['tenant_id'] == tenant_id]
router_info_list = []
# Pick up namespaces for Tenant Routers
for rid in router_ids:
# for routers without an interface - get_routers returns
# the router - but this is not yet populated in router_info
if rid not in self.router_info:
continue
router_info_list.append(self.router_info[rid])
return router_info_list
def main():
entry.main(
manager='vyatta.common.l3_agent.L3AgentMiddleware')

@ -0,0 +1,349 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import collections
import functools
import itertools
import re
from vyatta.common import exceptions as v_exc
HWADDR = r'(?:[a-zA-Z0-9]{2}:){5}[a-zA-Z0-9]{2}'
IPv4_ADDR = r'(?:\d+\.){3}\d+'
EMPTY_LINE = re.compile(r'^\s+$')
IFACE = re.compile(r'^(\w+):')
LINK_ETHER = re.compile(r'\s+link/ether\s+({0})'.format(HWADDR))
IP_ADDR = re.compile(r'\s+inet\s+({0})'.format(IPv4_ADDR))
def parse_interfaces(output):
ifaces = []
info = {}
for line in output.splitlines():
if not line or re.match(EMPTY_LINE, line):
continue
m = re.match(IFACE, line)
if m:
if info:
ifaces.append(info)
name = m.group(1)
info = dict(name=name, ip_addrs=[], mac_address=None)
continue
m = re.match(LINK_ETHER, line)
if m:
info['mac_address'] = m.group(1).lower()
m = re.match(IP_ADDR, line)
if m:
info['ip_addrs'].append(m.group(1))
if info:
ifaces.append(info)
return ifaces
class TableParser(collections.Iterable, collections.Sized):
idx_generator = itertools.count()
_S_EMPTY = next(idx_generator)
_S_TITLE_SEPARATOR_INTRO = next(idx_generator)
_S_TITLE_SEPARATOR_FIELD = next(idx_generator)
_S_TITLE_SEPARATOR_SPACE = next(idx_generator)
_S_EXTRACT_HEADERS = next(idx_generator)
_S_VALUE_LINE = next(idx_generator)
_S_STORE_RESULT = next(idx_generator)
del idx_generator
def __init__(self):
collections.Iterable.__init__(self)
self.results = list()
def __iter__(self):
return iter(self.results)
def __len__(self):
return len(self.results)
def __call__(self, stream):
stream = TableTokenizer(stream)
result = None
lines_buffer = list()
fields = list()
offs = 0
state = self._S_EMPTY
for chunk in stream:
if state == self._S_EMPTY:
stream.revert(chunk)
chunk = self._read_until_eol(stream)
chunk = ''.join(chunk)
if not chunk or chunk.isspace():
lines_buffer = list()
continue
offs = 0
fields = list()
lines_buffer.append(chunk)
state = self._S_TITLE_SEPARATOR_INTRO
continue
elif state == self._S_TITLE_SEPARATOR_INTRO:
if chunk == '\n':
lines_buffer = list()
state = self._S_EMPTY
continue
if chunk.isspace():
offs += len(chunk)
continue
stream.revert(chunk)
state = self._S_TITLE_SEPARATOR_FIELD
continue
elif state == self._S_TITLE_SEPARATOR_FIELD:
if chunk == '\n':
stream.revert(None)
state = self._S_EXTRACT_HEADERS
continue
if set(chunk) != set('-'):
raise ValueError((
'Unexpected chunk: {0}. Expect sequence of \'-\' '
'characters').format(chunk))
fields.append((offs, len(chunk)))
offs += len(chunk)
state = self._S_TITLE_SEPARATOR_SPACE
continue
elif state == self._S_TITLE_SEPARATOR_SPACE:
if chunk == '\n':
stream.revert(None)
state = self._S_EXTRACT_HEADERS
continue
if not chunk.isspace():
raise ValueError((
'Unexpected chunk: {0}. Expect '
'whitespace.').format(chunk))
offs += len(chunk)
state = self._S_TITLE_SEPARATOR_FIELD
continue
elif state == self._S_EXTRACT_HEADERS:
if not fields:
lines_buffer = list()
state = self._S_EMPTY
continue
result = self._extract_field_titles(lines_buffer, fields)
lines_buffer = list()
state = self._S_VALUE_LINE
continue
elif state == self._S_VALUE_LINE:
stream.revert(chunk)
chunk = self._read_until_eol(stream)
chunk = ''.join(chunk)
if not chunk:
stream.revert(None)
state = self._S_STORE_RESULT
continue
self._extract_field_values([chunk], fields, result)
state = self._S_VALUE_LINE
continue
elif state == self._S_STORE_RESULT:
self.results.append(result)
result = None
lines_buffer = list()
state = self._S_EMPTY
continue
else:
raise RuntimeError('Reach unreachable point.')
def _extract_field_titles(self, buffer, ranges):
return _Table(self._extract_data_by_ranges(buffer, ranges))
def _extract_field_values(self, buffer, ranges, table):
values = self._extract_data_by_ranges(buffer, ranges)
table.add(values)
def _read_until_eol(self, stream):
result = list()
try:
while True:
chunk = next(stream)
if chunk == '\n':
break
result.append(chunk)
except StopIteration:
pass
return result
def _extract_data_by_ranges(self, buffer, ranges):
result = list()
for offs, length in ranges:
offs_end = offs + length
if offs < 0 or offs_end <= offs:
raise ValueError(
'Invalid extract range offs {0} end {1}'.format(
offs, offs_end))
value = list()
for line in buffer:
if len(line) < offs:
raise ValueError(
'Unable to extract value from {0}, offs {1} end {2}: '
'payload too short'.format(line, offs, offs_end))
# Ignore offs_end, instead use delimiter to extract value
# Fix for mangled ip-addr values from 'sh vpn ipsec sa'
# parsing
value.append(line[offs:].split()[0])
value = ''.join(value)
value = value.strip()
result.append(value)
return tuple(result)
class _ReversibleIterator(collections.Iterator):
def __init__(self):
collections.Iterator.__init__(self)
self._reverse_storage = list()
def next(self):
if self._reverse_storage:
return self._reverse_storage.pop(0)
return self._next()
@abc.abstractmethod
def _next(self):
raise StopIteration
def revert(self, *items):
self._reverse_storage[:0] = reversed(items)
class TableTokenizer(_ReversibleIterator):
def __init__(self, stream):
_ReversibleIterator.__init__(self)
self.character_stream = _SourceWrapper(stream)
def _next(self):
chunk = list()
char = next(self.character_stream)
if char == '\n':
return char
value_type = type(char)
if char.isspace():
end_condition = lambda ch: ch != '\n' and ch.isspace()
else:
end_condition = lambda ch: not ch.isspace()
while end_condition(char):
chunk.append(char)
try:
char = next(self.character_stream)
except StopIteration:
break
else:
self.character_stream.revert(char)
return value_type('').join(chunk)
class _SourceWrapper(_ReversibleIterator):
_chunk = iter(tuple())
def __init__(self, stream):
_ReversibleIterator.__init__(self)
self._stream = iter(stream)
def _next(self):
while True:
try:
return next(self._chunk)
except StopIteration:
chunk = next(self._stream)
self._chunk = iter(chunk)
class _Table(collections.Iterable):
def __init__(self, headers):
collections.Iterable.__init__(self)
self.headers = tuple(headers)
self.rows = list()
def __iter__(self):
row_factory = functools.partial(_TableRow, self.headers)
return itertools.imap(row_factory, self.rows)
def __len__(self):
return len(self.rows)
def add(self, payload):
payload = tuple(payload)
assert len(payload) == len(self.headers)
self.rows.append(payload)
class _TableRow(collections.Iterable):
def __init__(self, headers, payload):
collections.Iterable.__init__(self)
if len(headers) != len(payload):
raise ValueError(
'Invalid table row: fields number is not equal headers '
'number.')
self.headers = headers
self.payload = payload
def __iter__(self):
return itertools.izip(self.headers, self.payload)
def cell_by_idx(self, idx):
try:
value = self.payload[idx]
except IndexError:
raise v_exc.TableCellNotFound
return value
def cell_by_name(self, name):
idx = self.headers.index(name)
return self.cell_by_idx(idx)
def get_column(self, idx_or_name):
try:
if not isinstance(idx_or_name, (int, long)):
raise ValueError
value = self.cell_by_idx(idx_or_name)
except (ValueError, v_exc.TableCellNotFound):
value = self.cell_by_name(idx_or_name)
return value

@ -0,0 +1,36 @@
# Copyright 2015 Brocade Communications System, 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 collections
from eventlet import greenthread
RouteRule = collections.namedtuple('RouteRule', 'dest_cidr, next_hop')
def retry(fn, args=None, kwargs=None, exceptions=None, limit=1, delay=0):
args = args or []
kwargs = kwargs or {}
while limit > 0:
try:
return fn(*args, **kwargs)
except Exception as e:
if not exceptions or not isinstance(e, exceptions):
raise
if delay:
greenthread.sleep(delay)
limit -= 1
raise

@ -0,0 +1,65 @@
# Copyright 2015 Brocade Communications System, 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.
TOKEN_GROUP = 'group'
TOKEN_PARAM = 'param'
TOKEN_END = 'end'
def parse_line(line):
value = None
if line.endswith('{'):
key = line[:-1].strip()
token = TOKEN_GROUP
elif line.endswith('}'):
key = line[:-1].strip()
token = TOKEN_END
else:
token = TOKEN_PARAM
chunks = line.split(' ', 1)
key = chunks.pop(0)
if chunks:
value = chunks[0]
return token, key, value
def config_iter(config):
for line in config.splitlines():
line = line.strip()
if line:
yield line
def parse_group(lines):
result = {}
for line in lines:
token, key, value = parse_line(line)
if token == TOKEN_PARAM:
result[key] = value
elif token == TOKEN_GROUP:
result[key] = parse_group(lines)
else:
break
return result
def parse_config(config):
lines = config_iter(config)
return parse_group(lines)

@ -0,0 +1,666 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import requests
import urllib
from neutron import context
from neutron.db import models_v2
from neutron.openstack.common import uuidutils
from neutron.tests import base
from vyatta.common import utils as vyatta_utils
from vyatta.vrouter import client as vyatta_client
from vyatta.vrouter import driver as vrouter_driver
_uuid = uuidutils.generate_uuid
def mock_patch(target, new=mock.DEFAULT):
patcher = mock.patch(target, new)
return patcher.start()
def mock_object(target, attribute, new=mock.DEFAULT):
patcher = mock.patch.object(target, attribute, new)
return patcher.start()
class TestVRouterDriver(base.BaseTestCase):
def setUp(self):
super(TestVRouterDriver, self).setUp()
# mock_object(
# vrouter_driver.VyattaVRouterDriver,
# '_management_network').return_value = _uuid()
self._nova_client = mock_patch(
'novaclient.v1_1.client.Client').return_value
self._server = self._create_server()
self._nova_client.servers.create.return_value = self._server
self._nova_client.servers.get.return_value = self._server
self._router_api_mock = mock_object(
vrouter_driver.VyattaVRouterDriver, '_get_router_api')
self._router_api = self._router_api_mock.return_value
self.driver = vrouter_driver.VyattaVRouterDriver()
@staticmethod
def _create_server():
server = mock.Mock()
server.id = _uuid()
server.status = 'ACTIVE'
server.interface_list.return_value = ['mng-iface']
return server
def test_create_router(self):
ctx = context.Context('', 'tenant_id1')
router = self.driver.create_router(ctx)
self.assertEqual(self._server.id, router)
def test_init_router(self):
ctx = context.Context('', 'tenant_id1')
router = {'id': _uuid()}
self.driver.init_router(ctx, router)
self._router_api_mock.assert_called_once_with(mock.ANY, router['id'])
self._router_api.init_router.assert_called_once_with(
mock.ANY, mock.ANY)
def test_delete_router(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
self.driver.delete_router(ctx, router_id)
self._router_api_mock.assert_called_once_with(mock.ANY, router_id)
self._router_api.disconnect.assert_called_once_with()
self._nova_client.servers.delete.assert_called_once_with(router_id)
def test_attach_interface(self):
ctx = context.Context('', 'tenant_id1')
port_id = _uuid()
self.driver.attach_interface(ctx, self._server.id, port_id)
self._nova_client.servers.get.assert_called_once_with(self._server.id)
self._server.interface_attach.assert_called_once_with(
port_id, mock.ANY, mock.ANY)
def test_detach_interface(self):
ctx = context.Context('', 'tenant_id1')
port_id = _uuid()
self.driver.detach_interface(ctx, self._server.id, port_id)
self._nova_client.servers.get.assert_called_once_with(self._server.id)
self._server.interface_detach.assert_called_once_with(port_id)
def test_configure_interface(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
interfaces = ['eth0', 'eth1']
self.driver.configure_interface(ctx, router_id, interfaces)
for interface in interfaces:
self._router_api.add_interface_to_router.assert_any_call(interface)
def test_deconfigure_interface(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
interfaces = ['eth0', 'eth1']
self.driver.deconfigure_interface(ctx, router_id, interfaces)
for interface in interfaces:
self._router_api.remove_interface_from_router.assert_any_call(
interface)
def test_configure_gateway(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
interfaces = ['eth0']
self.driver.configure_gateway(ctx, router_id, interfaces)
self._router_api.update_router.assert_called_once_with(
external_gateway_info=interfaces[0])
def test_clear_gateway(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
self.driver.clear_gateway(ctx, router_id, None)
self._router_api.update_router.assert_called_once_with(
external_gateway_info=None)
def test_assign_floating_ip(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
floating_ip = '192.168.1.13'
fixed_ip = '10.10.1.13'
self.driver.assign_floating_ip(ctx, router_id, floating_ip, fixed_ip)
self._router_api.assign_floating_ip.assert_called_once_with(
floating_ip, fixed_ip)
def test_unassign_floating_ip(self):
ctx = context.Context('', 'tenant_id1')
router_id = _uuid()
floating_ip = '192.168.1.13'
fixed_ip = '10.10.1.13'
self.driver.unassign_floating_ip(ctx, router_id, floating_ip, fixed_ip)
self._router_api.unassign_floating_ip.assert_called_once_with(
floating_ip, fixed_ip)
def test_update_static_routes(self):
ctx = context.Context('', 'tenant_idl')
router_id = _uuid()
RouteRule = vyatta_utils.RouteRule
routes_to_add = (
RouteRule(dest_cidr='10.1.0.0/24', next_hop='192.168.1.1'),
RouteRule(dest_cidr='10.2.0.0/24', next_hop='192.168.1.1')
)
routes_to_del = (
RouteRule(dest_cidr='10.3.0.0/24', next_hop='192.168.1.1'),
)
self.driver.update_static_routes(ctx, router_id,
routes_to_add, routes_to_del)
self._router_api.update_static_routes.assert_called_once_with(
routes_to_add, routes_to_del)
class TestVRouterDriverApi(base.BaseTestCase):
def setUp(self):
super(TestVRouterDriverApi, self).setUp()
self._nova_client = mock_patch(
'novaclient.v1_1.client.Client').return_value
# self._mock_object(vrouter_driver.VyattaVRouterDriver,
# '_management_network')
self.driver = vrouter_driver.VyattaVRouterDriver()
def _mock_object(self, target, attribute, new=mock.DEFAULT):
patcher = mock.patch.object(target, attribute, new)
return patcher.start()
def test_get_router_api(self):
ctx = mock.Mock()
router_id = _uuid()
router_addr = '172.16.18.10'
server = self._nova_client.servers.get.return_value
server.addresses = {
'external': [
{'addr': router_addr, }
]
}
query = ctx.session.query.return_value
query.filter_by.return_value.one.return_value = models_v2.Network(
name='external')
with mock.patch.object(
vyatta_client.VRouterRestAPIClient, 'connect') as api_connect:
api = self.driver._get_router_api(ctx, router_id)
self.assertIsNotNone(api)
api_connect.assert_called_once_with(router_addr)
SHOW_VERSION_OUTPUT = """
Version: VSE6.6R5X3
Description: Brocade Vyatta 5410 vRouter 6.6 R5X3
Copyright: 2006-2014 Vyatta, Inc.
"""
SHOW_CONFIG_OUTPUT = """
interfaces {
ethernet eth0 {
address 10.10.0.1
description External_Gateway
hw-id 08:00:27:02:b4:67
duplex auto
smp_affinity auto
speed auto
}
loopback lo {
}
}
service {
https {
http-redirect disable
}
lldp {
interface all {
}
snmp {
enable
}
}
snmp {
community public {
authorization ro
}
}
ssh {
port 22
}
}
system {
host-name router1
login {
user vyatta {
authentication {
encrypted-password ****************
}
level admin
}
}
syslog {
global {
facility all {
level debug
}
facility protocols {
level debug
}
}
user all {
facility all {
level emerg
}
}
}
time-zone GMT
}
"""
class VRouterRestAPIClientMixin(object):
def _create_client(self):
self.address = 'example.com'
client = vyatta_client.VRouterRestAPIClient()
client.address = self.address
client._vrouter_model = (
vyatta_client.VRouterRestAPIClient._VROUTER_VR_MODEL)
return client
class TestVRouterRestAPIClient(base.BaseTestCase,
VRouterRestAPIClientMixin):
def setUp(self):
super(TestVRouterRestAPIClient, self).setUp()
self._rest_mock = mock_object(
vyatta_client.VRouterRestAPIClient, '_rest_call')
self._rest_mock.side_effect = self._rest_call
m = mock_object(vyatta_client.VRouterRestAPIClient,
'get_ethernet_if_id')
m.return_value = 'eth0'
m = mock_object(
vyatta_client.VRouterRestAPIClient, '_get_admin_state')
m.return_value = False
def _check_cmd(self, cmd, check_type=None, check_cmd=None):
if check_type is not None:
self.assertEqual(cmd.cmd_type, check_type)
if check_cmd is not None:
self.assertEqual(cmd.cmd, check_cmd)
@staticmethod
def _rest_call(method, uri, headers=None, session=None):
response = mock.Mock()
response.status_code = requests.codes.OK
response.reason = 'Ok'
response.text = '{}'
response.headers = {
'Location': 'location'
}
return response
def _check_update_interface(self, action, interface_info):
prefix = '/location/{action}/interfaces/dataplane/eth0'.format(
action=action)
self._rest_mock.assert_any_call(
'PUT', prefix + '/address/' + interface_info['ip_address'])
self._rest_mock.assert_any_call(
'POST', '/location/commit')
self._rest_mock.assert_any_call(
'POST', '/location/save')
def test_init_router(self):
client = self._create_client()
with mock.patch('eventlet.greenthread.sleep'):
client.init_router('router1', True)
self._rest_mock.assert_any_call(
'PUT', '/location/delete/system/ip/disable-forwarding',
)
def test_add_interface_to_router(self):
interface_info = {
'mac_address': '08:00:27:02:b4:67',
'ip_address': '10.10.16.20',
'gateway_ip': '10.10.16.1'
}
client = self._create_client()
client.add_interface_to_router(interface_info)
self._check_update_interface('set', interface_info)
def test_remove_interface_from_router(self):
interface_info = {
'mac_address': '08:00:27:02:b4:67',
'ip_address': '10.10.16.20',
'gateway_ip': '10.10.16.1'
}
client = self._create_client()
client.remove_interface_from_router(interface_info)
self._check_update_interface('delete', interface_info)
def test_assign_floating_ip(self):
client = self._create_client()
client._external_gw_info = vyatta_client.InterfaceInfo(
'eth0', '172.16.18.10', '172.16.18.1')
client.assign_floating_ip('172.16.18.12', '10.10.10.12')
self.assertIn('172.16.18.12.10.10.10.12', client._floating_ip_dict)
client.unassign_floating_ip('172.16.18.12', '10.10.10.12')
self.assertNotIn('172.16.18.12.10.10.10.12', client._floating_ip_dict)
def test_update_static_routes(self):
cmd_batch_mock = mock.Mock()
mock_object(vyatta_client.VRouterRestAPIClient,
'exec_cmd_batch', cmd_batch_mock)
RouteRule = vyatta_utils.RouteRule
routes_to_add = tuple((
RouteRule(dest_cidr='10.1.0.0/24', next_hop='192.168.1.1'),
RouteRule(dest_cidr='10.2.0.0/24', next_hop='192.168.1.1'),
))
routes_to_del = tuple((
RouteRule(dest_cidr='10.3.0.0/24', next_hop='192.168.1.1'),
))
client = self._create_client()
client.update_static_routes(routes_to_add, routes_to_del)
expected_batch = list()
for rule in routes_to_add:
cmd = vyatta_client.SetCmd((
'protocols/static/route/{0}/next-hop/{1}').format(
urllib.quote_plus(rule.dest_cidr),
urllib.quote_plus(rule.next_hop)))
expected_batch.append(cmd)
for rule in routes_to_del:
cmd = vyatta_client.DeleteCmd('protocols/static/route/{0}'.format(
urllib.quote_plus(rule.dest_cidr)))
expected_batch.append(cmd)
cmd_batch_mock.assert_called_once_with(expected_batch)
def test_update_router(self):
client = self._create_client()
gw_info = {
'mac_address': '08:00:27:02:b4:67',
'ip_address': '10.10.16.20',
'gateway_ip': '10.10.16.1'
}
client.update_router('rotuer1', True, gw_info)
self._check_update_interface('set', gw_info)
self._rest_mock.assert_any_call(
'PUT', '/location/set/protocols/static/route/0.0.0.0%2F0/next-hop/'
+ gw_info['gateway_ip'])
self._rest_mock.reset_mock()
client.update_router('rotuer1', True)
self._check_update_interface('delete', gw_info)
self._rest_mock.assert_any_call(
'PUT', '/location/delete/protocols/static/route/0.0.0.0%2F0')
def test_get_nat_cmd(self):
client = self._create_client()
nat_cmd = client._get_nat_cmd()
self.assertEqual(nat_cmd, 'service/nat')
client._vrouter_model = (
vyatta_client.VRouterRestAPIClient._VROUTER_VSE_MODEL)
nat_cmd = client._get_nat_cmd()
self.assertEqual(nat_cmd, 'nat')
def test_add_snat_rule_cmd(self):
client = self._create_client()
cmd_list = []
client._add_snat_rule_cmd(
cmd_list, 2, 'eth1', '10.10.12.1', '192.168.12.1')
prefix = 'service/nat/source/rule/2'
self._check_cmd(cmd_list[0], 'set', prefix)
self._check_cmd(cmd_list[1], 'set',
prefix + '/outbound-interface/eth1')
self._check_cmd(cmd_list[2], 'set',
prefix + '/source/address/10.10.12.1')
self._check_cmd(cmd_list[3], 'set',
prefix + '/translation/address/192.168.12.1')
def test_add_dnat_rule_cmd(self):
client = self._create_client()
cmd_list = []
client._add_dnat_rule_cmd(
cmd_list, 2, 'eth1', '10.10.12.1', '192.168.12.1')
prefix = 'service/nat/destination/rule/2'
self._check_cmd(cmd_list[0], 'set', prefix)
self._check_cmd(cmd_list[1], 'set', prefix + '/inbound-interface/eth1')
self._check_cmd(cmd_list[2], 'set',
prefix + '/destination/address/10.10.12.1')
self._check_cmd(cmd_list[3], 'set',
prefix + '/translation/address/192.168.12.1')
def test_delete_snat_rule_cmd(self):
client = self._create_client()
cmd_list = []
client._delete_snat_rule_cmd(cmd_list, 2)
self._check_cmd(cmd_list[0], 'delete', 'service/nat/source/rule/2')
def test_delete_dnat_rule_cmd(self):
client = self._create_client()
cmd_list = []
client._delete_dnat_rule_cmd(cmd_list, 2)
self._check_cmd(cmd_list[0],
'delete', 'service/nat/destination/rule/2')
def test_set_router_name_cmd(self):
client = self._create_client()
cmd_list = []
client._set_router_name_cmd(cmd_list, 'router1')
self._check_cmd(cmd_list[0], 'set', 'system/host-name/router1')
def test_set_system_gateway_cmd(self):
client = self._create_client()
cmd_list = []
client._set_system_gateway_cmd(cmd_list, '10.10.16.1')
self.assertEqual(len(cmd_list), 1)
self._check_cmd(
cmd_list[0], 'set',
'protocols/static/route/0.0.0.0%2F0/next-hop/10.10.16.1')
def test_delete_system_gateway_cmd(self):
client = self._create_client()
cmd_list = []
# NOTE: Gateway parameter is not used
client._delete_system_gateway_cmd(cmd_list, '10.10.16.1')
self.assertEqual(len(cmd_list), 1)
self._check_cmd(
cmd_list[0], 'delete',
'protocols/static/route/0.0.0.0%2F0')
def test_configure_cmd_batch(self):
client = self._create_client()
cmd_list = [
vyatta_client.SetCmd('cmd1'),
vyatta_client.DeleteCmd('cmd2')
]
client.exec_cmd_batch(cmd_list)
self.assertEqual(
self._rest_mock.call_count, len(cmd_list) + 4)
def test_get_config_cmd(self):
client = self._create_client()
client._get_config_cmd('system/ip/disable-forwarding')
self.assertEqual(self._rest_mock.call_count, 3)
def test_show_cmd(self):
self._rest_mock.side_effect = [
self._make_http_response(201, headers={'Location': '/fake-url'}),
self._make_http_response(200, text=SHOW_VERSION_OUTPUT),
self._make_http_response(410),
self._make_http_response(200)]
client = self._create_client()
client._show_cmd('version')
self.assertEqual(self._rest_mock.call_count, 4)
def test_process_model(self):
client = vyatta_client.VRouterRestAPIClient()
with mock.patch.object(
vyatta_client.VRouterRestAPIClient, '_show_cmd') as show_cmd:
show_cmd.return_value = SHOW_VERSION_OUTPUT
client._process_model()
self.assertEqual(client._vrouter_model, 54)
def test_sync_cache(self):
client = self._create_client()
with mock.patch.object(
vyatta_client.VRouterRestAPIClient, '_show_cmd') as show_cmd:
show_cmd.return_value = SHOW_CONFIG_OUTPUT
client._sync_cache()
interface_info = vyatta_client.InterfaceInfo(
'eth0', '10.10.0.1')
self.assertEqual(client._external_gw_info, interface_info)
def _make_http_response(self, status_code, headers=None, text=None):
if headers is None:
headers = {}
response = mock.Mock()
response.status_code = status_code
response.headers = headers
response.text = text
return response
class TestLowLevelRestAPIClient(base.BaseTestCase,
VRouterRestAPIClientMixin):
def test_get_admin_state(self):
client = self._create_client()
with mock.patch.object(
vyatta_client.VRouterRestAPIClient,
'_show_cmd') as get_cmd:
get_cmd.return_value = "IP forwarding is on"
state = client._get_admin_state()
self.assertTrue(state)
def test_get_ethernet_if_id(self):
client = self._create_client()
with mock.patch.object(
vyatta_client.VRouterRestAPIClient,
'_get_interfaces') as get_ifs:
get_ifs.return_value = [{'name': 'dp0e5',
'ip_addrs': '192.168.21.3',
'mac_address': '08:00:27:4a:be:12'}]
if_id = client.get_ethernet_if_id('08:00:27:4a:be:12')
self.assertEqual(if_id, 'dp0e5')
def test_rest_call(self):
action = 'GET'
uri = '/show/config'
client = self._create_client()
with mock.patch('requests.request') as request:
client._rest_call(action, uri)
uri = 'https://{0}{1}'.format(self.address, uri)
request.assert_called_once_with(
action, uri, verify=False, auth=mock.ANY,
headers={'Content-Length': 0, 'Accept': 'application/json'})

@ -0,0 +1,360 @@
# Copyright 2015 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_config import cfg
from neutron import context
from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
from neutron.db import models_v2
from neutron.extensions import l3
from neutron.openstack.common import uuidutils
from neutron.tests.unit import test_db_plugin
from neutron.tests.unit import test_l3_plugin
from neutron.tests.unit import testlib_api
from neutron.tests.unit import testlib_plugin
from vyatta.common import utils as vyatta_utils
from vyatta.vrouter import neutron_plugin as vrouter_plugin
_uuid = uuidutils.generate_uuid
class FakeVRouterDriver(mock.Mock):
def create_router(self, *args, **kwargs):
return _uuid()
class VRouterTestPlugin(vrouter_plugin.VyattaVRouterMixin,
db_base_plugin_v2.NeutronDbPluginV2,
external_net_db.External_net_db_mixin):
def delete_port(self, context, port_id, l3_port_check=False):