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