Create vPing instances with dynamic ports created by nova/neutron
[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 # GLANCE_IMAGE_NAME = functest_yaml.get("general"). \
85 #    get("openstack").get("image_name")
86 GLANCE_IMAGE_NAME = "functest-vping"
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
102 NEUTRON_PRIVATE_SUBNET_NAME = functest_yaml.get("vping"). \
103     get("vping_private_subnet_name")
104
105 NEUTRON_PRIVATE_SUBNET_CIDR = functest_yaml.get("vping"). \
106     get("vping_private_subnet_cidr")
107
108 NEUTRON_ROUTER_NAME = functest_yaml.get("vping"). \
109     get("vping_router_name")
110
111 SECGROUP_NAME = "vPing-sg"
112 SECGROUP_DESCR = "Security group for vPing test case"
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     # Check if the network already exists
162     network_id = functest_utils.get_network_id(neutron,NEUTRON_PRIVATE_NET_NAME)
163     subnet_id = functest_utils.get_subnet_id(neutron,NEUTRON_PRIVATE_SUBNET_NAME)
164     router_id = functest_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 = functest_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 = functest_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 = functest_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 functest_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 functest_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 def create_security_group(neutron_client):
211     sg_id = functest_utils.get_security_group_id(neutron_client, SECGROUP_NAME)
212     if sg_id != '':
213         logger.info("Using existing security group '%s'..." % SECGROUP_NAME)
214     else:
215         logger.info("Creating security group  '%s'..." % SECGROUP_NAME)
216         SECGROUP = functest_utils.create_security_group(neutron_client,
217                                               SECGROUP_NAME,
218                                               SECGROUP_DESCR)
219         if not SECGROUP:
220             logger.error("Failed to create the security group...")
221             return False
222
223         sg_id = SECGROUP['id']
224
225         logger.debug("Security group '%s' with ID=%s created successfully." %\
226                       (SECGROUP['name'], sg_id))
227
228         logger.debug("Adding ICMP rules in security group '%s'..." % SECGROUP_NAME)
229         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
230                         'ingress', 'icmp'):
231             logger.error("Failed to create the security group rule...")
232             return False
233
234         logger.debug("Adding SSH rules in security group '%s'..." % SECGROUP_NAME)
235         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
236                         'ingress', 'tcp', '22', '22'):
237             logger.error("Failed to create the security group rule...")
238             return False
239
240         if not functest_utils.create_secgroup_rule(neutron_client, sg_id, \
241                         'egress', 'tcp', '22', '22'):
242             logger.error("Failed to create the security group rule...")
243             return False
244     return sg_id
245
246 def cleanup(nova, neutron, image_id, network_dic, sg_id):
247     if args.noclean:
248         logger.debug("The OpenStack resources are not deleted.")
249         return True
250
251     # delete both VMs
252     logger.info("Cleaning up...")
253     logger.debug("Deleting image...")
254     if not functest_utils.delete_glance_image(nova, image_id):
255         logger.error("Error deleting the glance image")
256
257     vm1 = functest_utils.get_instance_by_name(nova, NAME_VM_1)
258     if vm1:
259         logger.debug("Deleting '%s'..." % NAME_VM_1)
260         nova.servers.delete(vm1)
261         # wait until VMs are deleted
262         if not waitVmDeleted(nova, vm1):
263             logger.error(
264                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
265                     NAME_VM_1, functest_utils.get_instance_status(nova, vm1)))
266         else:
267             logger.debug("Instance %s terminated." % NAME_VM_1)
268
269     vm2 = functest_utils.get_instance_by_name(nova, NAME_VM_2)
270
271     if vm2:
272         logger.debug("Deleting '%s'..." % NAME_VM_2)
273         vm2 = nova.servers.find(name=NAME_VM_2)
274         nova.servers.delete(vm2)
275
276         if not waitVmDeleted(nova, vm2):
277             logger.error(
278                 "Instance '%s' with cannot be deleted. Status is '%s'" % (
279                     NAME_VM_2, functest_utils.get_instance_status(nova, vm2)))
280         else:
281             logger.debug("Instance %s terminated." % NAME_VM_2)
282
283     # delete created network
284     logger.debug("Deleting network '%s'..." % NEUTRON_PRIVATE_NET_NAME)
285     net_id = network_dic["net_id"]
286     subnet_id = network_dic["subnet_id"]
287     router_id = network_dic["router_id"]
288
289     if not functest_utils.remove_interface_router(neutron, router_id,
290                                                   subnet_id):
291         logger.error("Unable to remove subnet '%s' from router '%s'" % (
292             subnet_id, router_id))
293         return False
294
295     logger.debug("Interface removed successfully")
296     if not functest_utils.delete_neutron_router(neutron, router_id):
297         logger.error("Unable to delete router '%s'" % router_id)
298         return False
299
300     logger.debug("Router deleted successfully")
301
302     if not functest_utils.delete_neutron_subnet(neutron, subnet_id):
303         logger.error("Unable to delete subnet '%s'" % subnet_id)
304         return False
305
306     logger.debug(
307         "Subnet '%s' deleted successfully" % NEUTRON_PRIVATE_SUBNET_NAME)
308
309     if not functest_utils.delete_neutron_net(neutron, net_id):
310         logger.error("Unable to delete network '%s'" % net_id)
311         return False
312
313     logger.debug(
314         "Network '%s' deleted successfully" % NEUTRON_PRIVATE_NET_NAME)
315
316     if not functest_utils.delete_security_group(neutron, sg_id):
317         logger.error("Unable to delete security group '%s'" % sg_id)
318         return False
319     logger.debug(
320         "Security group '%s' deleted successfully" % sg_id)
321
322     return True
323
324 def push_results(start_time_ts, duration, test_status):
325     try:
326         logger.debug("Pushing result into DB...")
327         scenario = functest_utils.get_scenario(logger)
328         pod_name = functest_utils.get_pod_name(logger)
329         functest_utils.push_results_to_db(TEST_DB,
330                                           "functest",
331                                           "vPing",
332                                           logger, pod_name, scenario,
333                                           payload={'timestart': start_time_ts,
334                                                    'duration': duration,
335                                                    'status': test_status})
336     except:
337         logger.error("Error pushing results into Database '%s'" % sys.exc_info()[0])
338
339
340 def main():
341
342     creds_nova = functest_utils.get_credentials("nova")
343     nova_client = novaclient.Client('2', **creds_nova)
344     creds_neutron = functest_utils.get_credentials("neutron")
345     neutron_client = neutronclient.Client(**creds_neutron)
346     creds_keystone = functest_utils.get_credentials("keystone")
347     keystone_client = keystoneclient.Client(**creds_keystone)
348     glance_endpoint = keystone_client.service_catalog.url_for(service_type='image',
349                                                               endpoint_type='publicURL')
350     glance_client = glanceclient.Client(1, glance_endpoint,
351                                         token=keystone_client.auth_token)
352     EXIT_CODE = -1
353
354     image_id = None
355     flavor = None
356
357     # Check if the given image exists
358     image_id = functest_utils.get_image_id(glance_client, GLANCE_IMAGE_NAME)
359     if image_id != '':
360         logger.info("Using existing image '%s'..." % GLANCE_IMAGE_NAME)
361     else:
362         logger.info("Creating image '%s' from '%s'..." % (GLANCE_IMAGE_NAME,
363                                                        GLANCE_IMAGE_PATH))
364         image_id = functest_utils.create_glance_image(glance_client,
365                                                       GLANCE_IMAGE_NAME,
366                                                       GLANCE_IMAGE_PATH)
367         if not image_id:
368             logger.error("Failed to create a Glance image...")
369             return(EXIT_CODE)
370         logger.debug("Image '%s' with ID=%s created successfully." %\
371                   (GLANCE_IMAGE_NAME, image_id))
372
373
374     network_dic = create_private_neutron_net(neutron_client)
375
376
377     if not network_dic:
378         logger.error(
379             "There has been a problem when creating the neutron network")
380         return(EXIT_CODE)
381
382     network_id = network_dic["net_id"]
383
384     sg_id = create_security_group(neutron_client)
385
386     # Check if the given flavor exists
387     try:
388         flavor = nova_client.flavors.find(name=FLAVOR)
389         logger.info("Using existing Flavor '%s'" % FLAVOR)
390     except:
391         logger.error("Flavor '%s' not found." % FLAVOR)
392         logger.info("Available flavors are: ")
393         pMsg(nova_client.flavor.list())
394         return(EXIT_CODE)
395
396     # Deleting instances if they exist
397
398     servers = nova_client.servers.list()
399     for server in servers:
400         if server.name == NAME_VM_1 or server.name == NAME_VM_2:
401             logger.info("Instance %s found. Deleting..." % server.name)
402             server.delete()
403
404
405     # boot VM 1
406     # basic boot
407     # tune (e.g. flavor, images, network) to your specific
408     # openstack configuration here
409     # we consider start time at VM1 booting
410     start_time_ts = time.time()
411     end_time_ts = start_time_ts
412     logger.info("vPing Start Time:'%s'" % (
413         datetime.datetime.fromtimestamp(start_time_ts).strftime(
414             '%Y-%m-%d %H:%M:%S')))
415
416     # create VM
417     logger.info("Creating instance '%s'..." % NAME_VM_1)
418     logger.debug(
419         "Configuration:\n name=%s \n flavor=%s \n image=%s \n "
420         "network=%s \n" % (NAME_VM_1, flavor, image_id, network_id))
421     vm1 = nova_client.servers.create(
422         name=NAME_VM_1,
423         flavor=flavor,
424         image=image_id,
425         nics=[{"net-id": network_id}]
426     )
427
428     # wait until VM status is active
429     if not waitVmActive(nova_client, vm1):
430         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
431             NAME_VM_1, functest_utils.get_instance_status(nova_client, vm1)))
432         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
433         return (EXIT_CODE)
434     else:
435         logger.info("Instance '%s' is ACTIVE." % NAME_VM_1)
436
437     # Retrieve IP of first VM
438     # logger.debug("Fetching IP...")
439     # server = functest_utils.get_instance_by_name(nova_client, NAME_VM_1)
440     # theoretically there is only one IP address so we take the
441     # first element of the table
442     # Dangerous! To be improved!
443     test_ip = vm1.networks.get(NEUTRON_PRIVATE_NET_NAME)[0]
444     logger.debug("Instance '%s' got %s" % (NAME_VM_1, test_ip))
445
446     logger.info("Adding '%s' to security group '%s'..." % (NAME_VM_1, SECGROUP_NAME))
447     functest_utils.add_secgroup_to_instance(nova_client, vm1.id, sg_id)
448
449     # boot VM 2
450     # we will boot then execute a ping script with cloud-init
451     # the long chain corresponds to the ping procedure converted with base 64
452     # tune (e.g. flavor, images, network) to your specific openstack
453     #  configuration here
454
455
456     # create VM
457     logger.info("Creating instance '%s'..." % NAME_VM_2)
458     logger.debug(
459         "Configuration:\n name=%s \n flavor=%s \n image=%s \n "
460         "network=%s \n" % (NAME_VM_2, flavor, image_id, network_id))
461     vm2 = nova_client.servers.create(
462         name=NAME_VM_2,
463         flavor=flavor,
464         image=image_id,
465         nics=[{"net-id": network_id}]
466     )
467
468     if not waitVmActive(nova_client, vm2):
469         logger.error("Instance '%s' cannot be booted. Status is '%s'" % (
470             NAME_VM_2, functest_utils.get_instance_status(nova_client, vm2)))
471         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
472         return (EXIT_CODE)
473     else:
474         logger.info("Instance '%s' is ACTIVE." % NAME_VM_2)
475
476     logger.info("Adding '%s' to security group '%s'..." % (NAME_VM_2, SECGROUP_NAME))
477     functest_utils.add_secgroup_to_instance(nova_client, vm2.id, sg_id)
478
479     logger.info("Creating floating IP for VM '%s'..." % NAME_VM_2)
480     floatip = functest_utils.create_floating_ip(neutron_client)
481     if floatip == None:
482         logger.error("Cannot create floating IP.")
483         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
484         return (EXIT_CODE)
485     logger.info("Floating IP created: '%s'" % floatip)
486
487     logger.info("Associating floating ip: '%s' to VM '%s' " % (floatip, NAME_VM_2))
488     if not functest_utils.add_floating_ip(nova_client, vm2.id, floatip):
489         logger.error("Cannot associate floating IP to VM.")
490         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
491         return (EXIT_CODE)
492
493     logger.info("Trying to establish SSH connection to %s..." % floatip)
494     username='cirros'
495     password='cubswin:)'
496     ssh = paramiko.SSHClient()
497     ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
498
499     timeout = 50
500     while timeout > 0:
501         try:
502             ssh.connect(floatip, username=username, password=password, timeout=2)
503             logger.debug("SSH connection established to %s." % floatip)
504             break
505         except Exception, e:
506             #print e
507             logger.debug("Waiting for %s..." % floatip)
508             time.sleep(6)
509             timeout -= 1
510
511     if timeout == 0: # 300 sec timeout (5 min)
512         logger.error("Cannot establish connection to IP '%s'. Aborting" % floatip)
513         cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
514         return (EXIT_CODE)
515
516     scp = SCPClient(ssh.get_transport())
517
518     ping_script = REPO_PATH + "testcases/vPing/CI/libraries/ping.sh"
519     try:
520         scp.put(ping_script,"~/")
521     except Exception, e:
522         logger.error("Cannot SCP the file '%s' to VM '%s'" % (ping_script,floatip))
523
524
525     cmd = 'chmod 755 ~/ping.sh'
526     (stdin, stdout, stderr) = ssh.exec_command(cmd)
527     for line in stdout.readlines():
528         print line
529
530     logger.info("Waiting for ping...")
531     sec = 0
532     duration = 0
533
534     cmd = '~/ping.sh ' + test_ip
535     flag = False
536     while True:
537         time.sleep(1)
538         # we do the SCP every time in the loop because while testing, I observed
539         # that for some strange reason, the cirros VM was deleting the file if
540         # do the scp only once
541         (stdin, stdout, stderr) = ssh.exec_command(cmd)
542         output = stdout.readlines()
543         #for line in output:
544         #    print line
545
546         # print "--"+console_log
547         # report if the test is failed
548         for line in output:
549             if "vPing OK" in line:
550                 logger.info("vPing detected!")
551
552                 # we consider start time at VM1 booting
553                 end_time_ts = time.time()
554                 duration = round(end_time_ts - start_time_ts, 1)
555                 logger.info("vPing duration:'%s' s." % duration)
556                 EXIT_CODE = 0
557                 flag = True
558                 break
559             elif sec == PING_TIMEOUT:
560                 logger.info("Timeout reached.")
561                 flag = True
562                 break
563         if flag :
564             break
565         logger.debug("Pinging %s. Waiting for response..." % test_ip)
566         sec += 1
567
568     cleanup(nova_client, neutron_client, image_id, network_dic, sg_id)
569
570     test_status = "NOK"
571     if EXIT_CODE == 0:
572         logger.info("vPing OK")
573         test_status = "OK"
574     else:
575         duration = 0
576         logger.error("vPing FAILED")
577
578     if args.report:
579         push_results(start_time_ts, duration, test_status)
580
581     exit(EXIT_CODE)
582
583 if __name__ == '__main__':
584     main()