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