Allow vping to use existing glance image
[functest.git] / testcases / vPing / CI / libraries / vPing_ssh.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2015 All rights reserved
4 # This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # 0.1: This script boots the VM1 and allocates IP address from Nova
11 # Later, the VM2 boots then execute cloud-init to ping VM1.
12 # After successful ping, both the VMs are deleted.
13 # 0.2: measure test duration and publish results under json format
14 #
15 #
16 import argparse
17 import datetime
18 import logging
19 import os
20 import paramiko
21 import pprint
22 import re
23 import sys
24 import time
25 import yaml
26 from scp import SCPClient
27 from novaclient import client as novaclient
28 from neutronclient.v2_0 import client as neutronclient
29 from keystoneclient.v2_0 import client as keystoneclient
30 from glanceclient import client as glanceclient
31
32 pp = pprint.PrettyPrinter(indent=4)
33
34 parser = argparse.ArgumentParser()
35 image_exists = False
36
37 parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
38 parser.add_argument("-r", "--report",
39                     help="Create json result file",
40                     action="store_true")
41 parser.add_argument("-n", "--noclean",
42                     help="Don't clean the created resources for this test.",
43                     action="store_true")
44
45 args = parser.parse_args()
46
47 """ logging configuration """
48
49 logger = logging.getLogger('vPing_ssh')
50 logger.setLevel(logging.DEBUG)
51
52 ch = logging.StreamHandler()
53
54 if args.debug:
55     ch.setLevel(logging.DEBUG)
56 else:
57     ch.setLevel(logging.INFO)
58
59 formatter = logging.Formatter('%(asctime)s - %(name)s'
60                               '- %(levelname)s - %(message)s')
61
62 ch.setFormatter(formatter)
63 logger.addHandler(ch)
64 paramiko.util.log_to_file("/var/log/paramiko.log")
65
66 REPO_PATH = os.environ['repos_dir']+'/functest/'
67 if not os.path.exists(REPO_PATH):
68     logger.error("Functest repository directory not found '%s'" % REPO_PATH)
69     exit(-1)
70 sys.path.append(REPO_PATH + "testcases/")
71 import functest_utils
72
73 with open("/home/opnfv/functest/conf/config_functest.yaml") as f:
74     functest_yaml = yaml.safe_load(f)
75 f.close()
76
77 HOME = os.environ['HOME'] + "/"
78 # vPing parameters
79 VM_BOOT_TIMEOUT = 180
80 VM_DELETE_TIMEOUT = 100
81 PING_TIMEOUT = functest_yaml.get("vping").get("ping_timeout")
82 TEST_DB = functest_yaml.get("results").get("test_db_url")
83 NAME_VM_1 = functest_yaml.get("vping").get("vm_name_1")
84 NAME_VM_2 = functest_yaml.get("vping").get("vm_name_2")
85 GLANCE_IMAGE_NAME = functest_yaml.get("vping").get("image_name")
86 GLANCE_IMAGE_FILENAME = functest_yaml.get("general"). \
87     get("openstack").get("image_file_name")
88 GLANCE_IMAGE_FORMAT = functest_yaml.get("general"). \
89     get("openstack").get("image_disk_format")
90 GLANCE_IMAGE_PATH = functest_yaml.get("general"). \
91     get("directories").get("dir_functest_data") + "/" + GLANCE_IMAGE_FILENAME
92
93
94 FLAVOR = functest_yaml.get("vping").get("vm_flavor")
95
96 # NEUTRON Private Network parameters
97
98 NEUTRON_PRIVATE_NET_NAME = functest_yaml.get("vping"). \
99     get("vping_private_net_name")
100 NEUTRON_PRIVATE_SUBNET_NAME = functest_yaml.get("vping"). \
101     get("vping_private_subnet_name")
102 NEUTRON_PRIVATE_SUBNET_CIDR = functest_yaml.get("vping"). \
103     get("vping_private_subnet_cidr")
104 NEUTRON_ROUTER_NAME = functest_yaml.get("vping"). \
105     get("vping_router_name")
106
107 SECGROUP_NAME = functest_yaml.get("vping"). \
108     get("vping_sg_name")
109 SECGROUP_DESCR = functest_yaml.get("vping"). \
110     get("vping_sg_descr")
111
112
113 def pMsg(value):
114
115     """pretty printing"""
116     pp.pprint(value)
117
118
119 def waitVmActive(nova, vm):
120
121     # sleep and wait for VM status change
122     sleep_time = 3
123     count = VM_BOOT_TIMEOUT / sleep_time
124     while True:
125         status = functest_utils.get_instance_status(nova, vm)
126         logger.debug("Status: %s" % status)
127         if status == "ACTIVE":
128             return True
129         if status == "ERROR" or status == "error":
130             return False
131         if count == 0:
132             logger.debug("Booting a VM timed out...")
133             return False
134         count -= 1
135         time.sleep(sleep_time)
136     return False
137
138
139 def waitVmDeleted(nova, vm):
140
141     # sleep and wait for VM status change
142     sleep_time = 3
143     count = VM_DELETE_TIMEOUT / sleep_time
144     while True:
145         status = functest_utils.get_instance_status(nova, vm)
146         if not status:
147             return True
148         elif count == 0:
149             logger.debug("Timeout")
150             return False
151         else:
152             # return False
153             count -= 1
154         time.sleep(sleep_time)
155     return False
156
157
158 def create_private_neutron_net(neutron):
159
160     # Check if the network already exists
161     network_id = functest_utils.get_network_id(neutron, NEUTRON_PRIVATE_NET_NAME)
162     subnet_id = functest_utils.get_subnet_id(neutron, NEUTRON_PRIVATE_SUBNET_NAME)
163     router_id = functest_utils.get_router_id(neutron, NEUTRON_ROUTER_NAME)
164
165     if network_id != '' and subnet_id != '' and router_id != '':
166         logger.info("Using existing network '%s'..." % NEUTRON_PRIVATE_NET_NAME)
167     else:
168         neutron.format = 'json'
169         logger.info('Creating neutron network %s...' % NEUTRON_PRIVATE_NET_NAME)
170         network_id = functest_utils. \
171             create_neutron_net(neutron, NEUTRON_PRIVATE_NET_NAME)
172
173         if not network_id:
174             return False
175         logger.debug("Network '%s' created successfully" % network_id)
176         logger.debug('Creating Subnet....')
177         subnet_id = functest_utils. \
178             create_neutron_subnet(neutron,
179                                   NEUTRON_PRIVATE_SUBNET_NAME,
180                                   NEUTRON_PRIVATE_SUBNET_CIDR,
181                                   network_id)
182         if not subnet_id:
183             return False
184         logger.debug("Subnet '%s' created successfully" % subnet_id)
185         logger.debug('Creating Router...')
186         router_id = functest_utils. \
187             create_neutron_router(neutron, NEUTRON_ROUTER_NAME)
188
189         if not router_id:
190             return False
191
192         logger.debug("Router '%s' created successfully" % router_id)
193         logger.debug('Adding router to subnet...')
194
195         if not functest_utils.add_interface_router(neutron, router_id, subnet_id):
196             return False
197         logger.debug("Interface added successfully.")
198
199         logger.debug('Adding gateway to router...')
200         if not functest_utils.add_gateway_router(neutron, router_id):
201             return False
202         logger.debug("Gateway added successfully.")
203
204     network_dic = {'net_id': network_id,
205                    'subnet_id': subnet_id,
206                    'router_id': router_id}
207     return network_dic
208
209
210 def create_security_group(neutron_client):
211     sg_id = functest_utils.get_security_group_id(neutron_client, SECGROUP_NAME)
212     if sg_id != '':
213         logger.info("Using existing security group '%s'..." % SECGROUP_NAME)
214     else:
215         logger.info("Creating security group  '%s'..." % SECGROUP_NAME)
216         SECGROUP = functest_utils.create_security_group(neutron_client,
217                                                         SECGROUP_NAME,
218                                                         SECGROUP_DESCR)
219         if not SECGROUP:
220             logger.error("Failed to create the security group...")
221             return False
222
223         sg_id = SECGROUP['id']
224
225         logger.debug("Security group '%s' with ID=%s created successfully." %\
226                      (SECGROUP['name'], sg_id))
227
228         logger.debug("Adding ICMP rules in security group '%s'..." % SECGROUP_NAME)
229         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
230                                                    'ingress', 'icmp'):
231             logger.error("Failed to create the security group rule...")
232             return False
233
234         logger.debug("Adding SSH rules in security group '%s'..." % SECGROUP_NAME)
235         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
236                                                    'ingress', 'tcp', '22', '22'):
237             logger.error("Failed to create the security group rule...")
238             return False
239
240         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
241                                                    'egress', 'tcp', '22', '22'):
242             logger.error("Failed to create the security group rule...")
243             return False
244     return sg_id
245
246
247 def cleanup(nova, neutron, image_id, network_dic, sg_id, floatingip):
248     if args.noclean:
249         logger.debug("The OpenStack resources are not deleted.")
250         return True
251
252     # delete both VMs
253     logger.info("Cleaning up...")
254     if not image_exists:
255         logger.debug("Deleting image...")
256         if not functest_utils.delete_glance_image(nova, image_id):
257             logger.error("Error deleting the glance image")
258
259     vm1 = functest_utils.get_instance_by_name(nova, NAME_VM_1)
260     if vm1:
261         logger.debug("Deleting '%s'..." % NAME_VM_1)
262         nova.servers.delete(vm1)
263         # wait until VMs are deleted
264         if not waitVmDeleted(nova, vm1):
265             logger.error(
266                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
267                     NAME_VM_1, functest_utils.get_instance_status(nova, vm1)))
268         else:
269             logger.debug("Instance %s terminated." % NAME_VM_1)
270
271     vm2 = functest_utils.get_instance_by_name(nova, NAME_VM_2)
272
273     if vm2:
274         logger.debug("Deleting '%s'..." % NAME_VM_2)
275         vm2 = nova.servers.find(name=NAME_VM_2)
276         nova.servers.delete(vm2)
277
278         if not waitVmDeleted(nova, vm2):
279             logger.error(
280                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
281                     NAME_VM_2, functest_utils.get_instance_status(nova, vm2)))
282         else:
283             logger.debug("Instance %s terminated." % NAME_VM_2)
284
285     # delete created network
286     logger.debug("Deleting network '%s'..." % NEUTRON_PRIVATE_NET_NAME)
287     net_id = network_dic["net_id"]
288     subnet_id = network_dic["subnet_id"]
289     router_id = network_dic["router_id"]
290
291     if not functest_utils.remove_interface_router(neutron, router_id,
292                                                   subnet_id):
293         logger.error("Unable to remove subnet '%s' from router '%s'" % (
294             subnet_id, router_id))
295         return False
296
297     logger.debug("Interface removed successfully")
298     if not functest_utils.delete_neutron_router(neutron, router_id):
299         logger.error("Unable to delete router '%s'" % router_id)
300         return False
301
302     logger.debug("Router deleted successfully")
303
304     if not functest_utils.delete_neutron_subnet(neutron, subnet_id):
305         logger.error("Unable to delete subnet '%s'" % subnet_id)
306         return False
307
308     logger.debug(
309         "Subnet '%s' deleted successfully" % NEUTRON_PRIVATE_SUBNET_NAME)
310
311     if not functest_utils.delete_neutron_net(neutron, net_id):
312         logger.error("Unable to delete network '%s'" % net_id)
313         return False
314
315     logger.debug(
316         "Network '%s' deleted successfully" % NEUTRON_PRIVATE_NET_NAME)
317
318     if not functest_utils.delete_security_group(neutron, sg_id):
319         logger.error("Unable to delete security group '%s'" % sg_id)
320         return False
321     logger.debug(
322         "Security group '%s' deleted successfully" % sg_id)
323
324     logger.debug("Releasing floating ip '%s'..." % floatingip['fip_addr'])
325     if not functest_utils.delete_floating_ip(nova, floatingip['fip_id']):
326         logger.error("Unable to delete floatingip '%s'" % floatingip['fip_addr'])
327         return False
328     logger.debug(
329         "Floating IP '%s' deleted successfully" % floatingip['fip_addr'])
330     return True
331
332
333 def push_results(start_time_ts, duration, test_status):
334     try:
335         logger.debug("Pushing result into DB...")
336         scenario = functest_utils.get_scenario(logger)
337         version = scenario
338         criteria = "failed"
339         if test_status == "OK":
340             criteria = "passed"
341         pod_name = functest_utils.get_pod_name(logger)
342         build_tag = functest_utils.get_build_tag(logger)
343         functest_utils.push_results_to_db(TEST_DB,
344                                           "functest",
345                                           "vPing",
346                                           logger, pod_name, version, scenario,
347                                           criteria, build_tag,
348                                           payload={'timestart': start_time_ts,
349                                                    'duration': duration,
350                                                    'status': test_status})
351     except:
352         logger.error("Error pushing results into Database '%s'" % sys.exc_info()[0])
353
354
355 def main():
356
357     creds_nova = functest_utils.get_credentials("nova")
358     nova_client = novaclient.Client('2', **creds_nova)
359     creds_neutron = functest_utils.get_credentials("neutron")
360     neutron_client = neutronclient.Client(**creds_neutron)
361     creds_keystone = functest_utils.get_credentials("keystone")
362     keystone_client = keystoneclient.Client(**creds_keystone)
363     glance_endpoint = keystone_client.service_catalog.url_for(service_type='image',
364                                                               endpoint_type='publicURL')
365     glance_client = glanceclient.Client(1, glance_endpoint,
366                                         token=keystone_client.auth_token)
367     EXIT_CODE = -1
368
369     image_id = None
370     flavor = None
371
372     # Check if the given image exists
373     image_id = functest_utils.get_image_id(glance_client, GLANCE_IMAGE_NAME)
374     if image_id != '':
375         logger.info("Using existing image '%s'..." % GLANCE_IMAGE_NAME)
376         global image_exists
377         image_exists = True
378     else:
379         logger.info("Creating image '%s' from '%s'..." % (GLANCE_IMAGE_NAME,
380                                                           GLANCE_IMAGE_PATH))
381         image_id = functest_utils.create_glance_image(glance_client,
382                                                       GLANCE_IMAGE_NAME,
383                                                       GLANCE_IMAGE_PATH)
384         if not image_id:
385             logger.error("Failed to create a Glance image...")
386             return(EXIT_CODE)
387         logger.debug("Image '%s' with ID=%s created successfully." %\
388                      (GLANCE_IMAGE_NAME, image_id))
389
390     network_dic = create_private_neutron_net(neutron_client)
391     if not network_dic:
392         logger.error(
393             "There has been a problem when creating the neutron network")
394         return(EXIT_CODE)
395
396     network_id = network_dic["net_id"]
397
398     sg_id = create_security_group(neutron_client)
399
400     # Check if the given flavor exists
401     try:
402         flavor = nova_client.flavors.find(name=FLAVOR)
403         logger.info("Using existing Flavor '%s'..." % FLAVOR)
404     except:
405         logger.error("Flavor '%s' not found." % FLAVOR)
406         logger.info("Available flavors are: ")
407         pMsg(nova_client.flavor.list())
408         return(EXIT_CODE)
409
410     # Deleting instances if they exist
411     servers = nova_client.servers.list()
412     for server in servers:
413         if server.name == NAME_VM_1 or server.name == NAME_VM_2:
414             logger.info("Instance %s found. Deleting..." % server.name)
415             server.delete()
416
417     # boot VM 1
418     start_time_ts = time.time()
419     end_time_ts = start_time_ts
420     logger.info("vPing Start Time:'%s'" % (
421         datetime.datetime.fromtimestamp(start_time_ts).strftime(
422             '%Y-%m-%d %H:%M:%S')))
423
424     logger.info("Creating instance '%s'..." % NAME_VM_1)
425     logger.debug(
426         "Configuration:\n name=%s \n flavor=%s \n image=%s \n "
427         "network=%s \n" % (NAME_VM_1, flavor, image_id, network_id))
428     vm1 = nova_client.servers.create(
429         name=NAME_VM_1,
430         flavor=flavor,
431         image=image_id,
432         nics=[{"net-id": network_id}]
433     )
434
435     # wait until VM status is active
436     if not waitVmActive(nova_client, vm1):
437         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
438             NAME_VM_1, functest_utils.get_instance_status(nova_client, vm1)))
439         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatingip)
440         return (EXIT_CODE)
441     else:
442         logger.info("Instance '%s' is ACTIVE." % NAME_VM_1)
443
444     # Retrieve IP of first VM
445     test_ip = vm1.networks.get(NEUTRON_PRIVATE_NET_NAME)[0]
446     logger.debug("Instance '%s' got private ip '%s'." % (NAME_VM_1, test_ip))
447
448     logger.info("Adding '%s' to security group '%s'..." % (NAME_VM_1, SECGROUP_NAME))
449     functest_utils.add_secgroup_to_instance(nova_client, vm1.id, sg_id)
450
451     # boot VM 2
452     logger.info("Creating instance '%s'..." % NAME_VM_2)
453     logger.debug(
454         "Configuration:\n name=%s \n flavor=%s \n image=%s \n "
455         "network=%s \n" % (NAME_VM_2, flavor, image_id, network_id))
456     vm2 = nova_client.servers.create(
457         name=NAME_VM_2,
458         flavor=flavor,
459         image=image_id,
460         nics=[{"net-id": network_id}]
461     )
462
463     if not waitVmActive(nova_client, vm2):
464         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
465             NAME_VM_2, functest_utils.get_instance_status(nova_client, vm2)))
466         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatip_dic)
467         return (EXIT_CODE)
468     else:
469         logger.info("Instance '%s' is ACTIVE." % NAME_VM_2)
470
471     logger.info("Adding '%s' to security group '%s'..." % (NAME_VM_2, SECGROUP_NAME))
472     functest_utils.add_secgroup_to_instance(nova_client, vm2.id, sg_id)
473
474     logger.info("Creating floating IP for VM '%s'..." % NAME_VM_2)
475     floatip_dic = functest_utils.create_floating_ip(neutron_client)
476     floatip = floatip_dic['fip_addr']
477     floatip_id = floatip_dic['fip_id']
478
479     if floatip is None:
480         logger.error("Cannot create floating IP.")
481         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatip_dic)
482         return (EXIT_CODE)
483     logger.info("Floating IP created: '%s'" % floatip)
484
485     logger.info("Associating floating ip: '%s' to VM '%s' " % (floatip, NAME_VM_2))
486     if not functest_utils.add_floating_ip(nova_client, vm2.id, floatip):
487         logger.error("Cannot associate floating IP to VM.")
488         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatip_dic)
489         return (EXIT_CODE)
490
491     logger.info("Trying to establish SSH connection to %s..." % floatip)
492     username = 'cirros'
493     password = 'cubswin:)'
494     ssh = paramiko.SSHClient()
495     ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
496
497     timeout = 50
498     nolease = False
499     got_ip = False
500     discover_count = 0
501     cidr_first_octet = NEUTRON_PRIVATE_SUBNET_CIDR.split('.')[0]
502     while timeout > 0:
503         try:
504             ssh.connect(floatip, username=username, password=password, timeout=2)
505             logger.debug("SSH connection established to %s." % floatip)
506             break
507         except:
508             logger.debug("Waiting for %s..." % floatip)
509             time.sleep(6)
510             timeout -= 1
511
512         console_log = vm2.get_console_output()
513
514         # print each "Sending discover" captured on the console log
515         if len(re.findall("Sending discover", console_log)) > discover_count and not got_ip:
516             discover_count += 1
517             logger.debug("Console-log '%s': Sending discover..." % NAME_VM_2)
518
519         # check if eth0 got an ip, the line looks like this: "inet addr:192.168."....
520         # if the dhcp agent fails to assing ip, this line will not appear
521         if "inet addr:"+cidr_first_octet in console_log and not got_ip:
522             got_ip = True
523             logger.debug("The instance '%s' succeeded to get the IP from the dhcp agent.")
524
525         # if dhcp doesn't work, it shows "No lease, failing". The test will fail...
526         if "No lease, failing" in console_log and not nolease and not got_ip:
527                 nolease = True
528                 logger.debug("Console-log '%s': No lease, failing..." % NAME_VM_2)
529                 logger.info("The instance failed to get an IP from "\
530                             "the DHCP agent. The test will probably timeout...")
531
532     if timeout == 0:  # 300 sec timeout (5 min)
533         logger.error("Cannot establish connection to IP '%s'. Aborting" % floatip)
534         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatip_dic)
535         return (EXIT_CODE)
536
537     scp = SCPClient(ssh.get_transport())
538
539     ping_script = REPO_PATH + "testcases/vPing/CI/libraries/ping.sh"
540     try:
541         scp.put(ping_script, "~/")
542     except:
543         logger.error("Cannot SCP the file '%s' to VM '%s'" % (ping_script, floatip))
544
545     cmd = 'chmod 755 ~/ping.sh'
546     (stdin, stdout, stderr) = ssh.exec_command(cmd)
547     for line in stdout.readlines():
548         print line
549
550     logger.info("Waiting for ping...")
551     sec = 0
552     duration = 0
553
554     cmd = '~/ping.sh ' + test_ip
555     flag = False
556     while True:
557         time.sleep(1)
558         (stdin, stdout, stderr) = ssh.exec_command(cmd)
559         output = stdout.readlines()
560
561         for line in output:
562             if "vPing OK" in line:
563                 logger.info("vPing detected!")
564
565                 # we consider start time at VM1 booting
566                 end_time_ts = time.time()
567                 duration = round(end_time_ts - start_time_ts, 1)
568                 logger.info("vPing duration:'%s' s." % duration)
569                 EXIT_CODE = 0
570                 flag = True
571                 break
572             elif sec == PING_TIMEOUT:
573                 logger.info("Timeout reached.")
574                 flag = True
575                 break
576         if flag:
577             break
578         logger.debug("Pinging %s. Waiting for response..." % test_ip)
579         sec += 1
580
581     cleanup(nova_client, neutron_client, image_id, network_dic, sg_id, floatip_dic)
582
583     test_status = "NOK"
584     if EXIT_CODE == 0:
585         logger.info("vPing OK")
586         test_status = "OK"
587     else:
588         duration = 0
589         logger.error("vPing FAILED")
590
591     if args.report:
592         push_results(start_time_ts, duration, test_status)
593
594     exit(EXIT_CODE)
595
596 if __name__ == '__main__':
597     main()