This patch requires the v2 patch of Iwase-San. Signed-off-by: Fumihiko Kakuma <kak...@valinux.co.jp> --- .travis.yml | 2 +- ryu/lib/docker/__init__.py | 0 ryu/lib/docker/docker_base.py | 801 +++++++++++++++++++++ ryu/lib/docker/install_docker_test_pkg.sh | 43 ++ ryu/lib/docker/install_docker_test_pkg_common.sh | 39 + .../docker/install_docker_test_pkg_for_travis.sh | 12 + ryu/lib/docker/quagga.py | 332 +++++++++ ryu/lib/docker/ryubgp.py | 212 ++++++ ryu/tests/integrated/common/__init__.py | 0 ryu/tests/integrated/common/docker_base.py | 801 --------------------- .../integrated/common/install_docker_test_pkg.sh | 43 -- .../common/install_docker_test_pkg_common.sh | 39 - .../common/install_docker_test_pkg_for_travis.sh | 12 - ryu/tests/integrated/common/quagga.py | 332 --------- ryu/tests/integrated/common/ryubgp.py | 212 ------ tests/integrated/bgp/base.py | 6 +- tests/integrated/bgp/base_ip6.py | 6 +- tests/integrated/bgp/test_basic.py | 2 +- tests/integrated/bgp/test_ip6_basic.py | 2 +- 19 files changed, 1448 insertions(+), 1448 deletions(-) create mode 100644 ryu/lib/docker/__init__.py create mode 100644 ryu/lib/docker/docker_base.py create mode 100644 ryu/lib/docker/install_docker_test_pkg.sh create mode 100644 ryu/lib/docker/install_docker_test_pkg_common.sh create mode 100644 ryu/lib/docker/install_docker_test_pkg_for_travis.sh create mode 100644 ryu/lib/docker/quagga.py create mode 100644 ryu/lib/docker/ryubgp.py delete mode 100644 ryu/tests/integrated/common/__init__.py delete mode 100644 ryu/tests/integrated/common/docker_base.py delete mode 100644 ryu/tests/integrated/common/install_docker_test_pkg.sh delete mode 100644 ryu/tests/integrated/common/install_docker_test_pkg_common.sh delete mode 100644 ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh delete mode 100644 ryu/tests/integrated/common/quagga.py delete mode 100644 ryu/tests/integrated/common/ryubgp.py
diff --git a/.travis.yml b/.travis.yml index 9e5474a..cd35aac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ sudo: required # Required to enable Docker service install: - pip install tox coveralls - - bash ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh + - bash ryu/lib/docker/install_docker_test_pkg_for_travis.sh script: - NOSE_VERBOSE=0 tox -e $TOX_ENV diff --git a/ryu/lib/docker/__init__.py b/ryu/lib/docker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ryu/lib/docker/docker_base.py b/ryu/lib/docker/docker_base.py new file mode 100644 index 0000000..1ae2cc2 --- /dev/null +++ b/ryu/lib/docker/docker_base.py @@ -0,0 +1,801 @@ +# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +# +# This is based on the following +# https://github.com/osrg/gobgp/test/lib/base.py +# +# 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 __future__ import absolute_import + +import itertools +import logging +import os +import subprocess +import time + +import netaddr +import six + +LOG = logging.getLogger(__name__) + +DEFAULT_TEST_PREFIX = '' +DEFAULT_TEST_BASE_DIR = '/tmp/ctn_docker/bgp' +TEST_PREFIX = DEFAULT_TEST_PREFIX +TEST_BASE_DIR = DEFAULT_TEST_BASE_DIR + +BGP_FSM_IDLE = 'BGP_FSM_IDLE' +BGP_FSM_ACTIVE = 'BGP_FSM_ACTIVE' +BGP_FSM_ESTABLISHED = 'BGP_FSM_ESTABLISHED' + +BGP_ATTR_TYPE_ORIGIN = 1 +BGP_ATTR_TYPE_AS_PATH = 2 +BGP_ATTR_TYPE_NEXT_HOP = 3 +BGP_ATTR_TYPE_MULTI_EXIT_DISC = 4 +BGP_ATTR_TYPE_LOCAL_PREF = 5 +BGP_ATTR_TYPE_COMMUNITIES = 8 +BGP_ATTR_TYPE_ORIGINATOR_ID = 9 +BGP_ATTR_TYPE_CLUSTER_LIST = 10 +BGP_ATTR_TYPE_MP_REACH_NLRI = 14 +BGP_ATTR_TYPE_EXTENDED_COMMUNITIES = 16 + +BRIDGE_TYPE_DOCKER = 'docker' +BRIDGE_TYPE_BRCTL = 'brctl' +BRIDGE_TYPE_OVS = 'ovs' + + +class CommandError(Exception): + def __init__(self, out): + super(CommandError, self).__init__() + self.out = out + + +def try_several_times(f, t=3, s=1): + e = RuntimeError() + for _ in range(t): + try: + r = f() + except RuntimeError as e: + time.sleep(s) + else: + return r + raise e + + +class CmdBuffer(list): + def __init__(self, delim='\n'): + super(CmdBuffer, self).__init__() + self.delim = delim + + def __lshift__(self, value): + self.append(value) + + def __str__(self): + return self.delim.join(self) + + +class CommandOut(str): + + def __new__(cls, stdout, stderr, command, returncode, **kwargs): + stdout = stdout or '' + obj = super(CommandOut, cls).__new__(cls, stdout, **kwargs) + obj.stderr = stderr or '' + obj.command = command + obj.returncode = returncode + return obj + + +class Command(object): + + def _execute(self, cmd, capture=False, executable=None): + """Execute a command using subprocess.Popen() + :Parameters: + - out: stdout from subprocess.Popen() + out has some attributes. + out.returncode: returncode of subprocess.Popen() + out.stderr: stderr from subprocess.Popen() + """ + if capture: + p_stdout = subprocess.PIPE + p_stderr = subprocess.PIPE + else: + p_stdout = None + p_stderr = None + pop = subprocess.Popen(cmd, shell=True, executable=executable, + stdout=p_stdout, + stderr=p_stderr) + __stdout, __stderr = pop.communicate() + _stdout = six.text_type(__stdout, 'utf-8') + _stderr = six.text_type(__stderr, 'utf-8') + out = CommandOut(_stdout, _stderr, cmd, pop.returncode) + return out + + def execute(self, cmd, capture=True, try_times=1, interval=1): + out = None + for i in range(try_times): + out = self._execute(cmd, capture=capture) + LOG.info(out.command) + if out.returncode == 0: + return out + LOG.error("stdout: %s", out) + LOG.error("stderr: %s", out.stderr) + if i + 1 >= try_times: + break + time.sleep(interval) + raise CommandError(out) + + def sudo(self, cmd, capture=True, try_times=1, interval=1): + cmd = 'sudo %s' % cmd + return self.execute(cmd, capture=capture, + try_times=try_times, interval=interval) + + +class DockerImage(object): + def __init__(self, baseimage='ubuntu:16.04'): + self.baseimage = baseimage + self.cmd = Command() + + def get_images(self): + out = self.cmd.sudo('sudo docker images') + images = [] + for line in out.splitlines()[1:]: + images.append(line.split()[0]) + return images + + def exist(self, name): + return name in self.get_images() + + def build(self, tagname, dockerfile_dir): + self.cmd.sudo( + "docker build -t {0} {1}".format(tagname, dockerfile_dir), + try_times=3) + + def remove(self, tagname, check_exist=False): + if check_exist and not self.exist(tagname): + return tagname + self.cmd.sudo("docker rmi -f %s" % tagname, try_times=3) + + def create_quagga(self, tagname='quagga', image=None, check_exist=False): + if check_exist and self.exist(tagname): + return tagname + workdir = os.path.join(TEST_BASE_DIR, tagname) + pkges = ' '.join([ + 'telnet', + 'tcpdump', + 'quagga', + ]) + if image: + use_image = image + else: + use_image = self.baseimage + c = CmdBuffer() + c << 'FROM %s' % use_image + c << 'RUN apt-get update' + c << 'RUN apt-get install -qy --no-install-recommends %s' % pkges + c << 'CMD /usr/lib/quagga/bgpd' + + self.cmd.sudo('rm -rf %s' % workdir) + self.cmd.execute('mkdir -p %s' % workdir) + self.cmd.execute("echo '%s' > %s/Dockerfile" % (str(c), workdir)) + self.build(tagname, workdir) + return tagname + + def create_ryu(self, tagname='ryu', image=None, check_exist=False): + if check_exist and self.exist(tagname): + return tagname + workdir = os.path.join(TEST_BASE_DIR, tagname) + workdir_ctn = '/root/osrg/ryu' + pkges = ' '.join([ + 'tcpdump', + 'iproute2', + ]) + if image: + use_image = image + else: + use_image = self.baseimage + c = CmdBuffer() + c << 'FROM %s' % use_image + c << 'ADD ryu %s' % workdir_ctn + install = ' '.join([ + 'RUN apt-get update', + '&& apt-get install -qy --no-install-recommends %s' % pkges, + '&& cd %s' % workdir_ctn, + # Note: Clean previous builds, because "python setup.py install" + # might fail if the current directory contains the symlink to + # Docker host file systems. + '&& rm -rf *.egg-info/ build/ dist/ .tox/ *.log' + '&& pip install -r tools/pip-requires -r tools/optional-requires', + '&& python setup.py install', + ]) + c << install + + self.cmd.sudo('rm -rf %s' % workdir) + self.cmd.execute('mkdir -p %s' % workdir) + self.cmd.execute("echo '%s' > %s/Dockerfile" % (str(c), workdir)) + self.cmd.execute('cp -r ../ryu %s/' % workdir) + self.build(tagname, workdir) + return tagname + + +class Bridge(object): + def __init__(self, name, subnet='', start_ip=None, end_ip=None, + with_ip=True, self_ip=False, + fixed_ip=None, reuse=False, + br_type='docker'): + """Manage a bridge + :Parameters: + - name: bridge name + - subnet: network cider to be used in this bridge + - start_ip: start address of an ip to be used in the subnet + - end_ip: end address of an ip to be used in the subnet + - with_ip: specify if assign automatically an ip address + - self_ip: specify if assign an ip address for the bridge + - fixed_ip: an ip address to be assigned to the bridge + - reuse: specify if use an existing bridge + - br_type: One either in a 'docker', 'brctl' or 'ovs' + """ + self.cmd = Command() + self.name = name + if br_type not in (BRIDGE_TYPE_DOCKER, BRIDGE_TYPE_BRCTL, + BRIDGE_TYPE_OVS): + raise Exception("argument error br_type: %s" % br_type) + self.br_type = br_type + self.docker_nw = bool(self.br_type == BRIDGE_TYPE_DOCKER) + if TEST_PREFIX != '': + self.name = '{0}_{1}'.format(TEST_PREFIX, name) + self.with_ip = with_ip + if with_ip: + self.subnet = netaddr.IPNetwork(subnet) + if start_ip: + self.start_ip = start_ip + else: + self.start_ip = netaddr.IPAddress(self.subnet.first) + if end_ip: + self.end_ip = end_ip + else: + self.end_ip = netaddr.IPAddress(self.subnet.last) + + def _ip_gen(): + for host in netaddr.IPRange(self.start_ip, self.end_ip): + yield host + self._ip_generator = _ip_gen() + # throw away first network address + self.next_ip_address() + + self.self_ip = self_ip + if fixed_ip: + self.ip_addr = fixed_ip + else: + self.ip_addr = self.next_ip_address() + if not reuse: + def f(): + if self.br_type == BRIDGE_TYPE_DOCKER: + gw = "--gateway %s" % self.ip_addr.split('/')[0] + v6 = '' + if self.subnet.version == 6: + v6 = '--ipv6' + cmd = ("docker network create --driver bridge %s " + "%s --subnet %s %s" % (v6, gw, subnet, self.name)) + elif self.br_type == BRIDGE_TYPE_BRCTL: + cmd = "ip link add {0} type bridge".format(self.name) + elif self.br_type == BRIDGE_TYPE_OVS: + cmd = "ovs-vsctl add-br {0}".format(self.name) + else: + raise ValueError('Unsupported br_type: %s' % self.br_type) + self.delete() + self.execute(cmd, sudo=True, retry=True) + try_several_times(f) + if not self.docker_nw: + self.execute("ip link set up dev {0}".format(self.name), + sudo=True, retry=True) + + if not self.docker_nw and self_ip: + ips = self.check_br_addr(self.name) + for key, ip in ips.items(): + if self.subnet.version == key: + self.execute( + "ip addr del {0} dev {1}".format(ip, self.name), + sudo=True, retry=True) + self.execute( + "ip addr add {0} dev {1}".format(self.ip_addr, self.name), + sudo=True, retry=True) + self.ctns = [] + + def get_bridges_dc(self): + out = self.execute('docker network ls', sudo=True, retry=True) + bridges = [] + for line in out.splitlines()[1:]: + bridges.append(line.split()[1]) + return bridges + + def get_bridges_brctl(self): + out = self.execute('brctl show', retry=True) + bridges = [] + for line in out.splitlines()[1:]: + bridges.append(line.split()[0]) + return bridges + + def get_bridges_ovs(self): + out = self.execute('ovs-vsctl list-br', sudo=True, retry=True) + return out.splitlines() + + def get_bridges(self): + if self.br_type == BRIDGE_TYPE_DOCKER: + return self.get_bridges_dc() + elif self.br_type == BRIDGE_TYPE_BRCTL: + return self.get_bridges_brctl() + elif self.br_type == BRIDGE_TYPE_OVS: + return self.get_bridges_ovs() + + def exist(self): + return self.name in self.get_bridges() + + def execute(self, cmd, capture=True, sudo=False, retry=False): + if sudo: + m = self.cmd.sudo + else: + m = self.cmd.execute + if retry: + return m(cmd, capture=capture, try_times=3, interval=1) + else: + return m(cmd, capture=capture) + + def check_br_addr(self, br): + ips = {} + cmd = "ip a show dev %s" % br + for line in self.execute(cmd, sudo=True).split('\n'): + if line.strip().startswith("inet "): + elems = [e.strip() for e in line.strip().split(' ')] + ips[4] = elems[1] + elif line.strip().startswith("inet6 "): + elems = [e.strip() for e in line.strip().split(' ')] + ips[6] = elems[1] + return ips + + def next_ip_address(self): + return "{0}/{1}".format(next(self._ip_generator), + self.subnet.prefixlen) + + def addif(self, ctn): + name = ctn.next_if_name() + self.ctns.append(ctn) + ip_address = None + if self.docker_nw: + ipv4 = None + ipv6 = None + ip_address = self.next_ip_address() + ip_address_ip = ip_address.split('/')[0] + version = 4 + if netaddr.IPNetwork(ip_address).version == 6: + version = 6 + opt_ip = "--ip %s" % ip_address_ip + if version == 4: + ipv4 = ip_address + else: + opt_ip = "--ip6 %s" % ip_address_ip + ipv6 = ip_address + cmd = "docker network connect %s %s %s" % ( + opt_ip, self.name, ctn.docker_name()) + self.execute(cmd, sudo=True) + ctn.set_addr_info(bridge=self.name, ipv4=ipv4, ipv6=ipv6, + ifname=name) + else: + if self.with_ip: + ip_address = self.next_ip_address() + version = 4 + if netaddr.IPNetwork(ip_address).version == 6: + version = 6 + ctn.pipework(self, ip_address, name, version=version) + else: + ctn.pipework(self, '0/0', name) + return ip_address + + def delete(self, check_exist=True): + if check_exist: + if not self.exist(): + return + if self.br_type == BRIDGE_TYPE_DOCKER: + self.execute("docker network rm %s" % self.name, + sudo=True, retry=True) + elif self.br_type == BRIDGE_TYPE_BRCTL: + self.execute("ip link set down dev %s" % self.name, + sudo=True, retry=True) + self.execute( + "ip link delete %s type bridge" % self.name, + sudo=True, retry=True) + elif self.br_type == BRIDGE_TYPE_OVS: + self.execute( + "ovs-vsctl del-br %s" % self.name, + sudo=True, retry=True) + + +class Container(object): + def __init__(self, name, image=None): + self.name = name + self.image = image + self.shared_volumes = [] + self.ip_addrs = [] + self.ip6_addrs = [] + self.is_running = False + self.eths = [] + self.id = None + + self.cmd = Command() + self.remove() + + def docker_name(self): + if TEST_PREFIX == DEFAULT_TEST_PREFIX: + return self.name + return '{0}_{1}'.format(TEST_PREFIX, self.name) + + def get_docker_id(self): + if self.id: + return self.id + else: + return self.docker_name() + + def next_if_name(self): + name = 'eth{0}'.format(len(self.eths) + 1) + self.eths.append(name) + return name + + def set_addr_info(self, bridge, ipv4=None, ipv6=None, ifname='eth0'): + if ipv4: + self.ip_addrs.append((ifname, ipv4, bridge)) + if ipv6: + self.ip6_addrs.append((ifname, ipv6, bridge)) + + def get_addr_info(self, bridge, ipv=4): + addrinfo = {} + if ipv == 4: + ip_addrs = self.ip_addrs + elif ipv == 6: + ip_addrs = self.ip6_addrs + else: + return None + for addr in ip_addrs: + if addr[2] == bridge: + addrinfo[addr[1]] = addr[0] + return addrinfo + + def execute(self, cmd, capture=True, sudo=False, retry=False): + if sudo: + m = self.cmd.sudo + else: + m = self.cmd.execute + if retry: + return m(cmd, capture=capture, try_times=3, interval=1) + else: + return m(cmd, capture=capture) + + def dcexec(self, cmd, capture=True, retry=False): + if retry: + return self.cmd.sudo(cmd, capture=capture, try_times=3, interval=1) + else: + return self.cmd.sudo(cmd, capture=capture) + + def exec_on_ctn(self, cmd, capture=True, detach=False): + name = self.docker_name() + flag = '-d' if detach else '' + return self.dcexec('docker exec {0} {1} {2}'.format( + flag, name, cmd), capture=capture) + + def get_containers(self, allctn=False): + cmd = 'docker ps --no-trunc=true' + if allctn: + cmd += ' --all=true' + out = self.dcexec(cmd, retry=True) + containers = [] + for line in out.splitlines()[1:]: + containers.append(line.split()[-1]) + return containers + + def exist(self, allctn=False): + return self.docker_name() in self.get_containers(allctn=allctn) + + def run(self): + c = CmdBuffer(' ') + c << "docker run --privileged=true" + for sv in self.shared_volumes: + c << "-v {0}:{1}".format(sv[0], sv[1]) + c << "--name {0} --hostname {0} -id {1}".format(self.docker_name(), + self.image) + self.id = self.dcexec(str(c), retry=True) + self.is_running = True + self.exec_on_ctn("ip li set up dev lo") + ipv4 = None + ipv6 = None + for line in self.exec_on_ctn("ip a show dev eth0").split('\n'): + if line.strip().startswith("inet "): + elems = [e.strip() for e in line.strip().split(' ')] + ipv4 = elems[1] + elif line.strip().startswith("inet6 "): + elems = [e.strip() for e in line.strip().split(' ')] + ipv6 = elems[1] + self.set_addr_info(bridge='docker0', ipv4=ipv4, ipv6=ipv6, + ifname='eth0') + return 0 + + def stop(self, check_exist=True): + if check_exist: + if not self.exist(allctn=False): + return + ctn_id = self.get_docker_id() + out = self.dcexec('docker stop -t 0 %s' % ctn_id, retry=True) + self.is_running = False + return out + + def remove(self, check_exist=True): + if check_exist: + if not self.exist(allctn=True): + return + ctn_id = self.get_docker_id() + out = self.dcexec('docker rm -f %s' % ctn_id, retry=True) + self.is_running = False + return out + + def pipework(self, bridge, ip_addr, intf_name="", version=4): + if not self.is_running: + LOG.warning('Call run() before pipeworking') + return + c = CmdBuffer(' ') + c << "pipework {0}".format(bridge.name) + + if intf_name != "": + c << "-i {0}".format(intf_name) + else: + intf_name = "eth1" + ipv4 = None + ipv6 = None + if version == 4: + ipv4 = ip_addr + else: + c << '-a 6' + ipv6 = ip_addr + c << "{0} {1}".format(self.docker_name(), ip_addr) + self.set_addr_info(bridge=bridge.name, ipv4=ipv4, ipv6=ipv6, + ifname=intf_name) + self.execute(str(c), sudo=True, retry=True) + + def get_pid(self): + if self.is_running: + cmd = "docker inspect -f '{{.State.Pid}}' %s" % self.docker_name() + return int(self.dcexec(cmd)) + return -1 + + def start_tcpdump(self, interface=None, filename=None): + if not interface: + interface = "eth0" + if not filename: + filename = "{0}/{1}.dump".format( + self.shared_volumes[0][1], interface) + self.exec_on_ctn( + "tcpdump -i {0} -w {1}".format(interface, filename), + detach=True) + + +class BGPContainer(Container): + + WAIT_FOR_BOOT = 1 + RETRY_INTERVAL = 5 + DEFAULT_PEER_ARG = {'neigh_addr': '', + 'passwd': None, + 'vpn': False, + 'flowspec': False, + 'is_rs_client': False, + 'is_rr_client': False, + 'cluster_id': None, + 'policies': None, + 'passive': False, + 'local_addr': '', + 'as2': False, + 'graceful_restart': None, + 'local_as': None, + 'prefix_limit': None} + default_peer_keys = sorted(DEFAULT_PEER_ARG.keys()) + DEFAULT_ROUTE_ARG = {'prefix': None, + 'rf': 'ipv4', + 'attr': None, + 'next-hop': None, + 'as-path': None, + 'community': None, + 'med': None, + 'local-pref': None, + 'extended-community': None, + 'matchs': None, + 'thens': None} + default_route_keys = sorted(DEFAULT_ROUTE_ARG.keys()) + + def __init__(self, name, asn, router_id, ctn_image_name=None): + self.config_dir = TEST_BASE_DIR + if TEST_PREFIX: + self.config_dir = os.path.join(self.config_dir, TEST_PREFIX) + self.config_dir = os.path.join(self.config_dir, name) + self.asn = asn + self.router_id = router_id + self.peers = {} + self.routes = {} + self.policies = {} + super(BGPContainer, self).__init__(name, ctn_image_name) + self.execute( + 'rm -rf {0}'.format(self.config_dir), sudo=True) + self.execute('mkdir -p {0}'.format(self.config_dir)) + self.execute('chmod 777 {0}'.format(self.config_dir)) + + def __repr__(self): + return str({'name': self.name, 'asn': self.asn, + 'router_id': self.router_id}) + + def run(self, wait=False, w_time=WAIT_FOR_BOOT): + self.create_config() + super(BGPContainer, self).run() + if wait: + time.sleep(w_time) + return w_time + + def add_peer(self, peer, bridge='', reload_config=True, v6=False, + peer_info=None): + peer_info = peer_info or {} + self.peers[peer] = self.DEFAULT_PEER_ARG.copy() + self.peers[peer].update(peer_info) + peer_keys = sorted(self.peers[peer].keys()) + if peer_keys != self.default_peer_keys: + raise Exception("argument error peer_info: %s" % peer_info) + + neigh_addr = '' + local_addr = '' + it = itertools.product(self.ip_addrs, peer.ip_addrs) + if v6: + it = itertools.product(self.ip6_addrs, peer.ip6_addrs) + + for me, you in it: + if bridge != '' and bridge != me[2]: + continue + if me[2] == you[2]: + neigh_addr = you[1] + local_addr = me[1] + if v6: + addr, mask = local_addr.split('/') + local_addr = "{0}%{1}/{2}".format(addr, me[0], mask) + break + + if neigh_addr == '': + raise Exception('peer {0} seems not ip reachable'.format(peer)) + + if not self.peers[peer]['policies']: + self.peers[peer]['policies'] = {} + + self.peers[peer]['neigh_addr'] = neigh_addr + self.peers[peer]['local_addr'] = local_addr + if self.is_running and reload_config: + self.create_config() + self.reload_config() + + def del_peer(self, peer, reload_config=True): + del self.peers[peer] + if self.is_running and reload_config: + self.create_config() + self.reload_config() + + def disable_peer(self, peer): + raise NotImplementedError() + + def enable_peer(self, peer): + raise NotImplementedError() + + def log(self): + return self.execute('cat {0}/*.log'.format(self.config_dir)) + + def add_route(self, route, reload_config=True, route_info=None): + route_info = route_info or {} + self.routes[route] = self.DEFAULT_ROUTE_ARG.copy() + self.routes[route].update(route_info) + route_keys = sorted(self.routes[route].keys()) + if route_keys != self.default_route_keys: + raise Exception("argument error route_info: %s" % route_info) + self.routes[route]['prefix'] = route + if self.is_running and reload_config: + self.create_config() + self.reload_config() + + def add_policy(self, policy, peer, typ, default='accept', + reload_config=True): + self.set_default_policy(peer, typ, default) + self.define_policy(policy) + self.assign_policy(peer, policy, typ) + if self.is_running and reload_config: + self.create_config() + self.reload_config() + + def set_default_policy(self, peer, typ, default): + if (typ in ['in', 'out', 'import', 'export'] and + default in ['reject', 'accept']): + if 'default-policy' not in self.peers[peer]: + self.peers[peer]['default-policy'] = {} + self.peers[peer]['default-policy'][typ] = default + else: + raise Exception('wrong type or default') + + def define_policy(self, policy): + self.policies[policy['name']] = policy + + def assign_policy(self, peer, policy, typ): + if peer not in self.peers: + raise Exception('peer {0} not found'.format(peer.name)) + name = policy['name'] + if name not in self.policies: + raise Exception('policy {0} not found'.format(name)) + self.peers[peer]['policies'][typ] = policy + + def get_local_rib(self, peer, rf): + raise NotImplementedError() + + def get_global_rib(self, rf): + raise NotImplementedError() + + def get_neighbor_state(self, peer_id): + raise NotImplementedError() + + def get_reachablily(self, prefix, timeout=20): + version = netaddr.IPNetwork(prefix).version + addr = prefix.split('/')[0] + if version == 4: + ping_cmd = 'ping' + elif version == 6: + ping_cmd = 'ping6' + else: + raise Exception( + 'unsupported route family: {0}'.format(version)) + cmd = '/bin/bash -c "/bin/{0} -c 1 -w 1 {1} | xargs echo"'.format( + ping_cmd, addr) + interval = 1 + count = 0 + while True: + res = self.exec_on_ctn(cmd) + LOG.info(res) + if '1 packets received' in res and '0% packet loss': + break + time.sleep(interval) + count += interval + if count >= timeout: + raise Exception('timeout') + return True + + def wait_for(self, expected_state, peer, timeout=120): + interval = 1 + count = 0 + while True: + state = self.get_neighbor_state(peer) + LOG.info("%s's peer %s state: %s", + self.router_id, peer.router_id, state) + if state == expected_state: + return + + time.sleep(interval) + count += interval + if count >= timeout: + raise Exception('timeout') + + def add_static_route(self, network, next_hop): + cmd = '/sbin/ip route add {0} via {1}'.format(network, next_hop) + self.exec_on_ctn(cmd) + + def set_ipv6_forward(self): + cmd = 'sysctl -w net.ipv6.conf.all.forwarding=1' + self.exec_on_ctn(cmd) + + def create_config(self): + raise NotImplementedError() + + def reload_config(self): + raise NotImplementedError() diff --git a/ryu/lib/docker/install_docker_test_pkg.sh b/ryu/lib/docker/install_docker_test_pkg.sh new file mode 100644 index 0000000..a771dfc --- /dev/null +++ b/ryu/lib/docker/install_docker_test_pkg.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -ex + +RYU_PATH=`dirname $0` + +source ${RYU_PATH}/install_docker_test_pkg_common.sh + +function add_docker_aptline { + sudo apt-get update + if ! apt-cache search docker-engine | grep docker-engine; then + VER=`lsb_release -r` + if echo $VER | grep 12.04; then + REL_NAME=precise + elif echo $VER | grep 14.04; then + REL_NAME=trusty + elif echo $VER | grep 15.10; then + REL_NAME=wily + elif echo $VER | grep 16.04; then + REL_NAME=xenial + else + retrun 1 + fi + RELEASE=ubuntu-$REL_NAME + sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D + sudo sh -c "echo deb https://apt.dockerproject.org/repo $RELEASE main > /etc/apt/sources.list.d/docker.list" + fi +} + +init_variables +process_options "$@" + +if [ $APTLINE_DOCKER -eq 1 ]; then + add_docker_aptline +fi + +sudo apt-get update +if apt-cache search docker-engine | grep docker-engine; then + DOCKER_PKG=docker-engine +else + DOCKER_PKG=docker.io +fi +sudo apt-get install -y $DOCKER_PKG +install_depends_pkg diff --git a/ryu/lib/docker/install_docker_test_pkg_common.sh b/ryu/lib/docker/install_docker_test_pkg_common.sh new file mode 100644 index 0000000..44a3e10 --- /dev/null +++ b/ryu/lib/docker/install_docker_test_pkg_common.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -ex + +function init_variables { + APTLINE_DOCKER=0 + DIR_BASE=/tmp +} + +function process_options { + local max + local i + max=$# + i=1 + while [ $i -le $max ]; do + case "$1" in + -a|--add-docker-aptline) + APTLINE_DOCKER=1 + ;; + -d|--download-dir) + shift; ((i++)) + DIR_BASE=$1 + ;; + esac + shift; ((i++)) + done +} + +function install_pipework { + if ! which /usr/local/bin/pipework >/dev/null + then + sudo rm -rf $DIR_BASE/pipework + git clone https://github.com/jpetazzo/pipework.git $DIR_BASE/pipework + sudo install -m 0755 $DIR_BASE/pipework/pipework /usr/local/bin/pipework + fi +} + +function install_depends_pkg { + install_pipework +} diff --git a/ryu/lib/docker/install_docker_test_pkg_for_travis.sh b/ryu/lib/docker/install_docker_test_pkg_for_travis.sh new file mode 100644 index 0000000..d8c3b49 --- /dev/null +++ b/ryu/lib/docker/install_docker_test_pkg_for_travis.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -ex + +RYU_PATH=`dirname $0` + +source ${RYU_PATH}/install_docker_test_pkg_common.sh + +init_variables +process_options "$@" + +sudo apt-get update +install_depends_pkg diff --git a/ryu/lib/docker/quagga.py b/ryu/lib/docker/quagga.py new file mode 100644 index 0000000..9b6d218 --- /dev/null +++ b/ryu/lib/docker/quagga.py @@ -0,0 +1,332 @@ +# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +# +# This is based on the following +# https://github.com/osrg/gobgp/test/lib/quagga.py +# +# 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 __future__ import absolute_import + +import logging +import os + +import netaddr + +from . import docker_base as base + +LOG = logging.getLogger(__name__) + + +class QuaggaBGPContainer(base.BGPContainer): + + WAIT_FOR_BOOT = 1 + SHARED_VOLUME = '/etc/quagga' + + def __init__(self, name, asn, router_id, ctn_image_name, zebra=False): + super(QuaggaBGPContainer, self).__init__(name, asn, router_id, + ctn_image_name) + self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) + self.zebra = zebra + self._create_config_debian() + + def run(self, wait=False, w_time=WAIT_FOR_BOOT): + w_time = super(QuaggaBGPContainer, + self).run(wait=wait, w_time=self.WAIT_FOR_BOOT) + return w_time + + def get_global_rib(self, prefix='', rf='ipv4'): + rib = [] + if prefix != '': + return self.get_global_rib_with_prefix(prefix, rf) + + out = self.vtysh('show bgp {0} unicast'.format(rf), config=False) + if out.startswith('No BGP network exists'): + return rib + + read_next = False + + for line in out.split('\n'): + ibgp = False + if line[:2] == '*>': + line = line[2:] + if line[0] == 'i': + line = line[1:] + ibgp = True + elif not read_next: + continue + + elems = line.split() + + if len(elems) == 1: + read_next = True + prefix = elems[0] + continue + elif read_next: + nexthop = elems[0] + else: + prefix = elems[0] + nexthop = elems[1] + read_next = False + + rib.append({'prefix': prefix, 'nexthop': nexthop, + 'ibgp': ibgp}) + + return rib + + def get_global_rib_with_prefix(self, prefix, rf): + rib = [] + + lines = [line.strip() for line in self.vtysh( + 'show bgp {0} unicast {1}'.format(rf, prefix), + config=False).split('\n')] + + if lines[0] == '% Network not in table': + return rib + + lines = lines[2:] + + if lines[0].startswith('Not advertised'): + lines.pop(0) # another useless line + elif lines[0].startswith('Advertised to non peer-group peers:'): + lines = lines[2:] # other useless lines + else: + raise Exception('unknown output format {0}'.format(lines)) + + if lines[0] == 'Local': + aspath = [] + else: + aspath = [int(asn) for asn in lines[0].split()] + + nexthop = lines[1].split()[0].strip() + info = [s.strip(',') for s in lines[2].split()] + attrs = [] + if 'metric' in info: + med = info[info.index('metric') + 1] + attrs.append({'type': base.BGP_ATTR_TYPE_MULTI_EXIT_DISC, + 'metric': int(med)}) + if 'localpref' in info: + localpref = info[info.index('localpref') + 1] + attrs.append({'type': base.BGP_ATTR_TYPE_LOCAL_PREF, + 'value': int(localpref)}) + + rib.append({'prefix': prefix, 'nexthop': nexthop, + 'aspath': aspath, 'attrs': attrs}) + + return rib + + def get_neighbor_state(self, peer): + if peer not in self.peers: + raise Exception('not found peer {0}'.format(peer.router_id)) + + neigh_addr = self.peers[peer]['neigh_addr'].split('/')[0] + + info = [l.strip() for l in self.vtysh( + 'show bgp neighbors {0}'.format(neigh_addr), + config=False).split('\n')] + + if not info[0].startswith('BGP neighbor is'): + raise Exception('unknown format') + + idx1 = info[0].index('BGP neighbor is ') + idx2 = info[0].index(',') + n_addr = info[0][idx1 + len('BGP neighbor is '):idx2] + if n_addr == neigh_addr: + idx1 = info[2].index('= ') + state = info[2][idx1 + len('= '):] + if state.startswith('Idle'): + return base.BGP_FSM_IDLE + elif state.startswith('Active'): + return base.BGP_FSM_ACTIVE + elif state.startswith('Established'): + return base.BGP_FSM_ESTABLISHED + else: + return state + + raise Exception('not found peer {0}'.format(peer.router_id)) + + def send_route_refresh(self): + self.vtysh('clear ip bgp * soft', config=False) + + def create_config(self): + zebra = 'no' + self._create_config_bgp() + if self.zebra: + zebra = 'yes' + self._create_config_zebra() + self._create_config_daemons(zebra) + + def _create_config_debian(self): + c = base.CmdBuffer() + c << 'vtysh_enable=yes' + c << 'zebra_options=" --daemon -A 127.0.0.1"' + c << 'bgpd_options=" --daemon -A 127.0.0.1"' + c << 'ospfd_options=" --daemon -A 127.0.0.1"' + c << 'ospf6d_options=" --daemon -A ::1"' + c << 'ripd_options=" --daemon -A 127.0.0.1"' + c << 'ripngd_options=" --daemon -A ::1"' + c << 'isisd_options=" --daemon -A 127.0.0.1"' + c << 'babeld_options=" --daemon -A 127.0.0.1"' + c << 'watchquagga_enable=yes' + c << 'watchquagga_options=(--daemon)' + with open('{0}/debian.conf'.format(self.config_dir), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def _create_config_daemons(self, zebra='no'): + c = base.CmdBuffer() + c << 'zebra=%s' % zebra + c << 'bgpd=yes' + c << 'ospfd=no' + c << 'ospf6d=no' + c << 'ripd=no' + c << 'ripngd=no' + c << 'isisd=no' + c << 'babeld=no' + with open('{0}/daemons'.format(self.config_dir), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def _create_config_bgp(self): + + c = base.CmdBuffer() + c << 'hostname bgpd' + c << 'password zebra' + c << 'router bgp {0}'.format(self.asn) + c << 'bgp router-id {0}'.format(self.router_id) + if any(info['graceful_restart'] for info in self.peers.values()): + c << 'bgp graceful-restart' + + version = 4 + for peer, info in self.peers.items(): + version = netaddr.IPNetwork(info['neigh_addr']).version + n_addr = info['neigh_addr'].split('/')[0] + if version == 6: + c << 'no bgp default ipv4-unicast' + + c << 'neighbor {0} remote-as {1}'.format(n_addr, peer.asn) + if info['is_rs_client']: + c << 'neighbor {0} route-server-client'.format(n_addr) + for typ, p in info['policies'].items(): + c << 'neighbor {0} route-map {1} {2}'.format(n_addr, p['name'], + typ) + if info['passwd']: + c << 'neighbor {0} password {1}'.format(n_addr, info['passwd']) + if info['passive']: + c << 'neighbor {0} passive'.format(n_addr) + if version == 6: + c << 'address-family ipv6 unicast' + c << 'neighbor {0} activate'.format(n_addr) + c << 'exit-address-family' + + for route in self.routes.values(): + if route['rf'] == 'ipv4': + c << 'network {0}'.format(route['prefix']) + elif route['rf'] == 'ipv6': + c << 'address-family ipv6 unicast' + c << 'network {0}'.format(route['prefix']) + c << 'exit-address-family' + else: + raise Exception( + 'unsupported route faily: {0}'.format(route['rf'])) + + if self.zebra: + if version == 6: + c << 'address-family ipv6 unicast' + c << 'redistribute connected' + c << 'exit-address-family' + else: + c << 'redistribute connected' + + for name, policy in self.policies.items(): + c << 'access-list {0} {1} {2}'.format(name, policy['type'], + policy['match']) + c << 'route-map {0} permit 10'.format(name) + c << 'match ip address {0}'.format(name) + c << 'set metric {0}'.format(policy['med']) + + c << 'debug bgp as4' + c << 'debug bgp fsm' + c << 'debug bgp updates' + c << 'debug bgp events' + c << 'log file {0}/bgpd.log'.format(self.SHARED_VOLUME) + + with open('{0}/bgpd.conf'.format(self.config_dir), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def _create_config_zebra(self): + c = base.CmdBuffer() + c << 'hostname zebra' + c << 'password zebra' + c << 'log file {0}/zebra.log'.format(self.SHARED_VOLUME) + c << 'debug zebra packet' + c << 'debug zebra kernel' + c << 'debug zebra rib' + c << '' + + with open('{0}/zebra.conf'.format(self.config_dir), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def vtysh(self, cmd, config=True): + if not isinstance(cmd, list): + cmd = [cmd] + cmd = ' '.join("-c '{0}'".format(c) for c in cmd) + if config: + return self.exec_on_ctn( + "vtysh -d bgpd -c 'en' -c 'conf t' -c " + "'router bgp {0}' {1}".format(self.asn, cmd), + capture=True) + else: + return self.exec_on_ctn("vtysh -d bgpd {0}".format(cmd), + capture=True) + + def reload_config(self): + daemon = [] + daemon.append('bgpd') + if self.zebra: + daemon.append('zebra') + for d in daemon: + cmd = '/usr/bin/pkill {0} -SIGHUP'.format(d) + self.exec_on_ctn(cmd, capture=True) + + +class RawQuaggaBGPContainer(QuaggaBGPContainer): + def __init__(self, name, config, ctn_image_name, + zebra=False): + asn = None + router_id = None + for line in config.split('\n'): + line = line.strip() + if line.startswith('router bgp'): + asn = int(line[len('router bgp'):].strip()) + if line.startswith('bgp router-id'): + router_id = line[len('bgp router-id'):].strip() + if not asn: + raise Exception('asn not in quagga config') + if not router_id: + raise Exception('router-id not in quagga config') + self.config = config + super(RawQuaggaBGPContainer, self).__init__(name, asn, router_id, + ctn_image_name, zebra) + + def create_config(self): + with open(os.path.join(self.config_dir, 'bgpd.conf'), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(self.config) + f.writelines(self.config) diff --git a/ryu/lib/docker/ryubgp.py b/ryu/lib/docker/ryubgp.py new file mode 100644 index 0000000..8fe16f4 --- /dev/null +++ b/ryu/lib/docker/ryubgp.py @@ -0,0 +1,212 @@ +# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +# +# 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 __future__ import absolute_import + +import logging +import os +import time + +from . import docker_base as base + +LOG = logging.getLogger(__name__) + + +class RyuBGPContainer(base.BGPContainer): + + WAIT_FOR_BOOT = 1 + SHARED_VOLUME = '/etc/ryu' + + def __init__(self, name, asn, router_id, ctn_image_name): + super(RyuBGPContainer, self).__init__(name, asn, router_id, + ctn_image_name) + self.RYU_CONF = os.path.join(self.config_dir, 'ryu.conf') + self.SHARED_RYU_CONF = os.path.join(self.SHARED_VOLUME, 'ryu.conf') + self.SHARED_BGP_CONF = os.path.join(self.SHARED_VOLUME, 'bgp_conf.py') + self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) + + def _create_config_ryu(self): + c = base.CmdBuffer() + c << '[DEFAULT]' + c << 'verbose=True' + c << 'log_file=/etc/ryu/manager.log' + with open(self.RYU_CONF, 'w') as f: + LOG.info("[%s's new config]" % self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def _create_config_ryu_bgp(self): + c = base.CmdBuffer() + c << 'import os' + c << '' + c << 'BGP = {' + c << " 'local_as': %s," % str(self.asn) + c << " 'router_id': '%s'," % self.router_id + c << " 'neighbors': [" + c << " {" + for peer, info in self.peers.items(): + n_addr = info['neigh_addr'].split('/')[0] + c << " 'address': '%s'," % n_addr + c << " 'remote_as': %s," % str(peer.asn) + c << " 'enable_ipv4': True," + c << " 'enable_ipv6': True," + c << " 'enable_vpnv4': True," + c << " 'enable_vpnv6': True," + c << ' },' + c << ' ],' + c << " 'routes': [" + for route in self.routes.values(): + c << " {" + c << " 'prefix': '%s'," % route['prefix'] + c << " }," + c << " ]," + c << "}" + log_conf = """LOGGING = { + + # We use python logging package for logging. + 'version': 1, + 'disable_existing_loggers': False, + + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s ' + + '[%(process)d %(thread)d] %(message)s' + }, + 'simple': { + 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)s ' + + '%(message)s' + }, + 'stats': { + 'format': '%(message)s' + }, + }, + + 'handlers': { + # Outputs log to console. + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple' + }, + 'console_stats': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'stats' + }, + # Rotates log file when its size reaches 10MB. + 'log_file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join('.', 'bgpspeaker.log'), + 'maxBytes': '10000000', + 'formatter': 'verbose' + }, + 'stats_file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join('.', 'statistics_bgps.log'), + 'maxBytes': '10000000', + 'formatter': 'stats' + }, + }, + + # Fine-grained control of logging per instance. + 'loggers': { + 'bgpspeaker': { + 'handlers': ['console', 'log_file'], + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': False, + }, + 'stats': { + 'handlers': ['stats_file', 'console_stats'], + 'level': 'INFO', + 'propagate': False, + 'formatter': 'stats', + }, + }, + + # Root loggers. + 'root': { + 'handlers': ['console', 'log_file'], + 'level': 'DEBUG', + 'propagate': True, + }, +}""" + c << log_conf + with open(os.path.join(self.config_dir, 'bgp_conf.py'), 'w') as f: + LOG.info("[%s's new config]", self.name) + LOG.info(str(c)) + f.writelines(str(c)) + + def create_config(self): + self._create_config_ryu() + self._create_config_ryu_bgp() + + def is_running_ryu(self): + results = self.exec_on_ctn('ps ax') + running = False + for line in results.split('\n')[1:]: + if 'ryu-manager' in line: + running = True + return running + + def start_ryubgp(self, check_running=True, retry=False): + if check_running: + if self.is_running_ryu(): + return True + result = False + if retry: + try_times = 3 + else: + try_times = 1 + cmd = "ryu-manager --verbose " + cmd += "--config-file %s " % self.SHARED_RYU_CONF + cmd += "--bgp-app-config-file %s " % self.SHARED_BGP_CONF + cmd += "ryu.services.protocols.bgp.application" + for _ in range(try_times): + self.exec_on_ctn(cmd, detach=True) + if self.is_running_ryu(): + result = True + break + time.sleep(1) + return result + + def stop_ryubgp(self, check_running=True, retry=False): + if check_running: + if not self.is_running_ryu(): + return True + result = False + if retry: + try_times = 3 + else: + try_times = 1 + for _ in range(try_times): + cmd = '/usr/bin/pkill ryu-manager -SIGTERM' + self.exec_on_ctn(cmd) + if not self.is_running_ryu(): + result = True + break + time.sleep(1) + return result + + def run(self, wait=False, w_time=WAIT_FOR_BOOT): + w_time = super(RyuBGPContainer, + self).run(wait=wait, w_time=self.WAIT_FOR_BOOT) + return w_time + + def reload_config(self): + self.stop_ryubgp(retry=True) + self.start_ryubgp(retry=True) diff --git a/ryu/tests/integrated/common/__init__.py b/ryu/tests/integrated/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ryu/tests/integrated/common/docker_base.py b/ryu/tests/integrated/common/docker_base.py deleted file mode 100644 index 1ae2cc2..0000000 --- a/ryu/tests/integrated/common/docker_base.py +++ /dev/null @@ -1,801 +0,0 @@ -# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. -# -# This is based on the following -# https://github.com/osrg/gobgp/test/lib/base.py -# -# 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 __future__ import absolute_import - -import itertools -import logging -import os -import subprocess -import time - -import netaddr -import six - -LOG = logging.getLogger(__name__) - -DEFAULT_TEST_PREFIX = '' -DEFAULT_TEST_BASE_DIR = '/tmp/ctn_docker/bgp' -TEST_PREFIX = DEFAULT_TEST_PREFIX -TEST_BASE_DIR = DEFAULT_TEST_BASE_DIR - -BGP_FSM_IDLE = 'BGP_FSM_IDLE' -BGP_FSM_ACTIVE = 'BGP_FSM_ACTIVE' -BGP_FSM_ESTABLISHED = 'BGP_FSM_ESTABLISHED' - -BGP_ATTR_TYPE_ORIGIN = 1 -BGP_ATTR_TYPE_AS_PATH = 2 -BGP_ATTR_TYPE_NEXT_HOP = 3 -BGP_ATTR_TYPE_MULTI_EXIT_DISC = 4 -BGP_ATTR_TYPE_LOCAL_PREF = 5 -BGP_ATTR_TYPE_COMMUNITIES = 8 -BGP_ATTR_TYPE_ORIGINATOR_ID = 9 -BGP_ATTR_TYPE_CLUSTER_LIST = 10 -BGP_ATTR_TYPE_MP_REACH_NLRI = 14 -BGP_ATTR_TYPE_EXTENDED_COMMUNITIES = 16 - -BRIDGE_TYPE_DOCKER = 'docker' -BRIDGE_TYPE_BRCTL = 'brctl' -BRIDGE_TYPE_OVS = 'ovs' - - -class CommandError(Exception): - def __init__(self, out): - super(CommandError, self).__init__() - self.out = out - - -def try_several_times(f, t=3, s=1): - e = RuntimeError() - for _ in range(t): - try: - r = f() - except RuntimeError as e: - time.sleep(s) - else: - return r - raise e - - -class CmdBuffer(list): - def __init__(self, delim='\n'): - super(CmdBuffer, self).__init__() - self.delim = delim - - def __lshift__(self, value): - self.append(value) - - def __str__(self): - return self.delim.join(self) - - -class CommandOut(str): - - def __new__(cls, stdout, stderr, command, returncode, **kwargs): - stdout = stdout or '' - obj = super(CommandOut, cls).__new__(cls, stdout, **kwargs) - obj.stderr = stderr or '' - obj.command = command - obj.returncode = returncode - return obj - - -class Command(object): - - def _execute(self, cmd, capture=False, executable=None): - """Execute a command using subprocess.Popen() - :Parameters: - - out: stdout from subprocess.Popen() - out has some attributes. - out.returncode: returncode of subprocess.Popen() - out.stderr: stderr from subprocess.Popen() - """ - if capture: - p_stdout = subprocess.PIPE - p_stderr = subprocess.PIPE - else: - p_stdout = None - p_stderr = None - pop = subprocess.Popen(cmd, shell=True, executable=executable, - stdout=p_stdout, - stderr=p_stderr) - __stdout, __stderr = pop.communicate() - _stdout = six.text_type(__stdout, 'utf-8') - _stderr = six.text_type(__stderr, 'utf-8') - out = CommandOut(_stdout, _stderr, cmd, pop.returncode) - return out - - def execute(self, cmd, capture=True, try_times=1, interval=1): - out = None - for i in range(try_times): - out = self._execute(cmd, capture=capture) - LOG.info(out.command) - if out.returncode == 0: - return out - LOG.error("stdout: %s", out) - LOG.error("stderr: %s", out.stderr) - if i + 1 >= try_times: - break - time.sleep(interval) - raise CommandError(out) - - def sudo(self, cmd, capture=True, try_times=1, interval=1): - cmd = 'sudo %s' % cmd - return self.execute(cmd, capture=capture, - try_times=try_times, interval=interval) - - -class DockerImage(object): - def __init__(self, baseimage='ubuntu:16.04'): - self.baseimage = baseimage - self.cmd = Command() - - def get_images(self): - out = self.cmd.sudo('sudo docker images') - images = [] - for line in out.splitlines()[1:]: - images.append(line.split()[0]) - return images - - def exist(self, name): - return name in self.get_images() - - def build(self, tagname, dockerfile_dir): - self.cmd.sudo( - "docker build -t {0} {1}".format(tagname, dockerfile_dir), - try_times=3) - - def remove(self, tagname, check_exist=False): - if check_exist and not self.exist(tagname): - return tagname - self.cmd.sudo("docker rmi -f %s" % tagname, try_times=3) - - def create_quagga(self, tagname='quagga', image=None, check_exist=False): - if check_exist and self.exist(tagname): - return tagname - workdir = os.path.join(TEST_BASE_DIR, tagname) - pkges = ' '.join([ - 'telnet', - 'tcpdump', - 'quagga', - ]) - if image: - use_image = image - else: - use_image = self.baseimage - c = CmdBuffer() - c << 'FROM %s' % use_image - c << 'RUN apt-get update' - c << 'RUN apt-get install -qy --no-install-recommends %s' % pkges - c << 'CMD /usr/lib/quagga/bgpd' - - self.cmd.sudo('rm -rf %s' % workdir) - self.cmd.execute('mkdir -p %s' % workdir) - self.cmd.execute("echo '%s' > %s/Dockerfile" % (str(c), workdir)) - self.build(tagname, workdir) - return tagname - - def create_ryu(self, tagname='ryu', image=None, check_exist=False): - if check_exist and self.exist(tagname): - return tagname - workdir = os.path.join(TEST_BASE_DIR, tagname) - workdir_ctn = '/root/osrg/ryu' - pkges = ' '.join([ - 'tcpdump', - 'iproute2', - ]) - if image: - use_image = image - else: - use_image = self.baseimage - c = CmdBuffer() - c << 'FROM %s' % use_image - c << 'ADD ryu %s' % workdir_ctn - install = ' '.join([ - 'RUN apt-get update', - '&& apt-get install -qy --no-install-recommends %s' % pkges, - '&& cd %s' % workdir_ctn, - # Note: Clean previous builds, because "python setup.py install" - # might fail if the current directory contains the symlink to - # Docker host file systems. - '&& rm -rf *.egg-info/ build/ dist/ .tox/ *.log' - '&& pip install -r tools/pip-requires -r tools/optional-requires', - '&& python setup.py install', - ]) - c << install - - self.cmd.sudo('rm -rf %s' % workdir) - self.cmd.execute('mkdir -p %s' % workdir) - self.cmd.execute("echo '%s' > %s/Dockerfile" % (str(c), workdir)) - self.cmd.execute('cp -r ../ryu %s/' % workdir) - self.build(tagname, workdir) - return tagname - - -class Bridge(object): - def __init__(self, name, subnet='', start_ip=None, end_ip=None, - with_ip=True, self_ip=False, - fixed_ip=None, reuse=False, - br_type='docker'): - """Manage a bridge - :Parameters: - - name: bridge name - - subnet: network cider to be used in this bridge - - start_ip: start address of an ip to be used in the subnet - - end_ip: end address of an ip to be used in the subnet - - with_ip: specify if assign automatically an ip address - - self_ip: specify if assign an ip address for the bridge - - fixed_ip: an ip address to be assigned to the bridge - - reuse: specify if use an existing bridge - - br_type: One either in a 'docker', 'brctl' or 'ovs' - """ - self.cmd = Command() - self.name = name - if br_type not in (BRIDGE_TYPE_DOCKER, BRIDGE_TYPE_BRCTL, - BRIDGE_TYPE_OVS): - raise Exception("argument error br_type: %s" % br_type) - self.br_type = br_type - self.docker_nw = bool(self.br_type == BRIDGE_TYPE_DOCKER) - if TEST_PREFIX != '': - self.name = '{0}_{1}'.format(TEST_PREFIX, name) - self.with_ip = with_ip - if with_ip: - self.subnet = netaddr.IPNetwork(subnet) - if start_ip: - self.start_ip = start_ip - else: - self.start_ip = netaddr.IPAddress(self.subnet.first) - if end_ip: - self.end_ip = end_ip - else: - self.end_ip = netaddr.IPAddress(self.subnet.last) - - def _ip_gen(): - for host in netaddr.IPRange(self.start_ip, self.end_ip): - yield host - self._ip_generator = _ip_gen() - # throw away first network address - self.next_ip_address() - - self.self_ip = self_ip - if fixed_ip: - self.ip_addr = fixed_ip - else: - self.ip_addr = self.next_ip_address() - if not reuse: - def f(): - if self.br_type == BRIDGE_TYPE_DOCKER: - gw = "--gateway %s" % self.ip_addr.split('/')[0] - v6 = '' - if self.subnet.version == 6: - v6 = '--ipv6' - cmd = ("docker network create --driver bridge %s " - "%s --subnet %s %s" % (v6, gw, subnet, self.name)) - elif self.br_type == BRIDGE_TYPE_BRCTL: - cmd = "ip link add {0} type bridge".format(self.name) - elif self.br_type == BRIDGE_TYPE_OVS: - cmd = "ovs-vsctl add-br {0}".format(self.name) - else: - raise ValueError('Unsupported br_type: %s' % self.br_type) - self.delete() - self.execute(cmd, sudo=True, retry=True) - try_several_times(f) - if not self.docker_nw: - self.execute("ip link set up dev {0}".format(self.name), - sudo=True, retry=True) - - if not self.docker_nw and self_ip: - ips = self.check_br_addr(self.name) - for key, ip in ips.items(): - if self.subnet.version == key: - self.execute( - "ip addr del {0} dev {1}".format(ip, self.name), - sudo=True, retry=True) - self.execute( - "ip addr add {0} dev {1}".format(self.ip_addr, self.name), - sudo=True, retry=True) - self.ctns = [] - - def get_bridges_dc(self): - out = self.execute('docker network ls', sudo=True, retry=True) - bridges = [] - for line in out.splitlines()[1:]: - bridges.append(line.split()[1]) - return bridges - - def get_bridges_brctl(self): - out = self.execute('brctl show', retry=True) - bridges = [] - for line in out.splitlines()[1:]: - bridges.append(line.split()[0]) - return bridges - - def get_bridges_ovs(self): - out = self.execute('ovs-vsctl list-br', sudo=True, retry=True) - return out.splitlines() - - def get_bridges(self): - if self.br_type == BRIDGE_TYPE_DOCKER: - return self.get_bridges_dc() - elif self.br_type == BRIDGE_TYPE_BRCTL: - return self.get_bridges_brctl() - elif self.br_type == BRIDGE_TYPE_OVS: - return self.get_bridges_ovs() - - def exist(self): - return self.name in self.get_bridges() - - def execute(self, cmd, capture=True, sudo=False, retry=False): - if sudo: - m = self.cmd.sudo - else: - m = self.cmd.execute - if retry: - return m(cmd, capture=capture, try_times=3, interval=1) - else: - return m(cmd, capture=capture) - - def check_br_addr(self, br): - ips = {} - cmd = "ip a show dev %s" % br - for line in self.execute(cmd, sudo=True).split('\n'): - if line.strip().startswith("inet "): - elems = [e.strip() for e in line.strip().split(' ')] - ips[4] = elems[1] - elif line.strip().startswith("inet6 "): - elems = [e.strip() for e in line.strip().split(' ')] - ips[6] = elems[1] - return ips - - def next_ip_address(self): - return "{0}/{1}".format(next(self._ip_generator), - self.subnet.prefixlen) - - def addif(self, ctn): - name = ctn.next_if_name() - self.ctns.append(ctn) - ip_address = None - if self.docker_nw: - ipv4 = None - ipv6 = None - ip_address = self.next_ip_address() - ip_address_ip = ip_address.split('/')[0] - version = 4 - if netaddr.IPNetwork(ip_address).version == 6: - version = 6 - opt_ip = "--ip %s" % ip_address_ip - if version == 4: - ipv4 = ip_address - else: - opt_ip = "--ip6 %s" % ip_address_ip - ipv6 = ip_address - cmd = "docker network connect %s %s %s" % ( - opt_ip, self.name, ctn.docker_name()) - self.execute(cmd, sudo=True) - ctn.set_addr_info(bridge=self.name, ipv4=ipv4, ipv6=ipv6, - ifname=name) - else: - if self.with_ip: - ip_address = self.next_ip_address() - version = 4 - if netaddr.IPNetwork(ip_address).version == 6: - version = 6 - ctn.pipework(self, ip_address, name, version=version) - else: - ctn.pipework(self, '0/0', name) - return ip_address - - def delete(self, check_exist=True): - if check_exist: - if not self.exist(): - return - if self.br_type == BRIDGE_TYPE_DOCKER: - self.execute("docker network rm %s" % self.name, - sudo=True, retry=True) - elif self.br_type == BRIDGE_TYPE_BRCTL: - self.execute("ip link set down dev %s" % self.name, - sudo=True, retry=True) - self.execute( - "ip link delete %s type bridge" % self.name, - sudo=True, retry=True) - elif self.br_type == BRIDGE_TYPE_OVS: - self.execute( - "ovs-vsctl del-br %s" % self.name, - sudo=True, retry=True) - - -class Container(object): - def __init__(self, name, image=None): - self.name = name - self.image = image - self.shared_volumes = [] - self.ip_addrs = [] - self.ip6_addrs = [] - self.is_running = False - self.eths = [] - self.id = None - - self.cmd = Command() - self.remove() - - def docker_name(self): - if TEST_PREFIX == DEFAULT_TEST_PREFIX: - return self.name - return '{0}_{1}'.format(TEST_PREFIX, self.name) - - def get_docker_id(self): - if self.id: - return self.id - else: - return self.docker_name() - - def next_if_name(self): - name = 'eth{0}'.format(len(self.eths) + 1) - self.eths.append(name) - return name - - def set_addr_info(self, bridge, ipv4=None, ipv6=None, ifname='eth0'): - if ipv4: - self.ip_addrs.append((ifname, ipv4, bridge)) - if ipv6: - self.ip6_addrs.append((ifname, ipv6, bridge)) - - def get_addr_info(self, bridge, ipv=4): - addrinfo = {} - if ipv == 4: - ip_addrs = self.ip_addrs - elif ipv == 6: - ip_addrs = self.ip6_addrs - else: - return None - for addr in ip_addrs: - if addr[2] == bridge: - addrinfo[addr[1]] = addr[0] - return addrinfo - - def execute(self, cmd, capture=True, sudo=False, retry=False): - if sudo: - m = self.cmd.sudo - else: - m = self.cmd.execute - if retry: - return m(cmd, capture=capture, try_times=3, interval=1) - else: - return m(cmd, capture=capture) - - def dcexec(self, cmd, capture=True, retry=False): - if retry: - return self.cmd.sudo(cmd, capture=capture, try_times=3, interval=1) - else: - return self.cmd.sudo(cmd, capture=capture) - - def exec_on_ctn(self, cmd, capture=True, detach=False): - name = self.docker_name() - flag = '-d' if detach else '' - return self.dcexec('docker exec {0} {1} {2}'.format( - flag, name, cmd), capture=capture) - - def get_containers(self, allctn=False): - cmd = 'docker ps --no-trunc=true' - if allctn: - cmd += ' --all=true' - out = self.dcexec(cmd, retry=True) - containers = [] - for line in out.splitlines()[1:]: - containers.append(line.split()[-1]) - return containers - - def exist(self, allctn=False): - return self.docker_name() in self.get_containers(allctn=allctn) - - def run(self): - c = CmdBuffer(' ') - c << "docker run --privileged=true" - for sv in self.shared_volumes: - c << "-v {0}:{1}".format(sv[0], sv[1]) - c << "--name {0} --hostname {0} -id {1}".format(self.docker_name(), - self.image) - self.id = self.dcexec(str(c), retry=True) - self.is_running = True - self.exec_on_ctn("ip li set up dev lo") - ipv4 = None - ipv6 = None - for line in self.exec_on_ctn("ip a show dev eth0").split('\n'): - if line.strip().startswith("inet "): - elems = [e.strip() for e in line.strip().split(' ')] - ipv4 = elems[1] - elif line.strip().startswith("inet6 "): - elems = [e.strip() for e in line.strip().split(' ')] - ipv6 = elems[1] - self.set_addr_info(bridge='docker0', ipv4=ipv4, ipv6=ipv6, - ifname='eth0') - return 0 - - def stop(self, check_exist=True): - if check_exist: - if not self.exist(allctn=False): - return - ctn_id = self.get_docker_id() - out = self.dcexec('docker stop -t 0 %s' % ctn_id, retry=True) - self.is_running = False - return out - - def remove(self, check_exist=True): - if check_exist: - if not self.exist(allctn=True): - return - ctn_id = self.get_docker_id() - out = self.dcexec('docker rm -f %s' % ctn_id, retry=True) - self.is_running = False - return out - - def pipework(self, bridge, ip_addr, intf_name="", version=4): - if not self.is_running: - LOG.warning('Call run() before pipeworking') - return - c = CmdBuffer(' ') - c << "pipework {0}".format(bridge.name) - - if intf_name != "": - c << "-i {0}".format(intf_name) - else: - intf_name = "eth1" - ipv4 = None - ipv6 = None - if version == 4: - ipv4 = ip_addr - else: - c << '-a 6' - ipv6 = ip_addr - c << "{0} {1}".format(self.docker_name(), ip_addr) - self.set_addr_info(bridge=bridge.name, ipv4=ipv4, ipv6=ipv6, - ifname=intf_name) - self.execute(str(c), sudo=True, retry=True) - - def get_pid(self): - if self.is_running: - cmd = "docker inspect -f '{{.State.Pid}}' %s" % self.docker_name() - return int(self.dcexec(cmd)) - return -1 - - def start_tcpdump(self, interface=None, filename=None): - if not interface: - interface = "eth0" - if not filename: - filename = "{0}/{1}.dump".format( - self.shared_volumes[0][1], interface) - self.exec_on_ctn( - "tcpdump -i {0} -w {1}".format(interface, filename), - detach=True) - - -class BGPContainer(Container): - - WAIT_FOR_BOOT = 1 - RETRY_INTERVAL = 5 - DEFAULT_PEER_ARG = {'neigh_addr': '', - 'passwd': None, - 'vpn': False, - 'flowspec': False, - 'is_rs_client': False, - 'is_rr_client': False, - 'cluster_id': None, - 'policies': None, - 'passive': False, - 'local_addr': '', - 'as2': False, - 'graceful_restart': None, - 'local_as': None, - 'prefix_limit': None} - default_peer_keys = sorted(DEFAULT_PEER_ARG.keys()) - DEFAULT_ROUTE_ARG = {'prefix': None, - 'rf': 'ipv4', - 'attr': None, - 'next-hop': None, - 'as-path': None, - 'community': None, - 'med': None, - 'local-pref': None, - 'extended-community': None, - 'matchs': None, - 'thens': None} - default_route_keys = sorted(DEFAULT_ROUTE_ARG.keys()) - - def __init__(self, name, asn, router_id, ctn_image_name=None): - self.config_dir = TEST_BASE_DIR - if TEST_PREFIX: - self.config_dir = os.path.join(self.config_dir, TEST_PREFIX) - self.config_dir = os.path.join(self.config_dir, name) - self.asn = asn - self.router_id = router_id - self.peers = {} - self.routes = {} - self.policies = {} - super(BGPContainer, self).__init__(name, ctn_image_name) - self.execute( - 'rm -rf {0}'.format(self.config_dir), sudo=True) - self.execute('mkdir -p {0}'.format(self.config_dir)) - self.execute('chmod 777 {0}'.format(self.config_dir)) - - def __repr__(self): - return str({'name': self.name, 'asn': self.asn, - 'router_id': self.router_id}) - - def run(self, wait=False, w_time=WAIT_FOR_BOOT): - self.create_config() - super(BGPContainer, self).run() - if wait: - time.sleep(w_time) - return w_time - - def add_peer(self, peer, bridge='', reload_config=True, v6=False, - peer_info=None): - peer_info = peer_info or {} - self.peers[peer] = self.DEFAULT_PEER_ARG.copy() - self.peers[peer].update(peer_info) - peer_keys = sorted(self.peers[peer].keys()) - if peer_keys != self.default_peer_keys: - raise Exception("argument error peer_info: %s" % peer_info) - - neigh_addr = '' - local_addr = '' - it = itertools.product(self.ip_addrs, peer.ip_addrs) - if v6: - it = itertools.product(self.ip6_addrs, peer.ip6_addrs) - - for me, you in it: - if bridge != '' and bridge != me[2]: - continue - if me[2] == you[2]: - neigh_addr = you[1] - local_addr = me[1] - if v6: - addr, mask = local_addr.split('/') - local_addr = "{0}%{1}/{2}".format(addr, me[0], mask) - break - - if neigh_addr == '': - raise Exception('peer {0} seems not ip reachable'.format(peer)) - - if not self.peers[peer]['policies']: - self.peers[peer]['policies'] = {} - - self.peers[peer]['neigh_addr'] = neigh_addr - self.peers[peer]['local_addr'] = local_addr - if self.is_running and reload_config: - self.create_config() - self.reload_config() - - def del_peer(self, peer, reload_config=True): - del self.peers[peer] - if self.is_running and reload_config: - self.create_config() - self.reload_config() - - def disable_peer(self, peer): - raise NotImplementedError() - - def enable_peer(self, peer): - raise NotImplementedError() - - def log(self): - return self.execute('cat {0}/*.log'.format(self.config_dir)) - - def add_route(self, route, reload_config=True, route_info=None): - route_info = route_info or {} - self.routes[route] = self.DEFAULT_ROUTE_ARG.copy() - self.routes[route].update(route_info) - route_keys = sorted(self.routes[route].keys()) - if route_keys != self.default_route_keys: - raise Exception("argument error route_info: %s" % route_info) - self.routes[route]['prefix'] = route - if self.is_running and reload_config: - self.create_config() - self.reload_config() - - def add_policy(self, policy, peer, typ, default='accept', - reload_config=True): - self.set_default_policy(peer, typ, default) - self.define_policy(policy) - self.assign_policy(peer, policy, typ) - if self.is_running and reload_config: - self.create_config() - self.reload_config() - - def set_default_policy(self, peer, typ, default): - if (typ in ['in', 'out', 'import', 'export'] and - default in ['reject', 'accept']): - if 'default-policy' not in self.peers[peer]: - self.peers[peer]['default-policy'] = {} - self.peers[peer]['default-policy'][typ] = default - else: - raise Exception('wrong type or default') - - def define_policy(self, policy): - self.policies[policy['name']] = policy - - def assign_policy(self, peer, policy, typ): - if peer not in self.peers: - raise Exception('peer {0} not found'.format(peer.name)) - name = policy['name'] - if name not in self.policies: - raise Exception('policy {0} not found'.format(name)) - self.peers[peer]['policies'][typ] = policy - - def get_local_rib(self, peer, rf): - raise NotImplementedError() - - def get_global_rib(self, rf): - raise NotImplementedError() - - def get_neighbor_state(self, peer_id): - raise NotImplementedError() - - def get_reachablily(self, prefix, timeout=20): - version = netaddr.IPNetwork(prefix).version - addr = prefix.split('/')[0] - if version == 4: - ping_cmd = 'ping' - elif version == 6: - ping_cmd = 'ping6' - else: - raise Exception( - 'unsupported route family: {0}'.format(version)) - cmd = '/bin/bash -c "/bin/{0} -c 1 -w 1 {1} | xargs echo"'.format( - ping_cmd, addr) - interval = 1 - count = 0 - while True: - res = self.exec_on_ctn(cmd) - LOG.info(res) - if '1 packets received' in res and '0% packet loss': - break - time.sleep(interval) - count += interval - if count >= timeout: - raise Exception('timeout') - return True - - def wait_for(self, expected_state, peer, timeout=120): - interval = 1 - count = 0 - while True: - state = self.get_neighbor_state(peer) - LOG.info("%s's peer %s state: %s", - self.router_id, peer.router_id, state) - if state == expected_state: - return - - time.sleep(interval) - count += interval - if count >= timeout: - raise Exception('timeout') - - def add_static_route(self, network, next_hop): - cmd = '/sbin/ip route add {0} via {1}'.format(network, next_hop) - self.exec_on_ctn(cmd) - - def set_ipv6_forward(self): - cmd = 'sysctl -w net.ipv6.conf.all.forwarding=1' - self.exec_on_ctn(cmd) - - def create_config(self): - raise NotImplementedError() - - def reload_config(self): - raise NotImplementedError() diff --git a/ryu/tests/integrated/common/install_docker_test_pkg.sh b/ryu/tests/integrated/common/install_docker_test_pkg.sh deleted file mode 100644 index a771dfc..0000000 --- a/ryu/tests/integrated/common/install_docker_test_pkg.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -set -ex - -RYU_PATH=`dirname $0` - -source ${RYU_PATH}/install_docker_test_pkg_common.sh - -function add_docker_aptline { - sudo apt-get update - if ! apt-cache search docker-engine | grep docker-engine; then - VER=`lsb_release -r` - if echo $VER | grep 12.04; then - REL_NAME=precise - elif echo $VER | grep 14.04; then - REL_NAME=trusty - elif echo $VER | grep 15.10; then - REL_NAME=wily - elif echo $VER | grep 16.04; then - REL_NAME=xenial - else - retrun 1 - fi - RELEASE=ubuntu-$REL_NAME - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D - sudo sh -c "echo deb https://apt.dockerproject.org/repo $RELEASE main > /etc/apt/sources.list.d/docker.list" - fi -} - -init_variables -process_options "$@" - -if [ $APTLINE_DOCKER -eq 1 ]; then - add_docker_aptline -fi - -sudo apt-get update -if apt-cache search docker-engine | grep docker-engine; then - DOCKER_PKG=docker-engine -else - DOCKER_PKG=docker.io -fi -sudo apt-get install -y $DOCKER_PKG -install_depends_pkg diff --git a/ryu/tests/integrated/common/install_docker_test_pkg_common.sh b/ryu/tests/integrated/common/install_docker_test_pkg_common.sh deleted file mode 100644 index 44a3e10..0000000 --- a/ryu/tests/integrated/common/install_docker_test_pkg_common.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -set -ex - -function init_variables { - APTLINE_DOCKER=0 - DIR_BASE=/tmp -} - -function process_options { - local max - local i - max=$# - i=1 - while [ $i -le $max ]; do - case "$1" in - -a|--add-docker-aptline) - APTLINE_DOCKER=1 - ;; - -d|--download-dir) - shift; ((i++)) - DIR_BASE=$1 - ;; - esac - shift; ((i++)) - done -} - -function install_pipework { - if ! which /usr/local/bin/pipework >/dev/null - then - sudo rm -rf $DIR_BASE/pipework - git clone https://github.com/jpetazzo/pipework.git $DIR_BASE/pipework - sudo install -m 0755 $DIR_BASE/pipework/pipework /usr/local/bin/pipework - fi -} - -function install_depends_pkg { - install_pipework -} diff --git a/ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh b/ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh deleted file mode 100644 index d8c3b49..0000000 --- a/ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -ex - -RYU_PATH=`dirname $0` - -source ${RYU_PATH}/install_docker_test_pkg_common.sh - -init_variables -process_options "$@" - -sudo apt-get update -install_depends_pkg diff --git a/ryu/tests/integrated/common/quagga.py b/ryu/tests/integrated/common/quagga.py deleted file mode 100644 index 9b6d218..0000000 --- a/ryu/tests/integrated/common/quagga.py +++ /dev/null @@ -1,332 +0,0 @@ -# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. -# -# This is based on the following -# https://github.com/osrg/gobgp/test/lib/quagga.py -# -# 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 __future__ import absolute_import - -import logging -import os - -import netaddr - -from . import docker_base as base - -LOG = logging.getLogger(__name__) - - -class QuaggaBGPContainer(base.BGPContainer): - - WAIT_FOR_BOOT = 1 - SHARED_VOLUME = '/etc/quagga' - - def __init__(self, name, asn, router_id, ctn_image_name, zebra=False): - super(QuaggaBGPContainer, self).__init__(name, asn, router_id, - ctn_image_name) - self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) - self.zebra = zebra - self._create_config_debian() - - def run(self, wait=False, w_time=WAIT_FOR_BOOT): - w_time = super(QuaggaBGPContainer, - self).run(wait=wait, w_time=self.WAIT_FOR_BOOT) - return w_time - - def get_global_rib(self, prefix='', rf='ipv4'): - rib = [] - if prefix != '': - return self.get_global_rib_with_prefix(prefix, rf) - - out = self.vtysh('show bgp {0} unicast'.format(rf), config=False) - if out.startswith('No BGP network exists'): - return rib - - read_next = False - - for line in out.split('\n'): - ibgp = False - if line[:2] == '*>': - line = line[2:] - if line[0] == 'i': - line = line[1:] - ibgp = True - elif not read_next: - continue - - elems = line.split() - - if len(elems) == 1: - read_next = True - prefix = elems[0] - continue - elif read_next: - nexthop = elems[0] - else: - prefix = elems[0] - nexthop = elems[1] - read_next = False - - rib.append({'prefix': prefix, 'nexthop': nexthop, - 'ibgp': ibgp}) - - return rib - - def get_global_rib_with_prefix(self, prefix, rf): - rib = [] - - lines = [line.strip() for line in self.vtysh( - 'show bgp {0} unicast {1}'.format(rf, prefix), - config=False).split('\n')] - - if lines[0] == '% Network not in table': - return rib - - lines = lines[2:] - - if lines[0].startswith('Not advertised'): - lines.pop(0) # another useless line - elif lines[0].startswith('Advertised to non peer-group peers:'): - lines = lines[2:] # other useless lines - else: - raise Exception('unknown output format {0}'.format(lines)) - - if lines[0] == 'Local': - aspath = [] - else: - aspath = [int(asn) for asn in lines[0].split()] - - nexthop = lines[1].split()[0].strip() - info = [s.strip(',') for s in lines[2].split()] - attrs = [] - if 'metric' in info: - med = info[info.index('metric') + 1] - attrs.append({'type': base.BGP_ATTR_TYPE_MULTI_EXIT_DISC, - 'metric': int(med)}) - if 'localpref' in info: - localpref = info[info.index('localpref') + 1] - attrs.append({'type': base.BGP_ATTR_TYPE_LOCAL_PREF, - 'value': int(localpref)}) - - rib.append({'prefix': prefix, 'nexthop': nexthop, - 'aspath': aspath, 'attrs': attrs}) - - return rib - - def get_neighbor_state(self, peer): - if peer not in self.peers: - raise Exception('not found peer {0}'.format(peer.router_id)) - - neigh_addr = self.peers[peer]['neigh_addr'].split('/')[0] - - info = [l.strip() for l in self.vtysh( - 'show bgp neighbors {0}'.format(neigh_addr), - config=False).split('\n')] - - if not info[0].startswith('BGP neighbor is'): - raise Exception('unknown format') - - idx1 = info[0].index('BGP neighbor is ') - idx2 = info[0].index(',') - n_addr = info[0][idx1 + len('BGP neighbor is '):idx2] - if n_addr == neigh_addr: - idx1 = info[2].index('= ') - state = info[2][idx1 + len('= '):] - if state.startswith('Idle'): - return base.BGP_FSM_IDLE - elif state.startswith('Active'): - return base.BGP_FSM_ACTIVE - elif state.startswith('Established'): - return base.BGP_FSM_ESTABLISHED - else: - return state - - raise Exception('not found peer {0}'.format(peer.router_id)) - - def send_route_refresh(self): - self.vtysh('clear ip bgp * soft', config=False) - - def create_config(self): - zebra = 'no' - self._create_config_bgp() - if self.zebra: - zebra = 'yes' - self._create_config_zebra() - self._create_config_daemons(zebra) - - def _create_config_debian(self): - c = base.CmdBuffer() - c << 'vtysh_enable=yes' - c << 'zebra_options=" --daemon -A 127.0.0.1"' - c << 'bgpd_options=" --daemon -A 127.0.0.1"' - c << 'ospfd_options=" --daemon -A 127.0.0.1"' - c << 'ospf6d_options=" --daemon -A ::1"' - c << 'ripd_options=" --daemon -A 127.0.0.1"' - c << 'ripngd_options=" --daemon -A ::1"' - c << 'isisd_options=" --daemon -A 127.0.0.1"' - c << 'babeld_options=" --daemon -A 127.0.0.1"' - c << 'watchquagga_enable=yes' - c << 'watchquagga_options=(--daemon)' - with open('{0}/debian.conf'.format(self.config_dir), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def _create_config_daemons(self, zebra='no'): - c = base.CmdBuffer() - c << 'zebra=%s' % zebra - c << 'bgpd=yes' - c << 'ospfd=no' - c << 'ospf6d=no' - c << 'ripd=no' - c << 'ripngd=no' - c << 'isisd=no' - c << 'babeld=no' - with open('{0}/daemons'.format(self.config_dir), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def _create_config_bgp(self): - - c = base.CmdBuffer() - c << 'hostname bgpd' - c << 'password zebra' - c << 'router bgp {0}'.format(self.asn) - c << 'bgp router-id {0}'.format(self.router_id) - if any(info['graceful_restart'] for info in self.peers.values()): - c << 'bgp graceful-restart' - - version = 4 - for peer, info in self.peers.items(): - version = netaddr.IPNetwork(info['neigh_addr']).version - n_addr = info['neigh_addr'].split('/')[0] - if version == 6: - c << 'no bgp default ipv4-unicast' - - c << 'neighbor {0} remote-as {1}'.format(n_addr, peer.asn) - if info['is_rs_client']: - c << 'neighbor {0} route-server-client'.format(n_addr) - for typ, p in info['policies'].items(): - c << 'neighbor {0} route-map {1} {2}'.format(n_addr, p['name'], - typ) - if info['passwd']: - c << 'neighbor {0} password {1}'.format(n_addr, info['passwd']) - if info['passive']: - c << 'neighbor {0} passive'.format(n_addr) - if version == 6: - c << 'address-family ipv6 unicast' - c << 'neighbor {0} activate'.format(n_addr) - c << 'exit-address-family' - - for route in self.routes.values(): - if route['rf'] == 'ipv4': - c << 'network {0}'.format(route['prefix']) - elif route['rf'] == 'ipv6': - c << 'address-family ipv6 unicast' - c << 'network {0}'.format(route['prefix']) - c << 'exit-address-family' - else: - raise Exception( - 'unsupported route faily: {0}'.format(route['rf'])) - - if self.zebra: - if version == 6: - c << 'address-family ipv6 unicast' - c << 'redistribute connected' - c << 'exit-address-family' - else: - c << 'redistribute connected' - - for name, policy in self.policies.items(): - c << 'access-list {0} {1} {2}'.format(name, policy['type'], - policy['match']) - c << 'route-map {0} permit 10'.format(name) - c << 'match ip address {0}'.format(name) - c << 'set metric {0}'.format(policy['med']) - - c << 'debug bgp as4' - c << 'debug bgp fsm' - c << 'debug bgp updates' - c << 'debug bgp events' - c << 'log file {0}/bgpd.log'.format(self.SHARED_VOLUME) - - with open('{0}/bgpd.conf'.format(self.config_dir), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def _create_config_zebra(self): - c = base.CmdBuffer() - c << 'hostname zebra' - c << 'password zebra' - c << 'log file {0}/zebra.log'.format(self.SHARED_VOLUME) - c << 'debug zebra packet' - c << 'debug zebra kernel' - c << 'debug zebra rib' - c << '' - - with open('{0}/zebra.conf'.format(self.config_dir), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def vtysh(self, cmd, config=True): - if not isinstance(cmd, list): - cmd = [cmd] - cmd = ' '.join("-c '{0}'".format(c) for c in cmd) - if config: - return self.exec_on_ctn( - "vtysh -d bgpd -c 'en' -c 'conf t' -c " - "'router bgp {0}' {1}".format(self.asn, cmd), - capture=True) - else: - return self.exec_on_ctn("vtysh -d bgpd {0}".format(cmd), - capture=True) - - def reload_config(self): - daemon = [] - daemon.append('bgpd') - if self.zebra: - daemon.append('zebra') - for d in daemon: - cmd = '/usr/bin/pkill {0} -SIGHUP'.format(d) - self.exec_on_ctn(cmd, capture=True) - - -class RawQuaggaBGPContainer(QuaggaBGPContainer): - def __init__(self, name, config, ctn_image_name, - zebra=False): - asn = None - router_id = None - for line in config.split('\n'): - line = line.strip() - if line.startswith('router bgp'): - asn = int(line[len('router bgp'):].strip()) - if line.startswith('bgp router-id'): - router_id = line[len('bgp router-id'):].strip() - if not asn: - raise Exception('asn not in quagga config') - if not router_id: - raise Exception('router-id not in quagga config') - self.config = config - super(RawQuaggaBGPContainer, self).__init__(name, asn, router_id, - ctn_image_name, zebra) - - def create_config(self): - with open(os.path.join(self.config_dir, 'bgpd.conf'), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(self.config) - f.writelines(self.config) diff --git a/ryu/tests/integrated/common/ryubgp.py b/ryu/tests/integrated/common/ryubgp.py deleted file mode 100644 index 8fe16f4..0000000 --- a/ryu/tests/integrated/common/ryubgp.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. -# -# 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 __future__ import absolute_import - -import logging -import os -import time - -from . import docker_base as base - -LOG = logging.getLogger(__name__) - - -class RyuBGPContainer(base.BGPContainer): - - WAIT_FOR_BOOT = 1 - SHARED_VOLUME = '/etc/ryu' - - def __init__(self, name, asn, router_id, ctn_image_name): - super(RyuBGPContainer, self).__init__(name, asn, router_id, - ctn_image_name) - self.RYU_CONF = os.path.join(self.config_dir, 'ryu.conf') - self.SHARED_RYU_CONF = os.path.join(self.SHARED_VOLUME, 'ryu.conf') - self.SHARED_BGP_CONF = os.path.join(self.SHARED_VOLUME, 'bgp_conf.py') - self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) - - def _create_config_ryu(self): - c = base.CmdBuffer() - c << '[DEFAULT]' - c << 'verbose=True' - c << 'log_file=/etc/ryu/manager.log' - with open(self.RYU_CONF, 'w') as f: - LOG.info("[%s's new config]" % self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def _create_config_ryu_bgp(self): - c = base.CmdBuffer() - c << 'import os' - c << '' - c << 'BGP = {' - c << " 'local_as': %s," % str(self.asn) - c << " 'router_id': '%s'," % self.router_id - c << " 'neighbors': [" - c << " {" - for peer, info in self.peers.items(): - n_addr = info['neigh_addr'].split('/')[0] - c << " 'address': '%s'," % n_addr - c << " 'remote_as': %s," % str(peer.asn) - c << " 'enable_ipv4': True," - c << " 'enable_ipv6': True," - c << " 'enable_vpnv4': True," - c << " 'enable_vpnv6': True," - c << ' },' - c << ' ],' - c << " 'routes': [" - for route in self.routes.values(): - c << " {" - c << " 'prefix': '%s'," % route['prefix'] - c << " }," - c << " ]," - c << "}" - log_conf = """LOGGING = { - - # We use python logging package for logging. - 'version': 1, - 'disable_existing_loggers': False, - - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s ' + - '[%(process)d %(thread)d] %(message)s' - }, - 'simple': { - 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)s ' + - '%(message)s' - }, - 'stats': { - 'format': '%(message)s' - }, - }, - - 'handlers': { - # Outputs log to console. - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple' - }, - 'console_stats': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'stats' - }, - # Rotates log file when its size reaches 10MB. - 'log_file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join('.', 'bgpspeaker.log'), - 'maxBytes': '10000000', - 'formatter': 'verbose' - }, - 'stats_file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join('.', 'statistics_bgps.log'), - 'maxBytes': '10000000', - 'formatter': 'stats' - }, - }, - - # Fine-grained control of logging per instance. - 'loggers': { - 'bgpspeaker': { - 'handlers': ['console', 'log_file'], - 'handlers': ['console'], - 'level': 'DEBUG', - 'propagate': False, - }, - 'stats': { - 'handlers': ['stats_file', 'console_stats'], - 'level': 'INFO', - 'propagate': False, - 'formatter': 'stats', - }, - }, - - # Root loggers. - 'root': { - 'handlers': ['console', 'log_file'], - 'level': 'DEBUG', - 'propagate': True, - }, -}""" - c << log_conf - with open(os.path.join(self.config_dir, 'bgp_conf.py'), 'w') as f: - LOG.info("[%s's new config]", self.name) - LOG.info(str(c)) - f.writelines(str(c)) - - def create_config(self): - self._create_config_ryu() - self._create_config_ryu_bgp() - - def is_running_ryu(self): - results = self.exec_on_ctn('ps ax') - running = False - for line in results.split('\n')[1:]: - if 'ryu-manager' in line: - running = True - return running - - def start_ryubgp(self, check_running=True, retry=False): - if check_running: - if self.is_running_ryu(): - return True - result = False - if retry: - try_times = 3 - else: - try_times = 1 - cmd = "ryu-manager --verbose " - cmd += "--config-file %s " % self.SHARED_RYU_CONF - cmd += "--bgp-app-config-file %s " % self.SHARED_BGP_CONF - cmd += "ryu.services.protocols.bgp.application" - for _ in range(try_times): - self.exec_on_ctn(cmd, detach=True) - if self.is_running_ryu(): - result = True - break - time.sleep(1) - return result - - def stop_ryubgp(self, check_running=True, retry=False): - if check_running: - if not self.is_running_ryu(): - return True - result = False - if retry: - try_times = 3 - else: - try_times = 1 - for _ in range(try_times): - cmd = '/usr/bin/pkill ryu-manager -SIGTERM' - self.exec_on_ctn(cmd) - if not self.is_running_ryu(): - result = True - break - time.sleep(1) - return result - - def run(self, wait=False, w_time=WAIT_FOR_BOOT): - w_time = super(RyuBGPContainer, - self).run(wait=wait, w_time=self.WAIT_FOR_BOOT) - return w_time - - def reload_config(self): - self.stop_ryubgp(retry=True) - self.start_ryubgp(retry=True) diff --git a/tests/integrated/bgp/base.py b/tests/integrated/bgp/base.py index 26fa396..2f210de 100644 --- a/tests/integrated/bgp/base.py +++ b/tests/integrated/bgp/base.py @@ -20,9 +20,9 @@ import logging import sys import unittest -from ryu.tests.integrated.common import docker_base as ctn_base -from ryu.tests.integrated.common import ryubgp -from ryu.tests.integrated.common import quagga +from ryu.lib.docker import docker_base as ctn_base +from ryu.lib.docker import ryubgp +from ryu.lib.docker import quagga LOG = logging.getLogger(__name__) diff --git a/tests/integrated/bgp/base_ip6.py b/tests/integrated/bgp/base_ip6.py index be26faf..d867920 100644 --- a/tests/integrated/bgp/base_ip6.py +++ b/tests/integrated/bgp/base_ip6.py @@ -20,9 +20,9 @@ import logging import sys import unittest -from ryu.tests.integrated.common import docker_base as ctn_base -from ryu.tests.integrated.common import ryubgp -from ryu.tests.integrated.common import quagga +from ryu.lib.docker import docker_base as ctn_base +from ryu.lib.docker import ryubgp +from ryu.lib.docker import quagga LOG = logging.getLogger(__name__) diff --git a/tests/integrated/bgp/test_basic.py b/tests/integrated/bgp/test_basic.py index 5817d44..d1eda39 100644 --- a/tests/integrated/bgp/test_basic.py +++ b/tests/integrated/bgp/test_basic.py @@ -18,7 +18,7 @@ from __future__ import absolute_import import time -from ryu.tests.integrated.common import docker_base as ctn_base +from ryu.lib.docker import docker_base as ctn_base from . import base diff --git a/tests/integrated/bgp/test_ip6_basic.py b/tests/integrated/bgp/test_ip6_basic.py index 40461a5..911a0b5 100644 --- a/tests/integrated/bgp/test_ip6_basic.py +++ b/tests/integrated/bgp/test_ip6_basic.py @@ -18,7 +18,7 @@ from __future__ import absolute_import import time -from ryu.tests.integrated.common import docker_base as ctn_base +from ryu.lib.docker import docker_base as ctn_base from . import base_ip6 as base -- 2.7.4 ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Ryu-devel mailing list Ryu-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/ryu-devel