blob: e179d29054710edb26f5b672c29b9b80cfb16c06 [file] [log] [blame]
Matteo Scandolo48d3d2d2017-08-08 13:05:27 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
A R Karthickd0fdf3b2017-03-21 16:54:22 -070017import os
18import shutil
19import re
20from novaclient import client as nova_client
21from SSHTestAgent import SSHTestAgent
22from CordTestUtils import *
A R Karthick933f5b52017-03-27 15:27:16 -070023from CordTestUtils import log_test as log
24
25log.setLevel('INFO')
A R Karthickd0fdf3b2017-03-21 16:54:22 -070026
27class VSGAccess(object):
28
29 vcpe_map = {}
30 interface_map = {}
31 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}$')
32
33 @classmethod
34 def setUp(cls):
35 try:
36 shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.orig')
37 except:
38 pass
39
40 @classmethod
41 def tearDown(cls):
42 try:
43 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
44 except:
45 pass
46
Chetan Gaonker79b35532017-03-31 18:58:36 +000047 '''
48 @method: get_nova_credentials_v2
49 @Description: Get nova credentials
50 @params:
51 returns credential from env
52 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070053 @classmethod
54 def get_nova_credentials_v2(cls):
55 credential = {}
56 credential['username'] = os.environ['OS_USERNAME']
57 credential['api_key'] = os.environ['OS_PASSWORD']
58 credential['auth_url'] = os.environ['OS_AUTH_URL']
59 credential['project_id'] = os.environ['OS_TENANT_NAME']
60 return credential
61
Chetan Gaonker79b35532017-03-31 18:58:36 +000062 '''
63 @method: get_compute_nodes
64 @Description: Get the list of compute nodes
65 @params:
66 returns node list
67 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070068 @classmethod
69 def get_compute_nodes(cls):
70 credentials = cls.get_nova_credentials_v2()
71 nvclient = nova_client.Client('2', **credentials)
72 return nvclient.hypervisors.list()
73
Chetan Gaonker79b35532017-03-31 18:58:36 +000074 '''
75 @method: get_vsgs
76 @Description: Get list of vsg's running in compute node
77 @params: status of vsg
78 returns vsg wrappers
79 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070080 @classmethod
81 def get_vsgs(cls, active = True):
82 credentials = cls.get_nova_credentials_v2()
83 nvclient = nova_client.Client('2', **credentials)
84 vsgs = nvclient.servers.list(search_opts = {'all_tenants': 1})
85 if active is True:
86 vsgs = filter(lambda vsg: vsg.status == 'ACTIVE', vsgs)
87 vsg_wrappers = []
88 for vsg in vsgs:
89 vsg_wrappers.append(VSGWrapper(vsg))
90 return vsg_wrappers
91
Chetan Gaonker79b35532017-03-31 18:58:36 +000092 '''
93 @method: open_mgmt
94 @Description: Bringing up Interface for access to management
95 @params: intf = "Interface to open"
96 returns Gateway
97 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070098 @classmethod
99 def open_mgmt(cls, intf = 'eth0'):
100 if intf in cls.interface_map:
101 gw = cls.interface_map[intf]['gw']
102 ip = cls.interface_map[intf]['ip']
103 if gw != '0.0.0.0':
104 current_gw, _ = get_default_gw()
105 cmds = [ 'route del default gw {}'.format(current_gw),
106 'ifconfig {} {} up'.format(intf, ip),
107 'route add default gw {}'.format(gw) ]
108 for cmd in cmds:
109 os.system(cmd)
110 shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.lastdhcp')
111 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
112 return current_gw
113 return None
114
Chetan Gaonker79b35532017-03-31 18:58:36 +0000115 '''
116 @method: close_mgmt
117 @Description: Bringing up gateway deleting default
118 @params: intf = "Interface to open"
119 dict2 = retrieved data from GET method
120 returns: NA
121 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700122 @classmethod
123 def close_mgmt(cls, restore_gw, intf = 'eth0'):
124 if restore_gw:
125 cmds = [ 'route del default gw 0.0.0.0',
126 'route add default gw {}'.format(restore_gw),
127 'cp /etc/resolv.conf.lastdhcp /etc/resolv.conf',
128 'rm -f /etc/resolv.conf.lastdhcp'
129 ]
130 for cmd in cmds:
131 os.system(cmd)
132
Chetan Gaonker79b35532017-03-31 18:58:36 +0000133 '''
134 @method: health_check
135 @Description: Check if vsgs are reachable
136 @params:
137 returns True
138 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700139 @classmethod
140 def health_check(cls):
141 '''Returns 0 if all active vsgs are reachable through the compute node'''
142 vsgs = cls.get_vsgs()
143 vsg_status = []
144 for vsg in vsgs:
145 vsg_status.append(vsg.get_health())
146 unreachable = filter(lambda st: st == False, vsg_status)
147 return len(unreachable) == 0
148
Chetan Gaonker79b35532017-03-31 18:58:36 +0000149 '''
150 @method: get_vcpe_vsg
151 @Description: Getting vsg vm instance info from given vcpe
152 @params: vcpe = "vcpe name"
153 returns vsg
154 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700155 @classmethod
156 def get_vcpe_vsg(cls, vcpe):
157 '''Find the vsg hosting the vcpe service'''
158 if vcpe in cls.vcpe_map:
159 return cls.vcpe_map[vcpe]['vsg']
160 vsgs = cls.get_vsgs()
161 for vsg in vsgs:
162 cmd = 'sudo docker exec {} ls 2>/dev/null'.format(vcpe)
163 st, _ = vsg.run_cmd(cmd, timeout = 30)
164 if st == True:
165 return vsg
166 return None
167
Chetan Gaonker79b35532017-03-31 18:58:36 +0000168 '''
169 @method: save_vcpe_config
170 @Description: Saving vcpe config with lan & wan side info
171 @params: vsg
172 vcpe
173 returns True
174 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700175 @classmethod
176 def save_vcpe_config(cls, vsg, vcpe):
177 if vcpe not in cls.vcpe_map:
178 cmd_gw = "sudo docker exec %s ip route show | grep default | head -1 | awk '{print $3}'" %(vcpe)
179 vsg_ip = vsg.ip
180 if vsg_ip is None:
181 return False
182 st, output = vsg.run_cmd(cmd_gw, timeout = 30)
183 if st == False or not output:
184 return False
185 gw = output
186 cmd_wan = "sudo docker exec %s ip addr show eth0 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
187 cmd_lan = "sudo docker exec %s ip addr show eth1 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
188 st, output = vsg.run_cmd(cmd_wan, timeout = 30)
189 ip_wan = '0.0.0.0/24'
190 ip_lan = '0.0.0.0/24'
191 if st and output:
192 if cls.ip_addr_pattern.match(output):
193 ip_wan = output
194
195 st, output = vsg.run_cmd(cmd_lan, timeout = 30)
196 if st and output:
197 if cls.ip_addr_pattern.match(output):
198 ip_lan = output
199
200 cls.vcpe_map[vcpe] = { 'vsg': vsg, 'gw': gw, 'wan': ip_wan, 'lan': ip_lan }
201
202 return True
203
Chetan Gaonker79b35532017-03-31 18:58:36 +0000204 '''
205 @method: restore_vcpe_config
206 @Description: Restoring saved config for lan & wan
207 @params: vcpe
208 gw
209 wan
210 lan
211 returns True/False
212 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700213 @classmethod
214 def restore_vcpe_config(cls, vcpe, gw = True, wan = False, lan = False):
215 if vcpe in cls.vcpe_map:
216 vsg = cls.vcpe_map[vcpe]['vsg']
217 cmds = []
218 if gw is True:
219 #restore default gw
220 gw = cls.vcpe_map[vcpe]['gw']
221 cmds.append('sudo docker exec {} ip link set eth0 up'.format(vcpe))
222 cmds.append('sudo docker exec {} route add default gw {} dev eth0'.format(vcpe, gw))
223 if wan is True:
224 ip_wan = cls.vcpe_map[vcpe]['wan']
225 cmds.append('sudo docker exec {} ip addr set {} dev eth0'.format(vcpe, ip_wan))
226 if lan is True:
227 ip_lan = cls.vcpe_map[vcpe]['lan']
228 cmds.append('sudo docker exec {} ip addr set {} dev eth1'.format(vcpe, ip_lan))
229 ret_status = True
230 for cmd in cmds:
231 st, _ = vsg.run_cmd(cmd, timeout = 30)
232 if st == False:
233 ret_status = False
234 return ret_status
235 return False
236
Chetan Gaonker79b35532017-03-31 18:58:36 +0000237 '''
238 @method: get_vcpe_gw
239 @Description: Get gw of vcpe from created map
240 @params: vcpe
241 returns gw
242 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700243 @classmethod
244 def get_vcpe_gw(cls, vcpe):
245 if vcpe in cls.vcpe_map:
246 return cls.vcpe_map[vcpe]['gw']
247 return None
248
Chetan Gaonker79b35532017-03-31 18:58:36 +0000249 '''
250 @method: get_vcpe_wan
251 @Description:
252 @params:
253 return wan side of vcpe
254 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700255 @classmethod
256 def get_vcpe_wan(cls, vcpe):
257 if vcpe in cls.vcpe_map:
258 return cls.vcpe_map[vcpe]['wan']
259 return None
260
Chetan Gaonker79b35532017-03-31 18:58:36 +0000261 '''
262 @method: get_vcpe_lan
263 @Description:
264 @params:
265 returns True if contents of dict1 exists in dict2
266 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700267 @classmethod
268 def get_vcpe_lan(cls, vcpe):
269 if vcpe in cls.vcpe_map:
270 return cls.vcpe_map[vcpe]['lan']
271 return None
272
Chetan Gaonker79b35532017-03-31 18:58:36 +0000273 '''
274 @method: vcpe_wan_up
275 @Description:
276 @params:
277 returns status
278 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700279 @classmethod
280 def vcpe_wan_up(cls, vcpe):
281 return cls.restore_vcpe_config(vcpe)
282
Chetan Gaonker79b35532017-03-31 18:58:36 +0000283 '''
284 @method: vcpe_lan_up
285 @Description:
286 @params:
287 returns status
288 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700289 @classmethod
290 def vcpe_lan_up(cls, vcpe, vsg = None):
291 if vsg is None:
292 vsg = cls.get_vcpe_vsg(vcpe)
293 if vsg is None:
294 return False
295 cmd = 'sudo docker exec {} ip link set eth1 up'.format(vcpe)
296 st, _ = vsg.run_cmd(cmd, timeout = 30)
297 return st
298
Chetan Gaonker79b35532017-03-31 18:58:36 +0000299 '''
300 @method: vcpe_port_down
301 @Description:
302 @params:
303 returns status
304 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700305 #we cannot access compute node if the vcpe port gets dhcp as default would be through fabric
306 @classmethod
307 def vcpe_port_down(cls, vcpe, port, vsg = None):
308 if vsg is None:
309 vsg = cls.get_vcpe_vsg(vcpe)
310 if vsg is None:
311 return False
312 if not cls.save_vcpe_config(vsg, vcpe):
313 return False
314 cmd = 'sudo docker exec {} ip link set {} down'.format(vcpe, port)
315 st, _ = vsg.run_cmd(cmd, timeout = 30)
316 if st is False:
317 cls.restore_vcpe_config(vcpe)
318 return False
319 return st
320
Chetan Gaonker79b35532017-03-31 18:58:36 +0000321 '''
322 @method: vcpe_wan_down
323 @Description:
324 @params:
325 returns status
326 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700327 @classmethod
328 def vcpe_wan_down(cls, vcpe, vsg = None):
329 return cls.vcpe_port_down(vcpe, 'eth0', vsg = vsg)
330
Chetan Gaonker79b35532017-03-31 18:58:36 +0000331 '''
332 @method: vcpe_lan_down
333 @Description:
334 @params:
335 returns status
336 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700337 @classmethod
338 def vcpe_lan_down(cls, vcpe, vsg = None):
339 return cls.vcpe_port_down(vcpe, 'eth1', vsg = vsg)
340
Chetan Gaonker79b35532017-03-31 18:58:36 +0000341 '''
342 @method: save_interface_config
343 @Description:
344 @params:
345 returns NA
346 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700347 @classmethod
348 def save_interface_config(cls, intf):
349 if intf not in cls.interface_map:
350 ip = get_ip(intf)
351 if ip is None:
352 ip = '0.0.0.0'
353 default_gw, default_gw_device = get_default_gw()
354 if default_gw_device != intf:
355 default_gw = '0.0.0.0'
356 cls.interface_map[intf] = { 'ip' : ip, 'gw': default_gw }
357 #bounce the interface to remove default gw
358 cmds = ['ifconfig {} 0 down'.format(intf),
359 'ifconfig {} 0 up'.format(intf)
360 ]
361 for cmd in cmds:
362 os.system(cmd)
363
Chetan Gaonker79b35532017-03-31 18:58:36 +0000364 '''
365 @method: restore_interface_config
366 @Description:
367 @params:
368 returns NA
369 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700370 #open up access to compute node
371 @classmethod
372 def restore_interface_config(cls, intf, vcpe = None):
373 if intf in cls.interface_map:
374 ip = cls.interface_map[intf]['ip']
375 gw = cls.interface_map[intf]['gw']
376 del cls.interface_map[intf]
377 cmds = []
378 if vcpe is not None:
379 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
380 #bounce the vcpes to clear default gw
381 cmds.append('ifconfig {} 0 down'.format(vcpe))
382 cmds.append('ifconfig {} 0 up'.format(vcpe))
383 cmds.append('ifconfig {} {} up'.format(intf, ip))
384 if gw and gw != '0.0.0.0':
385 cmds.append('route add default gw {} dev {}'.format(gw, intf))
386 for cmd in cmds:
387 os.system(cmd)
388
Chetan Gaonker79b35532017-03-31 18:58:36 +0000389 '''
390 @method: vcpe_get_dhcp
391 @Description: Get DHCP from vcpe dhcp interface.
392 @params:
393 returns vcpe ip
394 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700395 @classmethod
396 def vcpe_get_dhcp(cls, vcpe, mgmt = 'eth0'):
397 '''Get DHCP from vcpe dhcp interface.'''
398 '''We have to also save the management interface config for restoration'''
399 cls.save_interface_config(mgmt)
400 getstatusoutput('pkill -9 dhclient')
401 st, output = getstatusoutput('dhclient -q {}'.format(vcpe))
402 getstatusoutput('pkill -9 dhclient')
403 vcpe_ip = get_ip(vcpe)
404 if vcpe_ip is None:
405 cls.restore_interface_config(mgmt)
406 return None
407 if output:
408 #workaround for docker container apparmor that prevents moving dhclient resolv.conf
409 start = output.find('/etc/resolv.conf')
410 if start >= 0:
411 end = output.find("'", start)
412 dns_file = output[start:end]
413 if os.access(dns_file, os.F_OK):
414 shutil.copy(dns_file, '/etc/resolv.conf')
415
416 default_gw, default_gw_device = get_default_gw()
417 if default_gw and default_gw_device == vcpe:
418 return vcpe_ip
419 cls.restore_interface_config(mgmt, vcpe = vcpe)
420 return None
421
422class VSGWrapper(object):
423
424 def __init__(self, vsg):
425 self.vsg = vsg
426 self.name = self.vsg.name
427 self.compute_node = self.get_compute_node()
428 self.ip = self.get_ip()
429
Chetan Gaonker79b35532017-03-31 18:58:36 +0000430 '''
431 @method: get_compute_node
432 @Description:
433 @params:
434 returns compute node name
435 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700436 def get_compute_node(self):
437 return self.vsg._info['OS-EXT-SRV-ATTR:hypervisor_hostname']
438
Chetan Gaonker79b35532017-03-31 18:58:36 +0000439 '''
440 @method: get_ip
441 @Description:
442 @params:
443 returns ip of network
444 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700445 def get_ip(self):
446 if 'management' in self.vsg.networks:
447 ips = self.vsg.networks['management']
448 if len(ips) > 0:
449 return ips[0]
450 return None
451
Chetan Gaonker79b35532017-03-31 18:58:36 +0000452 '''
453 @method: run_cmd_compute
454 @Description:
455 @params:
456 returns Status & output
457 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700458 def run_cmd_compute(self, cmd, timeout = 5):
459 ssh_agent = SSHTestAgent(self.compute_node)
460 st, output = ssh_agent.run_cmd(cmd, timeout = timeout)
461 if st == True and output:
462 output = output.strip()
463 else:
464 output = None
465
466 return st, output
467
Chetan Gaonker79b35532017-03-31 18:58:36 +0000468 '''
469 @method: run_cmd
470 @Description:
471 @params:
472 returns status & output
473 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700474 def run_cmd(self, cmd, timeout = 5, mgmt = 'eth0'):
475 last_gw = VSGAccess.open_mgmt(mgmt)
476 ssh_agent = SSHTestAgent(self.compute_node)
477 ssh_cmd = 'ssh {} {}'.format(self.ip, cmd)
478 st, output = ssh_agent.run_cmd(ssh_cmd, timeout = timeout)
479 if st == True and output:
480 output = output.strip()
481 else:
482 output = None
483 VSGAccess.close_mgmt(last_gw, mgmt)
484 return st, output
485
Chetan Gaonker79b35532017-03-31 18:58:36 +0000486 '''
487 @method: get_health
488 @Description:
489 @params:
490 returns Status
491 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700492 def get_health(self):
493 if self.ip is None:
A R Karthick850795b2017-03-27 14:07:03 -0700494 return True
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700495 cmd = 'ping -c 1 {}'.format(self.ip)
A R Karthick933f5b52017-03-27 15:27:16 -0700496 log.info('Pinging VSG %s at IP %s' %(self.name, self.ip))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700497 st, _ = self.run_cmd_compute(cmd)
A R Karthick36968e22017-11-07 10:45:03 -0800498 log.info('VSG %s at IP %s is %s through compute node %s' %(self.name, self.ip, 'reachable' if st == True else 'unreachable', self.compute_node))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700499 return st
500
Chetan Gaonker79b35532017-03-31 18:58:36 +0000501 '''
502 @method: check_access
503 @Description: validates access
504 @params:
505 returns Status
506 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700507 def check_access(self):
508 if self.ip is None:
A R Karthick933f5b52017-03-27 15:27:16 -0700509 return True
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700510 ssh_agent = SSHTestAgent(self.compute_node)
511 st, _ = ssh_agent.run_cmd('ls', timeout=10)
512 if st == False:
A R Karthick933f5b52017-03-27 15:27:16 -0700513 log.error('Compute node at %s is not accessible' %(self.compute_node))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700514 return st
A R Karthick933f5b52017-03-27 15:27:16 -0700515 log.info('Checking if VSG at %s is accessible from compute node %s' %(self.ip, self.compute_node))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700516 st, _ = ssh_agent.run_cmd('ssh {} ls'.format(self.ip), timeout=30)
A R Karthick933f5b52017-03-27 15:27:16 -0700517 if st == True:
518 log.info('OK')
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700519 return st