1 # Copyright (c) 2018 Cisco and / or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 # use this file except in compliance with the License. You may obtain a copy
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 """wrk traffic profile parser.
16 See LLD for the structure of a wrk traffic profile.
20 from os.path import isfile
21 from pprint import pformat
23 from yaml import load, YAMLError
24 from robot.api import logger
26 from resources.tools.wrk.wrk_errors import WrkError
29 class WrkTrafficProfile(object):
30 """The wrk traffic profile.
33 MANDATORY_PARAMS = ("urls",
40 def __init__(self, profile_name):
41 """Read the traffic profile from the yaml file.
43 :param profile_name: Path to the yaml file with the profile.
44 :type profile_name: str
45 :raises: WrkError if it is not possible to parse the profile.
48 self._profile_name = None
49 self._traffic_profile = None
51 self.profile_name = profile_name
54 with open(self.profile_name, 'r') as profile_file:
55 self.traffic_profile = load(profile_file)
56 except IOError as err:
57 raise WrkError(msg="An error occurred while opening the file '{0}'."
58 .format(self.profile_name),
60 except YAMLError as err:
61 raise WrkError(msg="An error occurred while parsing the traffic "
62 "profile '{0}'.".format(self.profile_name),
65 self._validate_traffic_profile()
67 if self.traffic_profile:
68 logger.debug("\nThe wrk traffic profile '{0}' is valid.\n".
69 format(self.profile_name))
70 logger.debug("wrk traffic profile '{0}':".format(self.profile_name))
71 logger.debug(pformat(self.traffic_profile))
73 logger.debug("\nThe wrk traffic profile '{0}' is invalid.\n".
74 format(self.profile_name))
75 raise WrkError("\nThe wrk traffic profile '{0}' is invalid.\n".
76 format(self.profile_name))
79 return pformat(self.traffic_profile)
82 return pformat(self.traffic_profile)
84 def _validate_traffic_profile(self):
85 """Validate the traffic profile.
87 The specification, the structure and the rules are described in
91 logger.debug("\nValidating the wrk traffic profile '{0}'...\n".
92 format(self.profile_name))
94 # Level 1: Check if the profile is a dictionary:
95 if not isinstance(self.traffic_profile, dict):
96 logger.error("The wrk traffic profile must be a dictionary.")
97 self.traffic_profile = None
100 # Level 2: Check if all mandatory parameters are present:
102 for param in self.MANDATORY_PARAMS:
103 if self.traffic_profile.get(param, None) is None:
104 logger.error("The parameter '{0}' in mandatory.".format(param))
107 self.traffic_profile = None
110 # Level 3: Mandatory params: Check if urls is a list:
112 if not isinstance(self.traffic_profile["urls"], list):
113 logger.error("The parameter 'urls' must be a list.")
116 # Level 3: Mandatory params: Check if cpus is a valid integer:
118 cpus = int(self.traffic_profile["cpus"])
121 self.traffic_profile["cpus"] = cpus
123 logger.error("The parameter 'cpus' must be an integer greater than "
127 # Level 3: Mandatory params: Check if first-cpu is a valid integer:
129 first_cpu = int(self.traffic_profile["first-cpu"])
132 self.traffic_profile["first-cpu"] = first_cpu
134 logger.error("The parameter 'first-cpu' must be an integer greater "
138 # Level 3: Mandatory params: Check if duration is a valid integer:
140 duration = int(self.traffic_profile["duration"])
143 self.traffic_profile["duration"] = duration
145 logger.error("The parameter 'duration' must be an integer "
149 # Level 3: Mandatory params: Check if nr-of-threads is a valid integer:
151 nr_of_threads = int(self.traffic_profile["nr-of-threads"])
152 if nr_of_threads < 1:
154 self.traffic_profile["nr-of-threads"] = nr_of_threads
156 logger.error("The parameter 'nr-of-threads' must be an integer "
160 # Level 3: Mandatory params: Check if nr-of-connections is a valid
163 nr_of_connections = int(self.traffic_profile["nr-of-connections"])
164 if nr_of_connections < 1:
166 self.traffic_profile["nr-of-connections"] = nr_of_connections
168 logger.error("The parameter 'nr-of-connections' must be an integer "
172 # Level 4: Optional params: Check if script is present:
173 script = self.traffic_profile.get("script", None)
174 if script is not None:
175 if not isinstance(script, str):
176 logger.error("The path to LuaJIT script in invalid")
179 if not isfile(script):
180 logger.error("The file '{0}' in not present.".
184 self.traffic_profile["script"] = None
185 logger.debug("The optional parameter 'LuaJIT script' is not "
186 "defined. No problem.")
188 # Level 4: Optional params: Check if header is present:
189 header = self.traffic_profile.get("header", None)
191 if not (isinstance(header, dict) or isinstance(header, str)):
192 logger.error("The parameter 'header' is not valid.")
195 if isinstance(header, dict):
197 for key, val in header.items():
198 header_lst.append("{0}: {1}".format(key, val))
200 self.traffic_profile["header"] = ", ".join(header_lst)
202 logger.error("The parameter 'header' is defined but "
206 self.traffic_profile["header"] = None
207 logger.debug("The optional parameter 'header' is not defined. "
210 # Level 4: Optional params: Check if latency is present:
211 latency = self.traffic_profile.get("latency", None)
212 if latency is not None:
214 latency = bool(latency)
215 self.traffic_profile["latency"] = latency
217 logger.error("The parameter 'latency' must be boolean.")
220 self.traffic_profile["latency"] = False
221 logger.debug("The optional parameter 'latency' is not defined. "
224 # Level 4: Optional params: Check if timeout is present:
225 timeout = self.traffic_profile.get("timeout", None)
228 timeout = int(timeout)
231 self.traffic_profile["timeout"] = timeout
233 logger.error("The parameter 'timeout' must be integer greater "
237 self.traffic_profile["timeout"] = None
238 logger.debug("The optional parameter 'timeout' is not defined. "
242 self.traffic_profile = None
245 # Level 5: Check dependencies between parameters:
246 # Level 5: Check urls and cpus:
247 if self.traffic_profile["cpus"] % len(self.traffic_profile["urls"]):
248 logger.error("The number of CPUs must be a multiplication of the "
250 self.traffic_profile = None
253 def profile_name(self):
254 """Getter - Profile name.
256 :returns: The traffic profile file path
259 return self._profile_name
262 def profile_name(self, profile_name):
266 :type profile_name: str
268 self._profile_name = profile_name
271 def traffic_profile(self):
272 """Getter: Traffic profile.
274 :returns: The traffic profile.
277 return self._traffic_profile
279 @traffic_profile.setter
280 def traffic_profile(self, profile):
281 """Setter - Traffic profile.
283 :param profile: The new traffic profile.
286 self._traffic_profile = profile