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