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