99f334e3b47b79fbae5dcf4e8e19ea366a0a8675
[csit.git] / resources / libraries / python / honeycomb / HoneycombSetup.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Implementation of keywords for Honeycomb setup."""
15
16 from robot.api import logger
17
18 from resources.libraries.python.HTTPRequest import HTTPRequest, HTTPCodes, \
19     HTTPRequestError
20 from resources.libraries.python.constants import Constants as Const
21 from resources.libraries.python.honeycomb.HoneycombUtil import HoneycombError
22 from resources.libraries.python.honeycomb.HoneycombUtil \
23     import HoneycombUtil as HcUtil
24 from resources.libraries.python.ssh import SSH
25 from resources.libraries.python.topology import NodeType
26
27
28 class HoneycombSetup(object):
29     """Implements keywords for Honeycomb setup.
30
31     The keywords implemented in this class make possible to:
32     - start Honeycomb,
33     - stop Honeycomb,
34     - check the Honeycomb start-up state,
35     - check the Honeycomb shutdown state,
36     - add VPP to the topology.
37     """
38
39     def __init__(self):
40         pass
41
42     @staticmethod
43     def start_honeycomb_on_duts(*nodes):
44         """Start Honeycomb on specified DUT nodes.
45
46         This keyword starts the Honeycomb service on specified DUTs.
47         The keyword just starts the Honeycomb and does not check its startup
48         state. Use the keyword "Check Honeycomb Startup State" to check if the
49         Honeycomb is up and running.
50         Honeycomb must be installed in "/opt" directory, otherwise the start
51         will fail.
52         :param nodes: List of nodes to start Honeycomb on.
53         :type nodes: list
54         :raises HoneycombError: If Honeycomb fails to start.
55         """
56
57         HoneycombSetup.print_environment(nodes)
58
59         logger.console("\nStarting Honeycomb service ...")
60
61         cmd = "sudo service honeycomb start"
62
63         for node in nodes:
64             if node['type'] == NodeType.DUT:
65                 ssh = SSH()
66                 ssh.connect(node)
67                 (ret_code, _, _) = ssh.exec_command_sudo(cmd)
68                 if int(ret_code) != 0:
69                     raise HoneycombError('Node {0} failed to start Honeycomb.'.
70                                          format(node['host']))
71                 else:
72                     logger.info("Starting the Honeycomb service on node {0} is "
73                                 "in progress ...".format(node['host']))
74
75     @staticmethod
76     def stop_honeycomb_on_duts(*nodes):
77         """Stop the Honeycomb service on specified DUT nodes.
78
79         This keyword stops the Honeycomb service on specified nodes. It just
80         stops the Honeycomb and does not check its shutdown state. Use the
81         keyword "Check Honeycomb Shutdown State" to check if Honeycomb has
82         stopped.
83         :param nodes: List of nodes to stop Honeycomb on.
84         :type nodes: list
85         :raises HoneycombError: If Honeycomb failed to stop.
86         """
87         logger.console("\nShutting down Honeycomb service ...")
88
89         cmd = "sudo service honeycomb stop"
90         errors = []
91
92         for node in nodes:
93             if node['type'] == NodeType.DUT:
94                 ssh = SSH()
95                 ssh.connect(node)
96                 (ret_code, _, _) = ssh.exec_command_sudo(cmd)
97                 if int(ret_code) != 0:
98                     errors.append(node['host'])
99                 else:
100                     logger.info("Stopping the Honeycomb service on node {0} is "
101                                 "in progress ...".format(node['host']))
102         if errors:
103             raise HoneycombError('Node(s) {0} failed to stop Honeycomb.'.
104                                  format(errors))
105
106     @staticmethod
107     def check_honeycomb_startup_state(*nodes):
108         """Check state of Honeycomb service during startup on specified nodes.
109
110         Reads html path from template file oper_vpp_version.url.
111
112         Honeycomb nodes reply with connection refused or the following status
113         codes depending on startup progress: codes 200, 401, 403, 404, 500, 503
114
115         :param nodes: List of DUT nodes starting Honeycomb.
116         :type nodes: list
117         :return: True if all GETs returned code 200(OK).
118         :rtype bool
119         """
120         path = HcUtil.read_path_from_url_file("oper_vpp_version")
121         expected_status_codes = (HTTPCodes.UNAUTHORIZED,
122                                  HTTPCodes.FORBIDDEN,
123                                  HTTPCodes.NOT_FOUND,
124                                  HTTPCodes.SERVICE_UNAVAILABLE,
125                                  HTTPCodes.INTERNAL_SERVER_ERROR)
126
127         for node in nodes:
128             if node['type'] == NodeType.DUT:
129                 HoneycombSetup.print_ports(node)
130                 status_code, _ = HTTPRequest.get(node, path, timeout=10,
131                                                  enable_logging=False)
132                 if status_code == HTTPCodes.OK:
133                     logger.info("Honeycomb on node {0} is up and running".
134                                 format(node['host']))
135                 elif status_code in expected_status_codes:
136                     if status_code == HTTPCodes.UNAUTHORIZED:
137                         logger.info('Unauthorized. If this triggers keyword '
138                                     'timeout, verify Honeycomb username and '
139                                     'password.')
140                     raise HoneycombError('Honeycomb on node {0} running but '
141                                          'not yet ready.'.format(node['host']),
142                                          enable_logging=False)
143                 else:
144                     raise HoneycombError('Unexpected return code: {0}.'.
145                                          format(status_code))
146
147                 status_code, _ = HcUtil.get_honeycomb_data(
148                     node, "config_vpp_interfaces")
149                 if status_code != HTTPCodes.OK:
150                     raise HoneycombError('Honeycomb on node {0} running but '
151                                          'not yet ready.'.format(node['host']),
152                                          enable_logging=False)
153         return True
154
155     @staticmethod
156     def check_honeycomb_shutdown_state(*nodes):
157         """Check state of Honeycomb service during shutdown on specified nodes.
158
159         Honeycomb nodes reply with connection refused or the following status
160         codes depending on shutdown progress: codes 200, 404.
161
162         :param nodes: List of DUT nodes stopping Honeycomb.
163         :type nodes: list
164         :return: True if all GETs fail to connect.
165         :rtype bool
166         """
167         cmd = "ps -ef | grep -v grep | grep honeycomb"
168         for node in nodes:
169             if node['type'] == NodeType.DUT:
170                 try:
171                     status_code, _ = HTTPRequest.get(node, '/index.html',
172                                                      timeout=5,
173                                                      enable_logging=False)
174                     if status_code == HTTPCodes.OK:
175                         raise HoneycombError('Honeycomb on node {0} is still '
176                                              'running.'.format(node['host']),
177                                              enable_logging=False)
178                     elif status_code == HTTPCodes.NOT_FOUND:
179                         raise HoneycombError('Honeycomb on node {0} is shutting'
180                                              ' down.'.format(node['host']),
181                                              enable_logging=False)
182                     else:
183                         raise HoneycombError('Unexpected return code: {0}.'.
184                                              format(status_code))
185                 except HTTPRequestError:
186                     logger.debug('Connection refused, checking the process '
187                                  'state ...')
188                     ssh = SSH()
189                     ssh.connect(node)
190                     (ret_code, _, _) = ssh.exec_command_sudo(cmd)
191                     if ret_code == 0:
192                         raise HoneycombError('Honeycomb on node {0} is still '
193                                              'running.'.format(node['host']),
194                                              enable_logging=False)
195                     else:
196                         logger.info("Honeycomb on node {0} has stopped".
197                                     format(node['host']))
198         return True
199
200     @staticmethod
201     def configure_unsecured_access(*nodes):
202         """Configure Honeycomb to allow restconf requests through insecure HTTP
203         used by tests. By default this is only allowed for localhost.
204
205          :param nodes: All nodes in test topology.
206          :type nodes: dict
207          :raises HoneycombError: If the configuration could not be changed.
208          """
209         # TODO: Modify tests to use HTTPS instead.
210
211         find = "restconf-binding-address"
212         replace = '\\"restconf-binding-address\\": \\"0.0.0.0\\",'
213
214         argument = '"/{0}/c\\ {1}"'.format(find, replace)
215         path = "{0}/config/honeycomb.json".format(Const.REMOTE_HC_DIR)
216         command = "sed -i {0} {1}".format(argument, path)
217
218         ssh = SSH()
219         for node in nodes:
220             if node['type'] == NodeType.DUT:
221                 ssh.connect(node)
222                 (ret_code, _, stderr) = ssh.exec_command_sudo(command)
223                 if ret_code != 0:
224                     raise HoneycombError("Failed to modify configuration on "
225                                          "node {0}, {1}".format(node, stderr))
226
227     @staticmethod
228     def print_environment(nodes):
229         """Print information about the nodes to log. The information is defined
230         by commands in cmds tuple at the beginning of this method.
231
232         :param nodes: List of DUT nodes to get information about.
233         :type nodes: list
234         """
235
236         # TODO: When everything is set and running in VIRL env, transform this
237         # method to a keyword checking the environment.
238
239         cmds = ("uname -a",
240                 "df -lh",
241                 "echo $JAVA_HOME",
242                 "echo $PATH",
243                 "which java",
244                 "java -version",
245                 "dpkg --list | grep openjdk",
246                 "ls -la /opt/honeycomb")
247
248         for node in nodes:
249             if node['type'] == NodeType.DUT:
250                 logger.info("Checking node {} ...".format(node['host']))
251                 for cmd in cmds:
252                     logger.info("Command: {}".format(cmd))
253                     ssh = SSH()
254                     ssh.connect(node)
255                     ssh.exec_command_sudo(cmd)
256
257     @staticmethod
258     def print_ports(node):
259         """Uses "sudo netstat -anp | grep java" to print port where a java
260         application listens.
261
262         :param node: Honeycomb node where we want to print the ports.
263         :type node: dict
264         """
265
266         cmds = ("netstat -anp | grep java",
267                 "ps -ef | grep [h]oneycomb")
268
269         logger.info("Checking node {} ...".format(node['host']))
270         for cmd in cmds:
271             logger.info("Command: {}".format(cmd))
272             ssh = SSH()
273             ssh.connect(node)
274             ssh.exec_command_sudo(cmd)
275
276     @staticmethod
277     def configure_log_level(node, level):
278         """Set Honeycomb logging to the specified level.
279
280         :param node: Honeycomb node.
281         :param level: Log level (INFO, DEBUG, TRACE).
282         :type node: dict
283         :type level: str
284         """
285
286         find = 'logger name=\\"io.fd\\"'
287         replace = '<logger name=\\"io.fd\\" level=\\"{0}\\"/>'.format(level)
288
289         argument = '"/{0}/c\\ {1}"'.format(find, replace)
290         path = "{0}/config/logback.xml".format(Const.REMOTE_HC_DIR)
291         command = "sed -i {0} {1}".format(argument, path)
292
293         ssh = SSH()
294         ssh.connect(node)
295         (ret_code, _, stderr) = ssh.exec_command_sudo(command)
296         if ret_code != 0:
297             raise HoneycombError("Failed to modify configuration on "
298                                  "node {0}, {1}".format(node, stderr))
299
300     @staticmethod
301     def enable_module_features(node):
302         """Configure Honeycomb to use VPP modules that are disabled by default.
303
304         Note: If the module is not enabled in VPP, Honeycomb will
305         be unable to establish VPP connection.
306
307         :param node: Honeycomb node.
308         :type node: dict
309         :raises HoneycombError: If the configuration could not be changed.
310          """
311
312         disabled_features = {
313             "NSH": "io.fd.honeycomb.vppnsh.impl.VppNshModule"
314         }
315
316         ssh = SSH()
317         ssh.connect(node)
318
319         for feature in disabled_features.keys():
320             # uncomment by replacing the entire line
321             find = replace = "{0}".format(disabled_features[feature])
322
323             argument = '"/{0}/c\\ {1}"'.format(find, replace)
324             path = "{0}/modules/io-fd-honeycomb-vpp-integration*module-config"\
325                 .format(Const.REMOTE_HC_DIR)
326             command = "sed -i {0} {1}".format(argument, path)
327
328             (ret_code, _, stderr) = ssh.exec_command_sudo(command)
329             if ret_code != 0:
330                 raise HoneycombError("Failed to modify configuration on "
331                                      "node {0}, {1}".format(node, stderr))