Setup and check honeycomb on all DUTs
[csit.git] / resources / libraries / python / 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 """Implements keywords for Honeycomb setup."""
15
16 import os.path
17 from xml.etree import ElementTree as ET
18
19 from robot.api import logger
20
21 from resources.libraries.python.topology import NodeType
22 from resources.libraries.python.ssh import SSH
23 from resources.libraries.python.HTTPRequest import HTTPRequest, \
24     HTTPRequestError, HTTP_CODES
25 from resources.libraries.python.constants import Constants as C
26
27
28 class HoneycombError(Exception):
29     """Exception(s) raised by methods working with Honeycomb."""
30
31     def __init__(self, msg, enable_logging=True):
32         """Sets the exception message and enables / disables logging
33
34         It is not wanted to log errors when using these keywords together
35         with keywords like "Wait until keyword succeeds".
36
37         :param msg: Message to be displayed and logged
38         :param enable_logging: When True, logging is enabled, otherwise
39         logging is disabled.
40         :type msg: str
41         :type enable_logging: bool
42         """
43         super(HoneycombError, self).__init__()
44         self._msg = msg
45         self._repr_msg = self.__module__ + '.' + \
46                          self.__class__.__name__ + ": " + self._msg
47         if enable_logging:
48             logger.error(self._msg)
49             logger.debug(self._repr_msg)
50
51     def __repr__(self):
52         return repr(self._repr_msg)
53
54     def __str__(self):
55         return str(self._repr_msg)
56
57
58 class HoneycombSetup(object):
59     """Implements keywords for Honeycomb setup."""
60
61     def __init__(self):
62         pass
63
64     @staticmethod
65     def start_honeycomb_on_all_duts(nodes):
66         """Start honeycomb on all DUT nodes in topology.
67
68         :param nodes: all nodes in topology
69         :type nodes: dict
70         """
71         logger.console("Starting honeycomb service")
72
73         for node in nodes.values():
74             if node['type'] == NodeType.DUT:
75                 HoneycombSetup.start_honeycomb(node)
76
77     @staticmethod
78     def start_honeycomb(node):
79         """Start up honeycomb on DUT node.
80
81         :param node: DUT node with honeycomb
82         :type node: dict
83         :return: ret_code, stdout, stderr
84         :rtype: tuple
85         :raises HoneycombError: if Honeycomb fails to start.
86         """
87
88         ssh = SSH()
89         ssh.connect(node)
90         cmd = os.path.join(C.REMOTE_HC_DIR, "start")
91         (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd)
92         if int(ret_code) != 0:
93             logger.debug('stdout: {0}'.format(stdout))
94             logger.debug('stderr: {0}'.format(stderr))
95             raise HoneycombError('Node {0} failed to start honeycomb'.
96                                  format(node['host']))
97         return ret_code, stdout, stderr
98
99     @staticmethod
100     def stop_honeycomb_on_all_duts(nodes):
101         """Stop the honeycomb service on all DUTs.
102
103         :param nodes: nodes in topology
104         :type nodes: dict
105         :return: ret_code, stdout, stderr
106         :rtype: tuple
107         :raises HoneycombError: if Honeycomb failed to stop.
108         """
109         logger.console("Shutting down honeycomb service")
110         errors = []
111         for node in nodes.values():
112             if node['type'] == NodeType.DUT:
113
114                 ssh = SSH()
115                 ssh.connect(node)
116                 cmd = os.path.join(C.REMOTE_HC_DIR, "stop")
117                 (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd)
118                 if int(ret_code) != 0:
119                     logger.debug('stdout: {0}'.format(stdout))
120                     logger.debug('stderr: {0}'.format(stderr))
121                     errors.append(node['host'])
122                     continue
123                 logger.info("Honeycomb was successfully stopped on node {0}.".
124                             format(node['host']))
125         if errors:
126             raise HoneycombError('Node(s) {0} failed to stop honeycomb.'.
127                                  format(errors))
128
129     @staticmethod
130     def check_honeycomb_startup_state(nodes):
131         """Check state of honeycomb service during startup.
132
133         Reads html path from template file vpp_version.url
134
135         Honeycomb node replies with connection refused or the following status
136         codes depending on startup progress: codes 200, 401, 403, 404, 503
137
138         :param nodes: nodes in topology
139         :type nodes: dict
140         :return: True if all GETs returned code 200(OK)
141         :rtype bool
142         """
143
144         url_file = os.path.join(C.RESOURCES_TPL_HC, "vpp_version.url")
145         with open(url_file) as template:
146             data = template.readline()
147
148         expected_status_codes = (HTTP_CODES["UNAUTHORIZED"],
149                                  HTTP_CODES["FORBIDDEN"],
150                                  HTTP_CODES["NOT_FOUND"],
151                                  HTTP_CODES["SERVICE_UNAVAILABLE"])
152
153         for node in nodes.values():
154             if node['type'] == NodeType.DUT:
155                 status_code, _ = HTTPRequest.get(node, data, timeout=10,
156                                                  enable_logging=False)
157                 if status_code == HTTP_CODES["OK"]:
158                     pass
159                 elif status_code in expected_status_codes:
160                     if status_code == HTTP_CODES["UNAUTHORIZED"]:
161                         logger.info('Unauthorized. If this triggers keyword '
162                                     'timeout, verify honeycomb '
163                                     'username and password')
164                     raise HoneycombError('Honeycomb on node {0} running but '
165                                          'not yet ready.'.format(node['host']),
166                                          enable_logging=False)
167                 else:
168                     raise HoneycombError('Unexpected return code: {0}'.
169                                          format(status_code))
170         return True
171
172     @staticmethod
173     def check_honeycomb_shutdown_state(nodes):
174         """Check state of honeycomb service during shutdown.
175
176         Honeycomb node replies with connection refused or the following status
177         codes depending on shutdown progress: codes 200, 404
178
179         :param nodes: nodes in topology
180         :type nodes: dict
181         :return: True if all GETs fail to connect
182         :rtype bool
183         """
184
185         for node in nodes.values():
186             if node['type'] == NodeType.DUT:
187                 try:
188                     status_code, _ = HTTPRequest.get(node, '/index.html',
189                                                      timeout=5,
190                                                      enable_logging=False)
191                     if status_code == HTTP_CODES["OK"]:
192                         raise HoneycombError('Honeycomb on node {0} is still '
193                                              'running'.format(node['host']),
194                                              enable_logging=False)
195                     elif status_code == HTTP_CODES["NOT_FOUND"]:
196                         raise HoneycombError('Honeycomb on node {0} is shutting'
197                                              ' down'.format(node['host']),
198                                              enable_logging=False)
199                     else:
200                         raise HoneycombError('Unexpected return code: {'
201                                              '0}'.format(status_code))
202                 except HTTPRequestError:
203                     logger.debug('Connection refused')
204
205         return True
206
207
208     @staticmethod
209     def add_vpp_to_honeycomb_network_topology(nodes, headers):
210         """Add vpp node to Honeycomb network topology.
211
212         :param nodes: all nodes in test topology
213         :param headers: headers to be used with PUT requests
214         :type nodes: dict
215         :type headers: dict
216         :return: status code and response from PUT requests
217         :rtype: tuple
218         :raises HoneycombError: if a node was not added to honeycomb topology
219
220         Reads HTML path from template file config_topology_node.url
221         Path to the node to be added, e.g.:
222         ("/restconf/config/network-topology:network-topology"
223          "/topology/topology-netconf/node/")
224         There must be "/" at the end, as generated node name is added
225         at the end.
226
227         Reads payload data from template file add_vpp_to_topology.xml
228         Information about node as XML structure, e.g.:
229         <node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
230             <node-id>
231                 {vpp_host}
232             </node-id>
233             <host xmlns="urn:opendaylight:netconf-node-topology">
234                 {vpp_ip}
235             </host>
236             <port xmlns="urn:opendaylight:netconf-node-topology">
237                 {vpp_port}
238             </port>
239             <username xmlns="urn:opendaylight:netconf-node-topology">
240                 {user}
241             </username>
242             <password xmlns="urn:opendaylight:netconf-node-topology">
243                 {passwd}
244             </password>
245             <tcp-only xmlns="urn:opendaylight:netconf-node-topology">
246                 false
247             </tcp-only>
248             <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">
249                 0
250             </keepalive-delay>
251         </node>
252         NOTE: The placeholders:
253             {vpp_host}
254             {vpp_ip}
255             {vpp_port}
256             {user}
257             {passwd}
258         MUST be there as they are replaced by correct values.
259         """
260
261         with open(os.path.join(C.RESOURCES_TPL_HC, "config_topology_node.url"))\
262                 as template:
263             path = template.readline()
264
265         try:
266             xml_data = ET.parse(os.path.join(C.RESOURCES_TPL_HC,
267                                              "add_vpp_to_topology.xml"))
268         except ET.ParseError as err:
269             raise HoneycombError(repr(err))
270         data = ET.tostring(xml_data.getroot())
271
272         status_codes = []
273         responses = []
274         for node_name, node in nodes.items():
275             if node['type'] == NodeType.DUT:
276                 try:
277                     payload = data.format(
278                         vpp_host=node_name,
279                         vpp_ip=node["host"],
280                         vpp_port=node['honeycomb']["netconf_port"],
281                         user=node['honeycomb']["user"],
282                         passwd=node['honeycomb']["passwd"])
283                     status_code, resp = HTTPRequest.put(
284                         node=node,
285                         path=path + '/' + node_name,
286                         headers=headers,
287                         payload=payload)
288                     if status_code != HTTP_CODES["OK"]:
289                         raise HoneycombError(
290                             "VPP {0} was not added to topology. "
291                             "Status code: {1}".format(node["host"],
292                                                       status_code))
293
294                     status_codes.append(status_code)
295                     responses.append(resp)
296
297                 except HTTPRequestError as err:
298                     raise HoneycombError("VPP {0} was not added to topology.\n"
299                                          "{1}".format(node["host"], repr(err)))
300
301         return status_codes, responses