Add utility tools to ApexLake
[yardstick.git] / yardstick / vTC / apexlake / experimental_framework / common.py
1 # Copyright (c) 2015 Intel Research and Development Ireland Ltd.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import os
16 import re
17 import ConfigParser
18 import logging
19 import fileinput
20 from experimental_framework.constants import conf_file_sections as cf
21 from experimental_framework.constants import framework_parameters as fp
22
23
24 # ------------------------------------------------------
25 # List of common variables
26 # ------------------------------------------------------
27
28 LOG = None
29 CONF_FILE = None
30 DEPLOYMENT_UNIT = None
31 ITERATIONS = None
32
33 BASE_DIR = None
34 RESULT_DIR = None
35 TEMPLATE_DIR = None
36 TEMPLATE_NAME = None
37 TEMPLATE_FILE_EXTENSION = None
38
39 PKTGEN = None
40 PKTGEN_DIR = None
41 PKTGEN_DPDK_DIRECTORY = None
42 PKTGEN_PROGRAM = None
43 PKTGEN_COREMASK = None
44 PKTGEN_MEMCHANNEL = None
45 PKTGEN_BUS_SLOT_NIC_1 = None
46 PKTGEN_BUS_SLOT_NIC_2 = None
47
48 INFLUXDB_IP = None
49 INFLUXDB_PORT = None
50 INFLUXDB_DB_NAME = None
51
52
53 # ------------------------------------------------------
54 # Initialization and Input 'heat_templates/'validation
55 # ------------------------------------------------------
56
57 def init(api=False):
58     global BASE_DIR
59     BASE_DIR = os.getcwd()
60     BASE_DIR = BASE_DIR.replace('/experimental_framework', '')
61     BASE_DIR = InputValidation.validate_directory_exist_and_format(
62         BASE_DIR, "Error 000001")
63
64     init_conf_file(api)
65     init_general_vars()
66     init_log()
67     if len(CONF_FILE.get_variable_list(cf.CFS_PKTGEN)) > 0:
68         init_pktgen()
69
70
71 def init_conf_file(api=False):
72     global CONF_FILE
73     if api:
74         CONF_FILE = ConfigurationFile(cf.get_sections_api())
75     else:
76         CONF_FILE = ConfigurationFile(cf.get_sections())
77
78
79 def init_general_vars():
80     global TEMPLATE_FILE_EXTENSION
81     global TEMPLATE_NAME
82     global TEMPLATE_DIR
83     global RESULT_DIR
84     global ITERATIONS
85
86     TEMPLATE_FILE_EXTENSION = '.yaml'
87
88     # Check Section in Configuration File
89     InputValidation.\
90         validate_configuration_file_section(
91             cf.CFS_GENERAL,
92             "Section " + cf.CFS_GENERAL +
93             "is not present in configuration file")
94
95     TEMPLATE_DIR = BASE_DIR + 'heat_templates/'
96
97     # Validate template name
98     InputValidation.\
99         validate_configuration_file_parameter(
100             cf.CFS_GENERAL,
101             cf.CFSG_TEMPLATE_NAME,
102             "Parameter " + cf.CFSG_TEMPLATE_NAME +
103             "is not present in configuration file")
104
105     TEMPLATE_NAME = CONF_FILE.get_variable(cf.CFS_GENERAL,
106                                            cf.CFSG_TEMPLATE_NAME)
107     InputValidation.validate_file_exist(
108         TEMPLATE_DIR + TEMPLATE_NAME,
109         "The provided template file does not exist")
110     RESULT_DIR = BASE_DIR + 'results/'
111
112     # Validate and assign Iterations
113     if cf.CFSG_ITERATIONS in CONF_FILE.get_variable_list(cf.CFS_GENERAL):
114         ITERATIONS = int(CONF_FILE.get_variable(cf.CFS_GENERAL,
115                                                 cf.CFSG_ITERATIONS))
116     else:
117         ITERATIONS = 1
118
119
120 def init_log():
121     global LOG
122     if cf.CFSG_DEBUG in CONF_FILE.get_variable_list(cf.CFS_GENERAL) and \
123             CONF_FILE.get_variable(cf.CFS_GENERAL, cf.CFSG_DEBUG):
124             logging.basicConfig(level=logging.DEBUG)
125     else:
126         logging.basicConfig(level=logging.INFO)
127
128     LOG = logging.getLogger()
129     log_formatter = logging.Formatter("%(asctime)s --- %(message)s")
130     file_handler = logging.FileHandler("{0}/{1}.log".format("./", "benchmark"))
131     file_handler.setFormatter(log_formatter)
132     file_handler.setLevel(logging.DEBUG)
133     LOG.addHandler(file_handler)
134
135
136 # ------------------------------------------------------
137 # InfluxDB conf variables
138 # ------------------------------------------------------
139 def init_influxdb():
140     global INFLUXDB_IP
141     global INFLUXDB_PORT
142     global INFLUXDB_DB_NAME
143
144     INFLUXDB_IP = CONF_FILE.get_variable(cf.CFS_INFLUXDB, cf.CFSI_IDB_IP)
145     INFLUXDB_PORT = CONF_FILE.get_variable(cf.CFS_INFLUXDB, cf.CFSI_IDB_PORT)
146     INFLUXDB_DB_NAME = CONF_FILE.get_variable(cf.CFS_INFLUXDB,
147                                               cf.CFSI_IDB_DB_NAME)
148
149
150 # ------------------------------------------------------
151 # Packet Generator conf variables
152 # ------------------------------------------------------
153 def init_pktgen():
154     global PKTGEN
155     global PKTGEN_DIR
156     global PKTGEN_PROGRAM
157     global PKTGEN_COREMASK
158     global PKTGEN_MEMCHANNEL
159     global PKTGEN_BUS_SLOT_NIC_1
160     global PKTGEN_BUS_SLOT_NIC_2
161     global PKTGEN_DPDK_DIRECTORY
162
163     msg = "Section {} is not present in the configuration file".\
164         format(cf.CFS_PKTGEN)
165     InputValidation.validate_configuration_file_section(cf.CFS_PKTGEN, msg)
166
167     pktgen_var_list = CONF_FILE.get_variable_list(cf.CFS_PKTGEN)
168     PKTGEN = 'dpdk_pktgen'  # default value
169     if cf.CFSP_PACKET_GENERATOR in pktgen_var_list:
170         msg = "Parameter {} is not present in section {}".format(
171             cf.CFSP_PACKET_GENERATOR, cf.CFS_PKTGEN)
172         InputValidation.validate_configuration_file_parameter(
173             cf.CFS_PKTGEN, cf.CFSP_PACKET_GENERATOR, msg)
174         PKTGEN = CONF_FILE.get_variable(
175             cf.CFS_PKTGEN, cf.CFSP_PACKET_GENERATOR)
176
177     if PKTGEN not in fp.get_supported_packet_generators():
178         raise ValueError('The specified packet generator is not supported '
179                          'by the framework')
180
181     # Check if the packet gen is dpdk_pktgen
182     if PKTGEN == cf.CFSP_PG_DPDK:
183         # Validation of DPDK pktgen directory
184         msg = "Parameter {} is not present in section {}".format(
185             cf.CFSP_DPDK_PKTGEN_DIRECTORY, cf.CFS_PKTGEN)
186         InputValidation.validate_configuration_file_parameter(
187             cf.CFS_PKTGEN, cf.CFSP_DPDK_PKTGEN_DIRECTORY, msg)
188         PKTGEN_DIR = CONF_FILE.get_variable(
189             cf.CFS_PKTGEN, cf.CFSP_DPDK_PKTGEN_DIRECTORY)
190         msg = "The directory {} does not exist.".format(PKTGEN_DIR)
191         PKTGEN_DIR = InputValidation.validate_directory_exist_and_format(
192             PKTGEN_DIR, msg)
193
194         # Validation of the DPDK program name
195         msg = "Parameter {} is not present in section {}".format(
196             cf.CFSP_DPDK_PROGRAM_NAME, cf.CFS_PKTGEN)
197         InputValidation.validate_configuration_file_parameter(
198             cf.CFS_PKTGEN, cf.CFSP_DPDK_PROGRAM_NAME, msg)
199         PKTGEN_PROGRAM = CONF_FILE.get_variable(
200             cf.CFS_PKTGEN, cf.CFSP_DPDK_PROGRAM_NAME)
201
202         # Validation of the DPDK Coremask parameter
203         msg = "Parameter {} is not present in section {}".format(
204             cf.CFSP_DPDK_COREMASK, cf.CFS_PKTGEN)
205         InputValidation.validate_configuration_file_parameter(
206             cf.CFS_PKTGEN, cf.CFSP_DPDK_COREMASK, msg)
207         PKTGEN_COREMASK = CONF_FILE.get_variable(
208             cf.CFS_PKTGEN, cf.CFSP_DPDK_COREMASK)
209
210         # Validation of the DPDK Memory Channel parameter
211         msg = "Parameter {} is not present in section {}".format(
212             cf.CFSP_DPDK_MEMORY_CHANNEL, cf.CFS_PKTGEN)
213         InputValidation.validate_configuration_file_parameter(
214             cf.CFS_PKTGEN, cf.CFSP_DPDK_MEMORY_CHANNEL, msg)
215         PKTGEN_MEMCHANNEL = CONF_FILE.get_variable(
216             cf.CFS_PKTGEN, cf.CFSP_DPDK_MEMORY_CHANNEL)
217
218         # Validation of the DPDK Bus Slot 1
219         msg = "Parameter {} is not present in section {}".format(
220             cf.CFSP_DPDK_BUS_SLOT_NIC_1, cf.CFS_PKTGEN)
221         InputValidation.validate_configuration_file_parameter(
222             cf.CFS_PKTGEN, cf.CFSP_DPDK_BUS_SLOT_NIC_1, msg)
223         PKTGEN_BUS_SLOT_NIC_1 = CONF_FILE.get_variable(
224             cf.CFS_PKTGEN, cf.CFSP_DPDK_BUS_SLOT_NIC_1)
225
226         # Validation of the DPDK Bus Slot 2
227         msg = "Parameter {} is not present in section {}".format(
228             cf.CFSP_DPDK_BUS_SLOT_NIC_2, cf.CFS_PKTGEN)
229         InputValidation.validate_configuration_file_parameter(
230             cf.CFS_PKTGEN, cf.CFSP_DPDK_BUS_SLOT_NIC_2, msg)
231         PKTGEN_BUS_SLOT_NIC_2 = CONF_FILE.get_variable(
232             cf.CFS_PKTGEN, cf.CFSP_DPDK_BUS_SLOT_NIC_2)
233
234         # Validation of DPDK directory parameter
235         msg = "Parameter {} is not present in section {}".format(
236             cf.CFSP_DPDK_DPDK_DIRECTORY, cf.CFS_PKTGEN)
237         InputValidation.validate_configuration_file_parameter(
238             cf.CFS_PKTGEN, cf.CFSP_DPDK_DPDK_DIRECTORY, msg)
239         PKTGEN_DPDK_DIRECTORY = CONF_FILE.get_variable(
240             cf.CFS_PKTGEN, cf.CFSP_DPDK_DPDK_DIRECTORY)
241         msg = "Directory {} does not exist".format(
242             cf.CFSP_DPDK_DPDK_DIRECTORY)
243         PKTGEN_DPDK_DIRECTORY = InputValidation.\
244             validate_directory_exist_and_format(PKTGEN_DPDK_DIRECTORY, msg)
245
246
247 # ------------------------------------------------------
248 # Configuration file access
249 # ------------------------------------------------------
250
251 class ConfigurationFile:
252     """
253     Used to extract data from the configuration file
254     """
255
256     def __init__(self, sections, config_file='conf.cfg'):
257         """
258         Reads configuration file sections
259
260         :param sections: list of strings representing the sections to be
261                          loaded
262         :param config_file: name of the configuration file (string)
263         :return: None
264         """
265         InputValidation.validate_string(
266             config_file, "The configuration file name must be a string")
267         config_file = BASE_DIR + config_file
268         InputValidation.validate_file_exist(
269             config_file, 'The provided configuration file does not exist')
270         self.config = ConfigParser.ConfigParser()
271         self.config.read(config_file)
272         for section in sections:
273             setattr(
274                 self, section, ConfigurationFile.
275                 _config_section_map(section, self.config))
276
277     @staticmethod
278     def _config_section_map(section, config_file):
279         """
280         Returns a dictionary with the configuration values for the specific
281         section
282
283         :param section: section to be loaded (string)
284         :param config_file: name of the configuration file (string)
285         :return: dict
286         """
287         dict1 = dict()
288         options = config_file.options(section)
289         for option in options:
290             dict1[option] = config_file.get(section, option)
291         return dict1
292
293     def get_variable(self, section, variable_name):
294         """
295         Returns the value correspondent to a variable
296
297         :param section: section to be loaded (string)
298         :param variable_name: name of the variable (string)
299         :return: string
300         """
301         message = "The variable name must be a string"
302         InputValidation.validate_string(variable_name, message)
303         if variable_name in self.get_variable_list(section):
304             sect = getattr(self, section)
305             return sect[variable_name]
306         else:
307             exc_msg = 'Parameter {} is not in the {} section of the conf file'
308             exc_msg.format(variable_name, section)
309             raise ValueError(exc_msg)
310
311     def get_variable_list(self, section):
312         """
313         Returns the list of the available variables in a section
314         :param section: section to be loaded (string)
315         :return: list
316         """
317         try:
318             return getattr(self, section)
319         except:
320             msg = 'Section {}  not found in the configuration file'.\
321                 format(section)
322             raise ValueError(msg)
323
324
325 # ------------------------------------------------------
326 # Get OpenStack Credentials
327 # ------------------------------------------------------
328 def get_credentials():
329     """
330     Returns the credentials for OpenStack access from the configuration file
331     :return: dictionary
332     """
333     credentials = dict()
334     credentials[cf.CFSO_IP_CONTROLLER] = CONF_FILE.get_variable(
335         cf.CFS_OPENSTACK, cf.CFSO_IP_CONTROLLER)
336     credentials[cf.CFSO_HEAT_URL] = CONF_FILE.get_variable(
337         cf.CFS_OPENSTACK, cf.CFSO_HEAT_URL)
338     credentials[cf.CFSO_USER] = CONF_FILE.get_variable(
339         cf.CFS_OPENSTACK, cf.CFSO_USER)
340     credentials[cf.CFSO_PASSWORD] = CONF_FILE.get_variable(
341         cf.CFS_OPENSTACK, cf.CFSO_PASSWORD)
342     credentials[cf.CFSO_AUTH_URI] = CONF_FILE.get_variable(
343         cf.CFS_OPENSTACK, cf.CFSO_AUTH_URI)
344     credentials[cf.CFSO_PROJECT] = CONF_FILE.get_variable(
345         cf.CFS_OPENSTACK, cf.CFSO_PROJECT)
346     return credentials
347
348
349 # ------------------------------------------------------
350 # Manage files
351 # ------------------------------------------------------
352
353 def get_heat_template_params():
354     """
355     Returns the list of deployment parameters from the configuration file
356     for the heat template
357
358     :return: dict
359     """
360     heat_parameters_list = CONF_FILE.get_variable_list(
361         cf.CFS_DEPLOYMENT_PARAMETERS)
362     testcase_parameters = dict()
363     for param in heat_parameters_list:
364         testcase_parameters[param] = CONF_FILE.get_variable(
365             cf.CFS_DEPLOYMENT_PARAMETERS, param)
366     return testcase_parameters
367
368
369 def get_testcase_params():
370     """
371     Returns the list of testcase parameters from the configuration file
372
373     :return: dict
374     """
375     testcase_parameters = dict()
376     parameters = CONF_FILE.get_variable_list(cf.CFS_TESTCASE_PARAMETERS)
377     for param in parameters:
378         testcase_parameters[param] = CONF_FILE.get_variable(
379             cf.CFS_TESTCASE_PARAMETERS, param)
380     return testcase_parameters
381
382
383 def get_file_first_line(file_name):
384     """
385     Returns the first line of a file
386
387     :param file_name: name of the file to be read (str)
388     :return: str
389     """
390     message = "The name of the file must be a string"
391     InputValidation.validate_string(file_name, message)
392     message = 'The file {} does not exist'.format(file_name)
393     InputValidation.validate_file_exist(file_name, message)
394     res = open(file_name, 'r')
395     return res.readline()
396
397
398 def replace_in_file(file, text_to_search, text_to_replace):
399     """
400     Replaces a string within a file
401
402     :param file: name of the file (str)
403     :param text_to_search: text to be replaced
404     :param text_to_replace: new text that will replace the previous
405     :return: None
406     """
407     message = 'The text to be replaced in the file must be a string'
408     InputValidation.validate_string(text_to_search, message)
409     message = 'The text to replace in the file must be a string'
410     InputValidation.validate_string(text_to_replace, message)
411     message = "The name of the file must be a string"
412     InputValidation.validate_string(file, message)
413     message = "The file does not exist"
414     InputValidation.validate_file_exist(file, message)
415     for line in fileinput.input(file, inplace=True):
416         print(line.replace(text_to_search, text_to_replace).rstrip())
417
418
419 # ------------------------------------------------------
420 # Shell interaction
421 # ------------------------------------------------------
422 def run_command(command):
423     LOG.info("Running command: " + command)
424     return os.system(command)
425
426
427 def push_data_influxdb(data):
428     ip = INFLUXDB_IP
429     port = INFLUXDB_PORT
430     db_name = INFLUXDB_DB_NAME
431     command = "curl -i -XPOST 'http://{}:{}/write?db={}' " \
432               "--data-binary {}".format(ip, port, db_name, data)
433     run_command(command)
434
435
436 # ------------------------------------------------------
437 # Expose variables to other modules
438 # ------------------------------------------------------
439
440 def get_base_dir():
441     return BASE_DIR
442
443
444 def get_template_dir():
445     return TEMPLATE_DIR
446
447
448 def get_dpdk_pktgen_vars():
449     if not (PKTGEN == 'dpdk_pktgen'):
450         return dict()
451     ret_val = dict()
452     ret_val[cf.CFSP_DPDK_PKTGEN_DIRECTORY] = PKTGEN_DIR
453     ret_val[cf.CFSP_DPDK_PROGRAM_NAME] = PKTGEN_PROGRAM
454     ret_val[cf.CFSP_DPDK_COREMASK] = PKTGEN_COREMASK
455     ret_val[cf.CFSP_DPDK_MEMORY_CHANNEL] = PKTGEN_MEMCHANNEL
456     ret_val[cf.CFSP_DPDK_BUS_SLOT_NIC_1] = PKTGEN_BUS_SLOT_NIC_1
457     ret_val[cf.CFSP_DPDK_BUS_SLOT_NIC_2] = PKTGEN_BUS_SLOT_NIC_2
458     ret_val[cf.CFSP_DPDK_DPDK_DIRECTORY] = PKTGEN_DPDK_DIRECTORY
459     return ret_val
460
461
462 # ------------------------------------------------------
463 # Configuration Variables from Config File
464 # ------------------------------------------------------
465 def get_deployment_configuration_variables_from_conf_file():
466     variables = dict()
467     types = dict()
468     all_variables = CONF_FILE.get_variable_list(cf.CFS_EXPERIMENT_VNF)
469     for var in all_variables:
470         v = CONF_FILE.get_variable(cf.CFS_EXPERIMENT_VNF, var)
471         type = re.findall(r'@\w*', v)
472         values = re.findall(r'\"(.+?)\"', v)
473         variables[var] = values
474         try:
475             types[var] = type[0][1:]
476         except IndexError:
477             LOG.debug("No type has been specified for variable " + var)
478     return variables
479
480
481 # ------------------------------------------------------
482 # benchmarks from Config File
483 # ------------------------------------------------------
484 def get_benchmarks_from_conf_file():
485     requested_benchmarks = list()
486     benchmarks = \
487         CONF_FILE.get_variable(cf.CFS_GENERAL, cf.CFSG_BENCHMARKS).split(', ')
488     for benchmark in benchmarks:
489         requested_benchmarks.append(benchmark)
490     return requested_benchmarks
491
492
493 class InputValidation(object):
494
495     @staticmethod
496     def validate_string(param, message):
497         if not isinstance(param, str):
498             raise ValueError(message)
499         return True
500
501     @staticmethod
502     def validate_integer(param, message):
503         if not isinstance(param, int):
504             raise ValueError(message)
505         return True
506
507     @staticmethod
508     def validate_dictionary(param, message):
509         if not isinstance(param, dict):
510             raise ValueError(message)
511         return True
512
513     @staticmethod
514     def validate_file_exist(file_name, message):
515         if not os.path.isfile(file_name):
516             raise ValueError(message + ' ' + file_name)
517         return True
518
519     @staticmethod
520     def validate_directory_exist_and_format(directory, message):
521         if not os.path.isdir(directory):
522             raise ValueError(message)
523         if not directory.endswith('/'):
524             return directory + '/'
525         return directory
526
527     @staticmethod
528     def validate_configuration_file_parameter(section, parameter, message):
529         params = CONF_FILE.get_variable_list(section)
530         if parameter not in params:
531             raise ValueError(message)
532         return True
533
534     @staticmethod
535     def validate_configuration_file_section(section, message):
536         if section not in cf.get_sections():
537             raise ValueError(message)
538         return True
539
540     @staticmethod
541     def validate_boolean(boolean, message):
542         if isinstance(boolean, bool):
543             return boolean
544         if isinstance(boolean, str):
545             if boolean == 'True':
546                 return True
547             if boolean == 'False':
548                 return False
549         raise ValueError(message)
550
551     @staticmethod
552     def validate_os_credentials(credentials):
553         if not isinstance(credentials, dict):
554             raise ValueError(
555                 'The provided openstack_credentials '
556                 'variable must be in dictionary format')
557
558         credential_keys = ['ip_controller', 'heat_url', 'user', 'password',
559                            'auth_uri', 'project']
560         missing = [
561             credential_key
562             for credential_key in credential_keys
563             if credential_key not in credentials.keys()
564         ]
565         if len(missing) == 0:
566             return True
567         msg = 'OpenStack Credentials Error! ' \
568               'The following parameters are missing: {}'.\
569             format(", ".join(missing))
570         raise ValueError(msg)