Put scenario in version field on the Test DB (rather that git indication)
[functest.git] / testcases / vPing / CI / libraries / vPing.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
17 import os
18 import time
19 import argparse
20 import pprint
21 import sys
22 import logging
23 import yaml
24 import datetime
25 from novaclient import client as novaclient
26 from neutronclient.v2_0 import client as neutronclient
27 from keystoneclient.v2_0 import client as keystoneclient
28 from glanceclient import client as glanceclient
29
30 pp = pprint.PrettyPrinter(indent=4)
31
32 parser = argparse.ArgumentParser()
33
34 parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
35 parser.add_argument("-r", "--report",
36                     help="Create json result file",
37                     action="store_true")
38
39 args = parser.parse_args()
40
41 """ logging configuration """
42
43 logger = logging.getLogger('vPing')
44 logger.setLevel(logging.DEBUG)
45
46 ch = logging.StreamHandler()
47
48 if args.debug:
49     ch.setLevel(logging.DEBUG)
50 else:
51     ch.setLevel(logging.INFO)
52
53 formatter = logging.Formatter('%(asctime)s - %(name)s'
54                               '- %(levelname)s - %(message)s')
55
56 ch.setFormatter(formatter)
57 logger.addHandler(ch)
58
59 REPO_PATH = os.environ['repos_dir']+'/functest/'
60 if not os.path.exists(REPO_PATH):
61     logger.error("Functest repository directory not found '%s'" % REPO_PATH)
62     exit(-1)
63 sys.path.append(REPO_PATH + "testcases/")
64 import functest_utils
65
66 with open("/home/opnfv/functest/conf/config_functest.yaml") as f:
67     functest_yaml = yaml.safe_load(f)
68 f.close()
69
70 HOME = os.environ['HOME'] + "/"
71 # vPing parameters
72 VM_BOOT_TIMEOUT = 180
73 VM_DELETE_TIMEOUT = 100
74 PING_TIMEOUT = functest_yaml.get("vping").get("ping_timeout")
75 TEST_DB = functest_yaml.get("results").get("test_db_url")
76 NAME_VM_1 = functest_yaml.get("vping").get("vm_name_1")
77 NAME_VM_2 = functest_yaml.get("vping").get("vm_name_2")
78 IP_1 = functest_yaml.get("vping").get("ip_1")
79 IP_2 = functest_yaml.get("vping").get("ip_2")
80 # GLANCE_IMAGE_NAME = functest_yaml.get("general"). \
81 #    get("openstack").get("image_name")
82 GLANCE_IMAGE_NAME = "functest-vping"
83 GLANCE_IMAGE_FILENAME = functest_yaml.get("general"). \
84     get("openstack").get("image_file_name")
85 GLANCE_IMAGE_FORMAT = functest_yaml.get("general"). \
86     get("openstack").get("image_disk_format")
87 GLANCE_IMAGE_PATH = functest_yaml.get("general"). \
88     get("directories").get("dir_functest_data") + "/" + GLANCE_IMAGE_FILENAME
89
90
91 FLAVOR = functest_yaml.get("vping").get("vm_flavor")
92
93 # NEUTRON Private Network parameters
94
95 NEUTRON_PRIVATE_NET_NAME = functest_yaml.get("vping"). \
96     get("vping_private_net_name")
97
98 NEUTRON_PRIVATE_SUBNET_NAME = functest_yaml.get("vping"). \
99     get("vping_private_subnet_name")
100
101 NEUTRON_PRIVATE_SUBNET_CIDR = functest_yaml.get("vping"). \
102     get("vping_private_subnet_cidr")
103
104 NEUTRON_ROUTER_NAME = functest_yaml.get("vping"). \
105     get("vping_router_name")
106
107
108 def pMsg(value):
109
110     """pretty printing"""
111     pp.pprint(value)
112
113
114 def waitVmActive(nova, vm):
115
116     # sleep and wait for VM status change
117     sleep_time = 3
118     count = VM_BOOT_TIMEOUT / sleep_time
119     while True:
120         status = functest_utils.get_instance_status(nova, vm)
121         logger.debug("Status: %s" % status)
122         if status == "ACTIVE":
123             return True
124         if status == "ERROR" or status == "error":
125             return False
126         if count == 0:
127             logger.debug("Booting a VM timed out...")
128             return False
129         count -= 1
130         time.sleep(sleep_time)
131     return False
132
133
134 def waitVmDeleted(nova, vm):
135
136     # sleep and wait for VM status change
137     sleep_time = 3
138     count = VM_DELETE_TIMEOUT / sleep_time
139     while True:
140         status = functest_utils.get_instance_status(nova, vm)
141         if not status:
142             return True
143         elif count == 0:
144             logger.debug("Timeout")
145             return False
146         else:
147             # return False
148             count -= 1
149         time.sleep(sleep_time)
150     return False
151
152
153 def create_private_neutron_net(neutron):
154
155     neutron.format = 'json'
156     logger.info('Creating neutron network %s...' % NEUTRON_PRIVATE_NET_NAME)
157     network_id = functest_utils. \
158         create_neutron_net(neutron, NEUTRON_PRIVATE_NET_NAME)
159
160     if not network_id:
161         return False
162     logger.debug("Network '%s' created successfully" % network_id)
163     logger.debug('Creating Subnet....')
164     subnet_id = functest_utils. \
165         create_neutron_subnet(neutron,
166                               NEUTRON_PRIVATE_SUBNET_NAME,
167                               NEUTRON_PRIVATE_SUBNET_CIDR,
168                               network_id)
169     if not subnet_id:
170         return False
171     logger.debug("Subnet '%s' created successfully" % subnet_id)
172     logger.debug('Creating Router...')
173     router_id = functest_utils. \
174         create_neutron_router(neutron, NEUTRON_ROUTER_NAME)
175
176     if not router_id:
177         return False
178
179     logger.debug("Router '%s' created successfully" % router_id)
180     logger.debug('Adding router to subnet...')
181
182     result = functest_utils.add_interface_router(neutron, router_id, subnet_id)
183
184     if not result:
185         return False
186
187     logger.debug("Interface added successfully.")
188     network_dic = {'net_id': network_id,
189                    'subnet_id': subnet_id,
190                    'router_id': router_id}
191     return network_dic
192
193
194 def cleanup(nova, neutron, image_id, network_dic, port_id1, port_id2):
195
196     # delete both VMs
197     logger.info("Cleaning up...")
198     logger.debug("Deleting image...")
199     if not functest_utils.delete_glance_image(nova, image_id):
200         logger.error("Error deleting the glance image")
201
202     vm1 = functest_utils.get_instance_by_name(nova, NAME_VM_1)
203     if vm1:
204         logger.debug("Deleting '%s'..." % NAME_VM_1)
205         nova.servers.delete(vm1)
206         # wait until VMs are deleted
207         if not waitVmDeleted(nova, vm1):
208             logger.error(
209                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
210                     NAME_VM_1, functest_utils.get_instance_status(nova, vm1)))
211         else:
212             logger.debug("Instance %s terminated." % NAME_VM_1)
213
214     vm2 = functest_utils.get_instance_by_name(nova, NAME_VM_2)
215
216     if vm2:
217         logger.debug("Deleting '%s'..." % NAME_VM_2)
218         vm2 = nova.servers.find(name=NAME_VM_2)
219         nova.servers.delete(vm2)
220
221         if not waitVmDeleted(nova, vm2):
222             logger.error(
223                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
224                     NAME_VM_2, functest_utils.get_instance_status(nova, vm2)))
225         else:
226             logger.debug("Instance %s terminated." % NAME_VM_2)
227
228     # delete created network
229     logger.info("Deleting network '%s'..." % NEUTRON_PRIVATE_NET_NAME)
230     net_id = network_dic["net_id"]
231     subnet_id = network_dic["subnet_id"]
232     router_id = network_dic["router_id"]
233
234     if not functest_utils.delete_neutron_port(neutron, port_id1):
235         logger.error("Unable to remove port '%s'" % port_id1)
236         return False
237     logger.debug("Port '%s' removed successfully" % port_id1)
238
239     if not functest_utils.delete_neutron_port(neutron, port_id2):
240         logger.error("Unable to remove port '%s'" % port_id2)
241         return False
242     logger.debug("Port '%s' removed successfully" % port_id2)
243
244     if not functest_utils.remove_interface_router(neutron, router_id,
245                                                   subnet_id):
246         logger.error("Unable to remove subnet '%s' from router '%s'" % (
247             subnet_id, router_id))
248         return False
249
250     logger.debug("Interface removed successfully")
251     if not functest_utils.delete_neutron_router(neutron, router_id):
252         logger.error("Unable to delete router '%s'" % router_id)
253         return False
254
255     logger.debug("Router deleted successfully")
256
257     if not functest_utils.delete_neutron_subnet(neutron, subnet_id):
258         logger.error("Unable to delete subnet '%s'" % subnet_id)
259         return False
260
261     logger.debug(
262         "Subnet '%s' deleted successfully" % NEUTRON_PRIVATE_SUBNET_NAME)
263
264     if not functest_utils.delete_neutron_net(neutron, net_id):
265         logger.error("Unable to delete network '%s'" % net_id)
266         return False
267
268     logger.debug(
269         "Network '%s' deleted successfully" % NEUTRON_PRIVATE_NET_NAME)
270
271     return True
272
273
274 def main():
275
276     creds_nova = functest_utils.get_credentials("nova")
277     nova_client = novaclient.Client('2', **creds_nova)
278     creds_neutron = functest_utils.get_credentials("neutron")
279     neutron_client = neutronclient.Client(**creds_neutron)
280     creds_keystone = functest_utils.get_credentials("keystone")
281     keystone_client = keystoneclient.Client(**creds_keystone)
282     glance_endpoint = keystone_client.service_catalog.url_for(service_type='image',
283                                                               endpoint_type='publicURL')
284     glance_client = glanceclient.Client(1, glance_endpoint,
285                                         token=keystone_client.auth_token)
286     EXIT_CODE = -1
287
288     image = None
289     flavor = None
290
291     logger.debug("Creating image '%s' from '%s'..." % (GLANCE_IMAGE_NAME,
292                                                        GLANCE_IMAGE_PATH))
293     image_id = functest_utils.create_glance_image(glance_client,
294                                                   GLANCE_IMAGE_NAME,
295                                                   GLANCE_IMAGE_PATH)
296     if not image_id:
297         logger.error("Failed to create a Glance image...")
298         exit(-1)
299
300     # Check if the given image exists
301     image = functest_utils.get_image_id(glance_client, GLANCE_IMAGE_NAME)
302     if image == '':
303         logger.error("ERROR: Glance image '%s' not found." % GLANCE_IMAGE_NAME)
304         logger.info("Available images are: ")
305         pMsg(nova_client.images.list())
306         exit(-1)
307
308     network_dic = create_private_neutron_net(neutron_client)
309
310     if not network_dic:
311         logger.error(
312             "There has been a problem when creating the neutron network")
313         exit(-1)
314
315     network_id = network_dic["net_id"]
316
317     # Check if the given flavor exists
318
319     try:
320         flavor = nova_client.flavors.find(name=FLAVOR)
321         logger.info("Flavor found '%s'" % FLAVOR)
322     except:
323         logger.error("Flavor '%s' not found." % FLAVOR)
324         logger.info("Available flavors are: ")
325         pMsg(nova_client.flavor.list())
326         exit(-1)
327
328     # Deleting instances if they exist
329
330     servers = nova_client.servers.list()
331     for server in servers:
332         if server.name == NAME_VM_1 or server.name == NAME_VM_2:
333             logger.info("Instance %s found. Deleting..." % server.name)
334             server.delete()
335
336     # boot VM 1
337     # basic boot
338     # tune (e.g. flavor, images, network) to your specific
339     # openstack configuration here
340     # we consider start time at VM1 booting
341     start_time_ts = time.time()
342     end_time_ts = start_time_ts
343     logger.info("vPing Start Time:'%s'" % (
344         datetime.datetime.fromtimestamp(start_time_ts).strftime(
345             '%Y-%m-%d %H:%M:%S')))
346
347     # create VM
348     logger.debug("Creating port 'vping-port-1' with IP %s..." % IP_1)
349     port_id1 = functest_utils.create_neutron_port(neutron_client,
350                                                   "vping-port-1", network_id,
351                                                   IP_1)
352     if not port_id1:
353         logger.error("Unable to create port.")
354         exit(-1)
355
356     logger.info("Creating instance '%s' with IP %s..." % (NAME_VM_1, IP_1))
357     logger.debug(
358         "Configuration:\n name=%s \n flavor=%s \n image=%s \n "
359         "network=%s \n" % (NAME_VM_1, flavor, image, network_id))
360     vm1 = nova_client.servers.create(
361         name=NAME_VM_1,
362         flavor=flavor,
363         image=image,
364         # nics = [{"net-id": network_id, "v4-fixed-ip": IP_1}]
365         nics=[{"port-id": port_id1}]
366     )
367
368     # wait until VM status is active
369     if not waitVmActive(nova_client, vm1):
370
371         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
372             NAME_VM_1, functest_utils.get_instance_status(nova_client, vm1)))
373         cleanup(nova_client, neutron_client, image_id, network_dic, port_id1)
374         return (EXIT_CODE)
375     else:
376         logger.info("Instance '%s' is ACTIVE." % NAME_VM_1)
377
378     # Retrieve IP of first VM
379     # logger.debug("Fetching IP...")
380     # server = functest_utils.get_instance_by_name(nova_client, NAME_VM_1)
381     # theoretically there is only one IP address so we take the
382     # first element of the table
383     # Dangerous! To be improved!
384     # test_ip = server.networks.get(NEUTRON_PRIVATE_NET_NAME)[0]
385     test_ip = IP_1
386     logger.debug("Instance '%s' got %s" % (NAME_VM_1, test_ip))
387
388     # boot VM 2
389     # we will boot then execute a ping script with cloud-init
390     # the long chain corresponds to the ping procedure converted with base 64
391     # tune (e.g. flavor, images, network) to your specific openstack
392     #  configuration here
393     u = "#!/bin/sh\n\nwhile true; do\n ping -c 1 %s 2>&1 >/dev/null\n " \
394         "RES=$?\n if [ \"Z$RES\" = \"Z0\" ] ; then\n  echo 'vPing OK'\n " \
395         "break\n else\n  echo 'vPing KO'\n fi\n sleep 1\ndone\n" % test_ip
396
397     # create VM
398     logger.debug("Creating port 'vping-port-2' with IP %s..." % IP_2)
399     port_id2 = functest_utils.create_neutron_port(neutron_client,
400                                                   "vping-port-2", network_id,
401                                                   IP_2)
402
403     if not port_id2:
404         logger.error("Unable to create port.")
405         exit(-1)
406     logger.info("Creating instance '%s' with IP %s..." % (NAME_VM_2, IP_2))
407     logger.debug(
408         "Configuration:\n name=%s \n flavor=%s \n image=%s \n network=%s "
409         "\n userdata= \n%s" % (
410             NAME_VM_2, flavor, image, network_id, u))
411     vm2 = nova_client.servers.create(
412         name=NAME_VM_2,
413         flavor=flavor,
414         image=image,
415         # nics = [{"net-id": network_id, "v4-fixed-ip": IP_2}],
416         nics=[{"port-id": port_id2}],
417         userdata=u
418     )
419
420     if not waitVmActive(nova_client, vm2):
421         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
422             NAME_VM_2, functest_utils.get_instance_status(nova_client, vm2)))
423         cleanup(nova_client, neutron_client, image_id, network_dic,
424                 port_id1, port_id2)
425         return (EXIT_CODE)
426     else:
427         logger.info("Instance '%s' is ACTIVE." % NAME_VM_2)
428
429     logger.info("Waiting for ping...")
430     sec = 0
431     metadata_tries = 0
432     console_log = vm2.get_console_output()
433     duration = 0
434
435     while True:
436         time.sleep(1)
437         console_log = vm2.get_console_output()
438         # print "--"+console_log
439         # report if the test is failed
440         if "vPing OK" in console_log:
441             logger.info("vPing detected!")
442
443             # we consider start time at VM1 booting
444             end_time_ts = time.time()
445             duration = round(end_time_ts - start_time_ts, 1)
446             logger.info("vPing duration:'%s'" % duration)
447             EXIT_CODE = 0
448             break
449         elif "failed to read iid from metadata" in console_log or \
450                 metadata_tries > 5:
451             EXIT_CODE = -2
452             break
453         elif sec == PING_TIMEOUT:
454             logger.info("Timeout reached.")
455             break
456         elif sec % 10 == 0:
457             if "request failed" in console_log:
458                 logger.debug("It seems userdata is not supported in nova boot."+\
459                             " Waiting a bit...")
460                 metadata_tries += 1
461             else:
462                 logger.debug("No ping detected yet...")
463         sec += 1
464
465     test_status = "NOK"
466     if EXIT_CODE == 0:
467         logger.info("vPing OK")
468         test_status = "OK"
469     elif EXIT_CODE == -2:
470         logger.info("Userdata is not supported in nova boot. Aborting test...")
471     else:
472         logger.error("vPing FAILED")
473
474     cleanup(nova_client, neutron_client, image_id, network_dic,
475             port_id1, port_id2)
476
477     try:
478         if args.report and EXIT_CODE != -2:
479             # Don't report if userdata is not supported
480             logger.debug("Push result into DB")
481             # TODO check path result for the file
482             scenario = functest_utils.get_scenario(logger)
483             pod_name = functest_utils.get_pod_name(logger)
484             functest_utils.push_results_to_db(TEST_DB,
485                                               "vPing_userdata",
486                                               logger, pod_name, scenario,
487                                               payload={'timestart': start_time_ts,
488                                                        'duration': duration,
489                                                        'status': test_status})
490             # with open("vPing-result.json", "w") as outfile:
491             # json.dump({'timestart': start_time_ts, 'duration': duration,
492             # 'status': test_status}, outfile, indent=4)
493     except:
494         logger.error("Error pushing results into Database '%s'" % sys.exc_info()[0])
495
496     exit(EXIT_CODE)
497
498 if __name__ == '__main__':
499     main()