0b09b6d4f6dea84d19f59f7c24c714af5026fe08
[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=[{"port-id": port_id2}],
416         userdata=u
417     )
418
419     if not waitVmActive(nova_client, vm2):
420         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
421             NAME_VM_2, functest_utils.get_instance_status(nova_client, vm2)))
422         cleanup(nova_client, neutron_client, image_id, network_dic,
423                 port_id1, port_id2)
424         return (EXIT_CODE)
425     else:
426         logger.info("Instance '%s' is ACTIVE." % NAME_VM_2)
427
428     logger.info("Waiting for ping...")
429     sec = 0
430     metadata_tries = 0
431     console_log = vm2.get_console_output()
432     duration = 0
433
434     while True:
435         time.sleep(1)
436         console_log = vm2.get_console_output()
437         # print "--"+console_log
438         # report if the test is failed
439         if "vPing OK" in console_log:
440             logger.info("vPing detected!")
441
442             # we consider start time at VM1 booting
443             end_time_ts = time.time()
444             duration = round(end_time_ts - start_time_ts, 1)
445             logger.info("vPing duration:'%s'" % duration)
446             EXIT_CODE = 0
447             break
448         elif "failed to read iid from metadata" in console_log or \
449                 metadata_tries > 5:
450             EXIT_CODE = -2
451             break
452         elif sec == PING_TIMEOUT:
453             logger.info("Timeout reached.")
454             break
455         elif sec % 10 == 0:
456             if "request failed" in console_log:
457                 logger.debug("It seems userdata is not supported in nova boot."+\
458                             " Waiting a bit...")
459                 metadata_tries += 1
460             else:
461                 logger.debug("Pinging %s. Waiting for response..." % IP_2)
462         sec += 1
463
464     test_status = "NOK"
465     if EXIT_CODE == 0:
466         logger.info("vPing OK")
467         test_status = "OK"
468     elif EXIT_CODE == -2:
469         logger.info("Userdata is not supported in nova boot. Aborting test...")
470     else:
471         logger.error("vPing FAILED")
472
473     cleanup(nova_client, neutron_client, image_id, network_dic,
474             port_id1, port_id2)
475
476     try:
477         if args.report and EXIT_CODE != -2:
478             # Don't report if userdata is not supported
479             logger.debug("Push result into DB")
480             # TODO check path result for the file
481             scenario = functest_utils.get_scenario(logger)
482             pod_name = functest_utils.get_pod_name(logger)
483             functest_utils.push_results_to_db(TEST_DB,
484                                               "vPing_userdata",
485                                               logger, pod_name, scenario,
486                                               payload={'timestart': start_time_ts,
487                                                        'duration': duration,
488                                                        'status': test_status})
489             # with open("vPing-result.json", "w") as outfile:
490             # json.dump({'timestart': start_time_ts, 'duration': duration,
491             # 'status': test_status}, outfile, indent=4)
492     except:
493         logger.error("Error pushing results into Database '%s'" % sys.exc_info()[0])
494
495     exit(EXIT_CODE)
496
497 if __name__ == '__main__':
498     main()