Tolerate failures when setting MTU
[csit.git] / resources / libraries / python / SetupFramework.py
1 # Copyright (c) 2019 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 """This module exists to provide setup utilities for the framework on topology
15 nodes. All tasks required to be run before the actual tests are started is
16 supposed to end up here.
17 """
18
19 from os import environ, remove
20 from os.path import basename
21 from tempfile import NamedTemporaryFile
22 import threading
23
24 from robot.api import logger
25
26 from resources.libraries.python.Constants import Constants as con
27 from resources.libraries.python.ssh import exec_cmd_no_error, scp_node
28 from resources.libraries.python.LocalExecution import run
29 from resources.libraries.python.topology import NodeType
30
31 __all__ = ["SetupFramework"]
32
33
34 def pack_framework_dir():
35     """Pack the testing WS into temp file, return its name.
36
37     :returns: Tarball file name.
38     :rtype: str
39     :raises Exception: When failed to pack testing framework.
40     """
41
42     try:
43         directory = environ["TMPDIR"]
44     except KeyError:
45         directory = None
46
47     if directory is not None:
48         tmpfile = NamedTemporaryFile(suffix=".tgz", prefix="csit-testing-",
49                                      dir="{0}".format(directory))
50     else:
51         tmpfile = NamedTemporaryFile(suffix=".tgz", prefix="csit-testing-")
52     file_name = tmpfile.name
53     tmpfile.close()
54
55     run(["tar", "--sparse", "--exclude-vcs", "--exclude=output*.xml",
56          "--exclude=./tmp", "-zcf", file_name, "."],
57         check=True, msg="Could not pack testing framework")
58
59     return file_name
60
61
62 def copy_tarball_to_node(tarball, node):
63     """Copy tarball file from local host to remote node.
64
65     :param tarball: Path to tarball to upload.
66     :param node: Dictionary created from topology.
67     :type tarball: str
68     :type node: dict
69     :returns: nothing
70     """
71     host = node['host']
72     logger.console('Copying tarball to {0} starts.'.format(host))
73     scp_node(node, tarball, "/tmp/")
74     logger.console('Copying tarball to {0} done.'.format(host))
75
76
77 def extract_tarball_at_node(tarball, node):
78     """Extract tarball at given node.
79
80     Extracts tarball using tar on given node to specific CSIT location.
81
82     :param tarball: Path to tarball to upload.
83     :param node: Dictionary created from topology.
84     :type tarball: str
85     :type node: dict
86     :returns: nothing
87     :raises RuntimeError: When failed to unpack tarball.
88     """
89     host = node['host']
90     logger.console('Extracting tarball to {0} on {1} starts.'
91                    .format(con.REMOTE_FW_DIR, host))
92     exec_cmd_no_error(
93         node, "sudo rm -rf {1}; mkdir {1}; tar -zxf {0} -C {1};"
94         " rm -f {0}".format(tarball, con.REMOTE_FW_DIR),
95         message='Failed to extract {0} at node {1}'.format(tarball, host),
96         timeout=30, include_reason=True)
97     logger.console('Extracting tarball to {0} on {1} done.'
98                    .format(con.REMOTE_FW_DIR, host))
99
100
101 def create_env_directory_at_node(node):
102     """Create fresh virtualenv to a directory, install pip requirements.
103
104     :param node: Node to create virtualenv on.
105     :type node: dict
106     :returns: nothing
107     :raises RuntimeError: When failed to setup virtualenv.
108     """
109     host = node['host']
110     logger.console('Virtualenv setup including requirements.txt on {0} starts.'
111                    .format(host))
112     exec_cmd_no_error(
113         node, 'cd {0} && rm -rf env'
114         ' && virtualenv --system-site-packages --never-download env'
115         ' && source env/bin/activate && pip install -r requirements.txt'
116         .format(con.REMOTE_FW_DIR), timeout=100, include_reason=True,
117         message="Failed install at node {host}".format(host=host))
118     logger.console('Virtualenv setup on {0} done.'.format(host))
119
120
121 def setup_node(node, tarball, remote_tarball, results=None):
122     """Copy a tarball to a node and extract it.
123
124     :param node: A node where the tarball will be copied and extracted.
125     :param tarball: Local path of tarball to be copied.
126     :param remote_tarball: Remote path of the tarball.
127     :param results: A list where to store the result of node setup, optional.
128     :type node: dict
129     :type tarball: str
130     :type remote_tarball: str
131     :type results: list
132     :returns: True - success, False - error
133     :rtype: bool
134     """
135     host = node['host']
136     try:
137         copy_tarball_to_node(tarball, node)
138         extract_tarball_at_node(remote_tarball, node)
139         if node['type'] == NodeType.TG:
140             create_env_directory_at_node(node)
141     except RuntimeError as exc:
142         logger.console("Node {node} setup failed, error: {err!r}".format(
143             node=host, err=exc))
144         result = False
145     else:
146         logger.console('Setup of node {0} done.'.format(host))
147         result = True
148
149     if isinstance(results, list):
150         results.append(result)
151     return result
152
153
154 def delete_local_tarball(tarball):
155     """Delete local tarball to prevent disk pollution.
156
157     :param tarball: Path of local tarball to delete.
158     :type tarball: str
159     :returns: nothing
160     """
161     remove(tarball)
162
163
164 def delete_framework_dir(node):
165     """Delete framework directory in /tmp/ on given node.
166
167     :param node: Node to delete framework directory on.
168     :type node: dict
169     """
170     host = node['host']
171     logger.console(
172         'Deleting framework directory on {0} starts.'.format(host))
173     exec_cmd_no_error(
174         node, 'sudo rm -rf {0}'.format(con.REMOTE_FW_DIR),
175         message="Framework delete failed at node {host}".format(host=host),
176         timeout=100, include_reason=True)
177     logger.console(
178         'Deleting framework directory on {0} done.'.format(host))
179
180
181 def cleanup_node(node, results=None):
182     """Delete a tarball from a node.
183
184     :param node: A node where the tarball will be delete.
185     :param results: A list where to store the result of node cleanup, optional.
186     :type node: dict
187     :type results: list
188     :returns: True - success, False - error
189     :rtype: bool
190     """
191     host = node['host']
192     try:
193         delete_framework_dir(node)
194     except RuntimeError:
195         logger.error("Cleanup of node {0} failed.".format(host))
196         result = False
197     else:
198         logger.console('Cleanup of node {0} done.'.format(host))
199         result = True
200
201     if isinstance(results, list):
202         results.append(result)
203     return result
204
205
206 class SetupFramework(object):
207     """Setup suite run on topology nodes.
208
209     Many VAT/CLI based tests need the scripts at remote hosts before executing
210     them. This class packs the whole testing directory and copies it over
211     to all nodes in topology under /tmp/
212     """
213
214     @staticmethod
215     def setup_framework(nodes):
216         """Pack the whole directory and extract in temp on each node.
217
218         :param nodes: Topology nodes.
219         :type nodes: dict
220         :raises RuntimeError: If setup framework failed.
221         """
222
223         tarball = pack_framework_dir()
224         msg = 'Framework packed to {0}'.format(tarball)
225         logger.console(msg)
226         logger.trace(msg)
227         remote_tarball = "/tmp/{0}".format(basename(tarball))
228
229         results = []
230         threads = []
231
232         for node in nodes.values():
233             args = node, tarball, remote_tarball, results
234             thread = threading.Thread(target=setup_node, args=args)
235             thread.start()
236             threads.append(thread)
237
238         logger.info(
239             'Executing node setups in parallel, waiting for threads to end')
240
241         for thread in threads:
242             thread.join()
243
244         logger.info('Results: {0}'.format(results))
245
246         delete_local_tarball(tarball)
247         if all(results):
248             logger.console('All nodes are ready.')
249         else:
250             raise RuntimeError('Failed to setup framework.')
251
252
253 class CleanupFramework(object):
254     """Clean up suite run on topology nodes."""
255
256     @staticmethod
257     def cleanup_framework(nodes):
258         """Perform cleanup on each node.
259
260         :param nodes: Topology nodes.
261         :type nodes: dict
262         :raises RuntimeError: If cleanup framework failed.
263         """
264
265         results = []
266         threads = []
267
268         for node in nodes.values():
269             thread = threading.Thread(target=cleanup_node,
270                                       args=(node, results))
271             thread.start()
272             threads.append(thread)
273
274         logger.info(
275             'Executing node cleanups in parallel, waiting for threads to end.')
276
277         for thread in threads:
278             thread.join()
279
280         logger.info('Results: {0}'.format(results))
281
282         if all(results):
283             logger.console('All nodes cleaned up.')
284         else:
285             raise RuntimeError('Failed to cleaned up framework.')