A R Karthick | d0fdf3b | 2017-03-21 16:54:22 -0700 | [diff] [blame] | 1 | import os |
| 2 | import shutil |
| 3 | import re |
| 4 | from novaclient import client as nova_client |
| 5 | from SSHTestAgent import SSHTestAgent |
| 6 | from CordTestUtils import * |
| 7 | |
| 8 | class VSGAccess(object): |
| 9 | |
| 10 | vcpe_map = {} |
| 11 | interface_map = {} |
| 12 | ip_addr_pattern = re.compile('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$') |
| 13 | |
| 14 | @classmethod |
| 15 | def setUp(cls): |
| 16 | try: |
| 17 | shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.orig') |
| 18 | except: |
| 19 | pass |
| 20 | |
| 21 | @classmethod |
| 22 | def tearDown(cls): |
| 23 | try: |
| 24 | shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf') |
| 25 | except: |
| 26 | pass |
| 27 | |
| 28 | @classmethod |
| 29 | def get_nova_credentials_v2(cls): |
| 30 | credential = {} |
| 31 | credential['username'] = os.environ['OS_USERNAME'] |
| 32 | credential['api_key'] = os.environ['OS_PASSWORD'] |
| 33 | credential['auth_url'] = os.environ['OS_AUTH_URL'] |
| 34 | credential['project_id'] = os.environ['OS_TENANT_NAME'] |
| 35 | return credential |
| 36 | |
| 37 | @classmethod |
| 38 | def get_compute_nodes(cls): |
| 39 | credentials = cls.get_nova_credentials_v2() |
| 40 | nvclient = nova_client.Client('2', **credentials) |
| 41 | return nvclient.hypervisors.list() |
| 42 | |
| 43 | @classmethod |
| 44 | def get_vsgs(cls, active = True): |
| 45 | credentials = cls.get_nova_credentials_v2() |
| 46 | nvclient = nova_client.Client('2', **credentials) |
| 47 | vsgs = nvclient.servers.list(search_opts = {'all_tenants': 1}) |
| 48 | if active is True: |
| 49 | vsgs = filter(lambda vsg: vsg.status == 'ACTIVE', vsgs) |
| 50 | vsg_wrappers = [] |
| 51 | for vsg in vsgs: |
| 52 | vsg_wrappers.append(VSGWrapper(vsg)) |
| 53 | return vsg_wrappers |
| 54 | |
| 55 | @classmethod |
| 56 | def open_mgmt(cls, intf = 'eth0'): |
| 57 | if intf in cls.interface_map: |
| 58 | gw = cls.interface_map[intf]['gw'] |
| 59 | ip = cls.interface_map[intf]['ip'] |
| 60 | if gw != '0.0.0.0': |
| 61 | current_gw, _ = get_default_gw() |
| 62 | cmds = [ 'route del default gw {}'.format(current_gw), |
| 63 | 'ifconfig {} {} up'.format(intf, ip), |
| 64 | 'route add default gw {}'.format(gw) ] |
| 65 | for cmd in cmds: |
| 66 | os.system(cmd) |
| 67 | shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.lastdhcp') |
| 68 | shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf') |
| 69 | return current_gw |
| 70 | return None |
| 71 | |
| 72 | @classmethod |
| 73 | def close_mgmt(cls, restore_gw, intf = 'eth0'): |
| 74 | if restore_gw: |
| 75 | cmds = [ 'route del default gw 0.0.0.0', |
| 76 | 'route add default gw {}'.format(restore_gw), |
| 77 | 'cp /etc/resolv.conf.lastdhcp /etc/resolv.conf', |
| 78 | 'rm -f /etc/resolv.conf.lastdhcp' |
| 79 | ] |
| 80 | for cmd in cmds: |
| 81 | os.system(cmd) |
| 82 | |
| 83 | @classmethod |
| 84 | def health_check(cls): |
| 85 | '''Returns 0 if all active vsgs are reachable through the compute node''' |
| 86 | vsgs = cls.get_vsgs() |
| 87 | vsg_status = [] |
| 88 | for vsg in vsgs: |
| 89 | vsg_status.append(vsg.get_health()) |
| 90 | unreachable = filter(lambda st: st == False, vsg_status) |
| 91 | return len(unreachable) == 0 |
| 92 | |
| 93 | @classmethod |
| 94 | def get_vcpe_vsg(cls, vcpe): |
| 95 | '''Find the vsg hosting the vcpe service''' |
| 96 | if vcpe in cls.vcpe_map: |
| 97 | return cls.vcpe_map[vcpe]['vsg'] |
| 98 | vsgs = cls.get_vsgs() |
| 99 | for vsg in vsgs: |
| 100 | cmd = 'sudo docker exec {} ls 2>/dev/null'.format(vcpe) |
| 101 | st, _ = vsg.run_cmd(cmd, timeout = 30) |
| 102 | if st == True: |
| 103 | return vsg |
| 104 | return None |
| 105 | |
| 106 | @classmethod |
| 107 | def save_vcpe_config(cls, vsg, vcpe): |
| 108 | if vcpe not in cls.vcpe_map: |
| 109 | cmd_gw = "sudo docker exec %s ip route show | grep default | head -1 | awk '{print $3}'" %(vcpe) |
| 110 | vsg_ip = vsg.ip |
| 111 | if vsg_ip is None: |
| 112 | return False |
| 113 | st, output = vsg.run_cmd(cmd_gw, timeout = 30) |
| 114 | if st == False or not output: |
| 115 | return False |
| 116 | gw = output |
| 117 | cmd_wan = "sudo docker exec %s ip addr show eth0 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe) |
| 118 | cmd_lan = "sudo docker exec %s ip addr show eth1 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe) |
| 119 | st, output = vsg.run_cmd(cmd_wan, timeout = 30) |
| 120 | ip_wan = '0.0.0.0/24' |
| 121 | ip_lan = '0.0.0.0/24' |
| 122 | if st and output: |
| 123 | if cls.ip_addr_pattern.match(output): |
| 124 | ip_wan = output |
| 125 | |
| 126 | st, output = vsg.run_cmd(cmd_lan, timeout = 30) |
| 127 | if st and output: |
| 128 | if cls.ip_addr_pattern.match(output): |
| 129 | ip_lan = output |
| 130 | |
| 131 | cls.vcpe_map[vcpe] = { 'vsg': vsg, 'gw': gw, 'wan': ip_wan, 'lan': ip_lan } |
| 132 | |
| 133 | return True |
| 134 | |
| 135 | @classmethod |
| 136 | def restore_vcpe_config(cls, vcpe, gw = True, wan = False, lan = False): |
| 137 | if vcpe in cls.vcpe_map: |
| 138 | vsg = cls.vcpe_map[vcpe]['vsg'] |
| 139 | cmds = [] |
| 140 | if gw is True: |
| 141 | #restore default gw |
| 142 | gw = cls.vcpe_map[vcpe]['gw'] |
| 143 | cmds.append('sudo docker exec {} ip link set eth0 up'.format(vcpe)) |
| 144 | cmds.append('sudo docker exec {} route add default gw {} dev eth0'.format(vcpe, gw)) |
| 145 | if wan is True: |
| 146 | ip_wan = cls.vcpe_map[vcpe]['wan'] |
| 147 | cmds.append('sudo docker exec {} ip addr set {} dev eth0'.format(vcpe, ip_wan)) |
| 148 | if lan is True: |
| 149 | ip_lan = cls.vcpe_map[vcpe]['lan'] |
| 150 | cmds.append('sudo docker exec {} ip addr set {} dev eth1'.format(vcpe, ip_lan)) |
| 151 | ret_status = True |
| 152 | for cmd in cmds: |
| 153 | st, _ = vsg.run_cmd(cmd, timeout = 30) |
| 154 | if st == False: |
| 155 | ret_status = False |
| 156 | return ret_status |
| 157 | return False |
| 158 | |
| 159 | @classmethod |
| 160 | def get_vcpe_gw(cls, vcpe): |
| 161 | if vcpe in cls.vcpe_map: |
| 162 | return cls.vcpe_map[vcpe]['gw'] |
| 163 | return None |
| 164 | |
| 165 | @classmethod |
| 166 | def get_vcpe_wan(cls, vcpe): |
| 167 | if vcpe in cls.vcpe_map: |
| 168 | return cls.vcpe_map[vcpe]['wan'] |
| 169 | return None |
| 170 | |
| 171 | @classmethod |
| 172 | def get_vcpe_lan(cls, vcpe): |
| 173 | if vcpe in cls.vcpe_map: |
| 174 | return cls.vcpe_map[vcpe]['lan'] |
| 175 | return None |
| 176 | |
| 177 | @classmethod |
| 178 | def vcpe_wan_up(cls, vcpe): |
| 179 | return cls.restore_vcpe_config(vcpe) |
| 180 | |
| 181 | @classmethod |
| 182 | def vcpe_lan_up(cls, vcpe, vsg = None): |
| 183 | if vsg is None: |
| 184 | vsg = cls.get_vcpe_vsg(vcpe) |
| 185 | if vsg is None: |
| 186 | return False |
| 187 | cmd = 'sudo docker exec {} ip link set eth1 up'.format(vcpe) |
| 188 | st, _ = vsg.run_cmd(cmd, timeout = 30) |
| 189 | return st |
| 190 | |
| 191 | #we cannot access compute node if the vcpe port gets dhcp as default would be through fabric |
| 192 | @classmethod |
| 193 | def vcpe_port_down(cls, vcpe, port, vsg = None): |
| 194 | if vsg is None: |
| 195 | vsg = cls.get_vcpe_vsg(vcpe) |
| 196 | if vsg is None: |
| 197 | return False |
| 198 | if not cls.save_vcpe_config(vsg, vcpe): |
| 199 | return False |
| 200 | cmd = 'sudo docker exec {} ip link set {} down'.format(vcpe, port) |
| 201 | st, _ = vsg.run_cmd(cmd, timeout = 30) |
| 202 | if st is False: |
| 203 | cls.restore_vcpe_config(vcpe) |
| 204 | return False |
| 205 | return st |
| 206 | |
| 207 | @classmethod |
| 208 | def vcpe_wan_down(cls, vcpe, vsg = None): |
| 209 | return cls.vcpe_port_down(vcpe, 'eth0', vsg = vsg) |
| 210 | |
| 211 | @classmethod |
| 212 | def vcpe_lan_down(cls, vcpe, vsg = None): |
| 213 | return cls.vcpe_port_down(vcpe, 'eth1', vsg = vsg) |
| 214 | |
| 215 | @classmethod |
| 216 | def save_interface_config(cls, intf): |
| 217 | if intf not in cls.interface_map: |
| 218 | ip = get_ip(intf) |
| 219 | if ip is None: |
| 220 | ip = '0.0.0.0' |
| 221 | default_gw, default_gw_device = get_default_gw() |
| 222 | if default_gw_device != intf: |
| 223 | default_gw = '0.0.0.0' |
| 224 | cls.interface_map[intf] = { 'ip' : ip, 'gw': default_gw } |
| 225 | #bounce the interface to remove default gw |
| 226 | cmds = ['ifconfig {} 0 down'.format(intf), |
| 227 | 'ifconfig {} 0 up'.format(intf) |
| 228 | ] |
| 229 | for cmd in cmds: |
| 230 | os.system(cmd) |
| 231 | |
| 232 | #open up access to compute node |
| 233 | @classmethod |
| 234 | def restore_interface_config(cls, intf, vcpe = None): |
| 235 | if intf in cls.interface_map: |
| 236 | ip = cls.interface_map[intf]['ip'] |
| 237 | gw = cls.interface_map[intf]['gw'] |
| 238 | del cls.interface_map[intf] |
| 239 | cmds = [] |
| 240 | if vcpe is not None: |
| 241 | shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf') |
| 242 | #bounce the vcpes to clear default gw |
| 243 | cmds.append('ifconfig {} 0 down'.format(vcpe)) |
| 244 | cmds.append('ifconfig {} 0 up'.format(vcpe)) |
| 245 | cmds.append('ifconfig {} {} up'.format(intf, ip)) |
| 246 | if gw and gw != '0.0.0.0': |
| 247 | cmds.append('route add default gw {} dev {}'.format(gw, intf)) |
| 248 | for cmd in cmds: |
| 249 | os.system(cmd) |
| 250 | |
| 251 | @classmethod |
| 252 | def vcpe_get_dhcp(cls, vcpe, mgmt = 'eth0'): |
| 253 | '''Get DHCP from vcpe dhcp interface.''' |
| 254 | '''We have to also save the management interface config for restoration''' |
| 255 | cls.save_interface_config(mgmt) |
| 256 | getstatusoutput('pkill -9 dhclient') |
| 257 | st, output = getstatusoutput('dhclient -q {}'.format(vcpe)) |
| 258 | getstatusoutput('pkill -9 dhclient') |
| 259 | vcpe_ip = get_ip(vcpe) |
| 260 | if vcpe_ip is None: |
| 261 | cls.restore_interface_config(mgmt) |
| 262 | return None |
| 263 | if output: |
| 264 | #workaround for docker container apparmor that prevents moving dhclient resolv.conf |
| 265 | start = output.find('/etc/resolv.conf') |
| 266 | if start >= 0: |
| 267 | end = output.find("'", start) |
| 268 | dns_file = output[start:end] |
| 269 | if os.access(dns_file, os.F_OK): |
| 270 | shutil.copy(dns_file, '/etc/resolv.conf') |
| 271 | |
| 272 | default_gw, default_gw_device = get_default_gw() |
| 273 | if default_gw and default_gw_device == vcpe: |
| 274 | return vcpe_ip |
| 275 | cls.restore_interface_config(mgmt, vcpe = vcpe) |
| 276 | return None |
| 277 | |
| 278 | class VSGWrapper(object): |
| 279 | |
| 280 | def __init__(self, vsg): |
| 281 | self.vsg = vsg |
| 282 | self.name = self.vsg.name |
| 283 | self.compute_node = self.get_compute_node() |
| 284 | self.ip = self.get_ip() |
| 285 | |
| 286 | def get_compute_node(self): |
| 287 | return self.vsg._info['OS-EXT-SRV-ATTR:hypervisor_hostname'] |
| 288 | |
| 289 | def get_ip(self): |
| 290 | if 'management' in self.vsg.networks: |
| 291 | ips = self.vsg.networks['management'] |
| 292 | if len(ips) > 0: |
| 293 | return ips[0] |
| 294 | return None |
| 295 | |
| 296 | def run_cmd_compute(self, cmd, timeout = 5): |
| 297 | ssh_agent = SSHTestAgent(self.compute_node) |
| 298 | st, output = ssh_agent.run_cmd(cmd, timeout = timeout) |
| 299 | if st == True and output: |
| 300 | output = output.strip() |
| 301 | else: |
| 302 | output = None |
| 303 | |
| 304 | return st, output |
| 305 | |
| 306 | def run_cmd(self, cmd, timeout = 5, mgmt = 'eth0'): |
| 307 | last_gw = VSGAccess.open_mgmt(mgmt) |
| 308 | ssh_agent = SSHTestAgent(self.compute_node) |
| 309 | ssh_cmd = 'ssh {} {}'.format(self.ip, cmd) |
| 310 | st, output = ssh_agent.run_cmd(ssh_cmd, timeout = timeout) |
| 311 | if st == True and output: |
| 312 | output = output.strip() |
| 313 | else: |
| 314 | output = None |
| 315 | VSGAccess.close_mgmt(last_gw, mgmt) |
| 316 | return st, output |
| 317 | |
| 318 | def get_health(self): |
| 319 | if self.ip is None: |
| 320 | return False |
| 321 | cmd = 'ping -c 1 {}'.format(self.ip) |
| 322 | st, _ = self.run_cmd_compute(cmd) |
| 323 | return st |
| 324 | |
| 325 | def check_access(self): |
| 326 | if self.ip is None: |
| 327 | return False |
| 328 | ssh_agent = SSHTestAgent(self.compute_node) |
| 329 | st, _ = ssh_agent.run_cmd('ls', timeout=10) |
| 330 | if st == False: |
| 331 | return st |
| 332 | st, _ = ssh_agent.run_cmd('ssh {} ls'.format(self.ip), timeout=30) |
| 333 | return st |