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