Change-Id: I8d7d93d906f275782ef33c37c4ce9b202b72a4c6changes/78/153378/2
parent
382050e73c
commit
b361a94ddb
@ -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):
|
||||