|
| 1 | +import os |
| 2 | +import os.path |
| 3 | +import re |
| 4 | +import time |
| 5 | +import docker |
| 6 | +import pytest |
| 7 | +import commands |
| 8 | +import tarfile |
| 9 | +import StringIO |
| 10 | +import subprocess |
| 11 | +from swsscommon import swsscommon |
| 12 | + |
| 13 | +def pytest_addoption(parser): |
| 14 | + parser.addoption("--dvsname", action="store", default=None, |
| 15 | + help="dvs name") |
| 16 | + |
| 17 | +class AsicDbValidator(object): |
| 18 | + def __init__(self, dvs): |
| 19 | + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) |
| 20 | + |
| 21 | + # get default dot1q vlan id |
| 22 | + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") |
| 23 | + |
| 24 | + keys = atbl.getKeys() |
| 25 | + assert len(keys) == 1 |
| 26 | + self.default_vlan_id = keys[0] |
| 27 | + |
| 28 | + # build port oid to front port name mapping |
| 29 | + self.portoidmap = {} |
| 30 | + self.portnamemap = {} |
| 31 | + self.hostifoidmap = {} |
| 32 | + self.hostifnamemap = {} |
| 33 | + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF") |
| 34 | + keys = atbl.getKeys() |
| 35 | + |
| 36 | + assert len(keys) == 32 |
| 37 | + for k in keys: |
| 38 | + (status, fvs) = atbl.get(k) |
| 39 | + |
| 40 | + assert status == True |
| 41 | + |
| 42 | + for fv in fvs: |
| 43 | + if fv[0] == "SAI_HOSTIF_ATTR_OBJ_ID": |
| 44 | + port_oid = fv[1] |
| 45 | + elif fv[0] == "SAI_HOSTIF_ATTR_NAME": |
| 46 | + port_name = fv[1] |
| 47 | + |
| 48 | + self.portoidmap[port_oid] = port_name |
| 49 | + self.portnamemap[port_name] = port_oid |
| 50 | + self.hostifoidmap[k] = port_name |
| 51 | + self.hostifnamemap[port_name] = k |
| 52 | + |
| 53 | + # get default acl table and acl rules |
| 54 | + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") |
| 55 | + keys = atbl.getKeys() |
| 56 | + |
| 57 | + assert len(keys) == 1 |
| 58 | + self.default_acl_table = keys[0] |
| 59 | + |
| 60 | + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") |
| 61 | + keys = atbl.getKeys() |
| 62 | + |
| 63 | + assert len(keys) == 2 |
| 64 | + self.default_acl_entries = keys |
| 65 | + |
| 66 | +class VirtualServer(object): |
| 67 | + def __init__(self, ctn_name, pid, i): |
| 68 | + self.nsname = "%s-srv%d" % (ctn_name, i) |
| 69 | + self.vifname = "vEthernet%d" % (i * 4) |
| 70 | + self.cleanup = True |
| 71 | + |
| 72 | + # create netns |
| 73 | + if os.path.exists("/var/run/netns/%s" % self.nsname): |
| 74 | + self.cleanup = False |
| 75 | + else: |
| 76 | + os.system("ip netns add %s" % self.nsname) |
| 77 | + |
| 78 | + # create vpeer link |
| 79 | + os.system("ip link add %s type veth peer name %s" % (self.nsname[0:12], self.vifname)) |
| 80 | + os.system("ip link set %s netns %s" % (self.nsname[0:12], self.nsname)) |
| 81 | + os.system("ip link set %s netns %d" % (self.vifname, pid)) |
| 82 | + |
| 83 | + # bring up link in the virtual server |
| 84 | + os.system("ip netns exec %s ip link set dev %s name eth0" % (self.nsname, self.nsname[0:12])) |
| 85 | + os.system("ip netns exec %s ip link set dev eth0 up" % (self.nsname)) |
| 86 | + os.system("ip netns exec %s ethtool -K eth0 tx off" % (self.nsname)) |
| 87 | + |
| 88 | + # bring up link in the virtual switch |
| 89 | + os.system("nsenter -t %d -n ip link set dev %s up" % (pid, self.vifname)) |
| 90 | + |
| 91 | + def __del__(self): |
| 92 | + if self.cleanup: |
| 93 | + os.system("ip netns delete %s" % self.nsname) |
| 94 | + |
| 95 | + def runcmd(self, cmd): |
| 96 | + os.system("ip netns exec %s %s" % (self.nsname, cmd)) |
| 97 | + |
| 98 | + def runcmd_async(self, cmd): |
| 99 | + return subprocess.Popen("ip netns exec %s %s" % (self.nsname, cmd), shell=True) |
| 100 | + |
| 101 | +class DockerVirtualSwitch(object): |
| 102 | + def __init__(self, name=None): |
| 103 | + self.pnames = ['fpmsyncd', |
| 104 | + 'intfmgrd', |
| 105 | + 'intfsyncd', |
| 106 | + 'neighsyncd', |
| 107 | + 'orchagent', |
| 108 | + 'portsyncd', |
| 109 | + 'redis-server', |
| 110 | + 'rsyslogd', |
| 111 | + 'syncd', |
| 112 | + 'teamsyncd', |
| 113 | + 'vlanmgrd', |
| 114 | + 'zebra'] |
| 115 | + self.mount = "/var/run/redis-vs" |
| 116 | + self.redis_sock = self.mount + '/' + "redis.sock" |
| 117 | + self.client = docker.from_env() |
| 118 | + |
| 119 | + self.ctn = None |
| 120 | + self.cleanup = True |
| 121 | + if name != None: |
| 122 | + # get virtual switch container |
| 123 | + for ctn in self.client.containers.list(): |
| 124 | + if ctn.name == name: |
| 125 | + self.ctn = ctn |
| 126 | + (status, output) = commands.getstatusoutput("docker inspect --format '{{.HostConfig.NetworkMode}}' %s" % name) |
| 127 | + ctn_sw_id = output.split(':')[1] |
| 128 | + self.cleanup = False |
| 129 | + if self.ctn == None: |
| 130 | + raise NameError("cannot find container %s" % name) |
| 131 | + |
| 132 | + # get base container |
| 133 | + for ctn in self.client.containers.list(): |
| 134 | + if ctn.id == ctn_sw_id or ctn.name == ctn_sw_id: |
| 135 | + ctn_sw_name = ctn.name |
| 136 | + |
| 137 | + (status, output) = commands.getstatusoutput("docker inspect --format '{{.State.Pid}}' %s" % ctn_sw_name) |
| 138 | + self.ctn_sw_pid = int(output) |
| 139 | + |
| 140 | + # create virtual servers |
| 141 | + self.servers = [] |
| 142 | + for i in range(32): |
| 143 | + server = VirtualServer(ctn_sw_name, self.ctn_sw_pid, i) |
| 144 | + self.servers.append(server) |
| 145 | + |
| 146 | + self.restart() |
| 147 | + else: |
| 148 | + self.ctn_sw = self.client.containers.run('debian:jessie', privileged=True, detach=True, |
| 149 | + command="bash", stdin_open=True) |
| 150 | + (status, output) = commands.getstatusoutput("docker inspect --format '{{.State.Pid}}' %s" % self.ctn_sw.name) |
| 151 | + self.ctn_sw_pid = int(output) |
| 152 | + |
| 153 | + # create virtual server |
| 154 | + self.servers = [] |
| 155 | + for i in range(32): |
| 156 | + server = VirtualServer(self.ctn_sw.name, self.ctn_sw_pid, i) |
| 157 | + self.servers.append(server) |
| 158 | + |
| 159 | + # create virtual switch container |
| 160 | + self.ctn = self.client.containers.run('docker-sonic-vs', privileged=True, detach=True, |
| 161 | + network_mode="container:%s" % self.ctn_sw.name, |
| 162 | + volumes={ self.mount: { 'bind': '/var/run/redis', 'mode': 'rw' } }) |
| 163 | + |
| 164 | + self.ctn.exec_run("sysctl -w net.ipv6.conf.all.disable_ipv6=0") |
| 165 | + self.check_ready() |
| 166 | + self.init_asicdb_validator() |
| 167 | + |
| 168 | + def destroy(self): |
| 169 | + if self.cleanup: |
| 170 | + self.ctn.remove(force=True) |
| 171 | + self.ctn_sw.remove(force=True) |
| 172 | + for s in self.servers: |
| 173 | + del(s) |
| 174 | + |
| 175 | + def check_ready(self, timeout=30): |
| 176 | + '''check if all processes in the dvs is ready''' |
| 177 | + |
| 178 | + re_space = re.compile('\s+') |
| 179 | + process_status = {} |
| 180 | + ready = False |
| 181 | + started = 0 |
| 182 | + while True: |
| 183 | + # get process status |
| 184 | + out = self.ctn.exec_run("supervisorctl status") |
| 185 | + for l in out.split('\n'): |
| 186 | + fds = re_space.split(l) |
| 187 | + if len(fds) < 2: |
| 188 | + continue |
| 189 | + process_status[fds[0]] = fds[1] |
| 190 | + |
| 191 | + # check if all processes are running |
| 192 | + ready = True |
| 193 | + for pname in self.pnames: |
| 194 | + try: |
| 195 | + if process_status[pname] != "RUNNING": |
| 196 | + ready = False |
| 197 | + except KeyError: |
| 198 | + ready = False |
| 199 | + |
| 200 | + if ready == True: |
| 201 | + break |
| 202 | + |
| 203 | + started += 1 |
| 204 | + if started > timeout: |
| 205 | + raise ValueError(out) |
| 206 | + |
| 207 | + time.sleep(1) |
| 208 | + |
| 209 | + def restart(self): |
| 210 | + self.ctn.restart() |
| 211 | + |
| 212 | + def init_asicdb_validator(self): |
| 213 | + self.asicdb = AsicDbValidator(self) |
| 214 | + |
| 215 | + def runcmd(self, cmd): |
| 216 | + return self.ctn.exec_run(cmd) |
| 217 | + |
| 218 | + def copy_file(self, path, filename): |
| 219 | + tarstr = StringIO.StringIO() |
| 220 | + tar = tarfile.open(fileobj=tarstr, mode="w") |
| 221 | + tar.add(filename, os.path.basename(filename)) |
| 222 | + tar.close() |
| 223 | + self.ctn.put_archive(path, tarstr.getvalue()) |
| 224 | + tarstr.close() |
| 225 | + |
| 226 | +@pytest.yield_fixture(scope="module") |
| 227 | +def dvs(request): |
| 228 | + name = request.config.getoption("--dvsname") |
| 229 | + dvs = DockerVirtualSwitch(name) |
| 230 | + yield dvs |
| 231 | + dvs.destroy() |
0 commit comments