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