79443b9d43c98540ed9b789a2da2aff931477ed7
[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         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 {ip} done.'.format(ip=host))
147         logger.info('Setup of {type} node {ip} done.'.format(type=node['type'],
148                                                              ip=host))
149         result = True
150
151     if isinstance(results, list):
152         results.append(result)
153     return result
154
155
156 def delete_local_tarball(tarball):
157     """Delete local tarball to prevent disk pollution.
158
159     :param tarball: Path of local tarball to delete.
160     :type tarball: str
161     :returns: nothing
162     """
163     remove(tarball)
164
165
166 def delete_framework_dir(node):
167     """Delete framework directory in /tmp/ on given node.
168
169     :param node: Node to delete framework directory on.
170     :type node: dict
171     """
172     host = node['host']
173     logger.console(
174         'Deleting framework directory on {0} starts.'.format(host))
175     exec_cmd_no_error(
176         node, 'sudo rm -rf {0}'.format(con.REMOTE_FW_DIR),
177         message="Framework delete failed at node {host}".format(host=host),
178         timeout=100, include_reason=True)
179     logger.console(
180         'Deleting framework directory on {0} done.'.format(host))
181
182
183 def cleanup_node(node, results=None):
184     """Delete a tarball from a node.
185
186     :param node: A node where the tarball will be delete.
187     :param results: A list where to store the result of node cleanup, optional.
188     :type node: dict
189     :type results: list
190     :returns: True - success, False - error
191     :rtype: bool
192     """
193     host = node['host']
194     try:
195         delete_framework_dir(node)
196     except RuntimeError:
197         logger.error("Cleanup of node {0} failed.".format(host))
198         result = False
199     else:
200         logger.console('Cleanup of node {0} done.'.format(host))
201         result = True
202
203     if isinstance(results, list):
204         results.append(result)
205     return result
206
207
208 class SetupFramework(object):
209     """Setup suite run on topology nodes.
210
211     Many VAT/CLI based tests need the scripts at remote hosts before executing
212     them. This class packs the whole testing directory and copies it over
213     to all nodes in topology under /tmp/
214     """
215
216     @staticmethod
217     def setup_framework(nodes):
218         """Pack the whole directory and extract in temp on each node.
219
220         :param nodes: Topology nodes.
221         :type nodes: dict
222         :raises RuntimeError: If setup framework failed.
223         """
224
225         tarball = pack_framework_dir()
226         msg = 'Framework packed to {0}'.format(tarball)
227         logger.console(msg)
228         logger.trace(msg)
229         remote_tarball = "/tmp/{0}".format(basename(tarball))
230
231         results = []
232         threads = []
233
234         for node in nodes.values():
235             args = node, tarball, remote_tarball, results
236             thread = threading.Thread(target=setup_node, args=args)
237             thread.start()
238             threads.append(thread)
239
240         logger.info(
241             'Executing node setups in parallel, waiting for threads to end')
242
243         for thread in threads:
244             thread.join()
245
246         logger.info('Results: {0}'.format(results))
247
248         delete_local_tarball(tarball)
249         if all(results):
250             logger.console('All nodes are ready.')
251         else:
252             raise RuntimeError('Failed to setup framework.')
253
254
255 class CleanupFramework(object):
256     """Clean up suite run on topology nodes."""
257
258     @staticmethod
259     def cleanup_framework(nodes):
260         """Perform cleanup on each node.
261
262         :param nodes: Topology nodes.
263         :type nodes: dict
264         :raises RuntimeError: If cleanup framework failed.
265         """
266
267         results = []
268         threads = []
269
270         for node in nodes.values():
271             thread = threading.Thread(target=cleanup_node,
272                                       args=(node, results))
273             thread.start()
274             threads.append(thread)
275
276         logger.info(
277             'Executing node cleanups in parallel, waiting for threads to end.')
278
279         for thread in threads:
280             thread.join()
281
282         logger.info('Results: {0}'.format(results))
283
284         if all(results):
285             logger.console('All nodes cleaned up.')
286         else:
287             raise RuntimeError('Failed to cleaned up framework.')