Revert "CSIT-986: Use MLRsearch from pip"
[csit.git] / resources / libraries / python / SetupFramework.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 """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 shlex import split
20 from subprocess import Popen, PIPE, call
21 from multiprocessing import Pool
22 from tempfile import NamedTemporaryFile
23 from os.path import basename
24 from os import environ
25
26 from robot.api import logger
27 from robot.libraries.BuiltIn import BuiltIn
28
29 from resources.libraries.python.ssh import SSH
30 from resources.libraries.python.constants import Constants as con
31 from resources.libraries.python.topology import NodeType
32
33 __all__ = ["SetupFramework"]
34
35
36 def pack_framework_dir():
37     """Pack the testing WS into temp file, return its name.
38
39     :returns: Tarball file name.
40     :rtype: str
41     :raises Exception: When failed to pack testing framework.
42     """
43
44     try:
45         directory = environ["TMPDIR"]
46     except KeyError:
47         directory = None
48
49     if directory is not None:
50         tmpfile = NamedTemporaryFile(suffix=".tgz", prefix="openvpp-testing-",
51                                      dir="{0}".format(directory))
52     else:
53         tmpfile = NamedTemporaryFile(suffix=".tgz", prefix="openvpp-testing-")
54     file_name = tmpfile.name
55     tmpfile.close()
56
57     proc = Popen(
58         split("tar --sparse --exclude-vcs --exclude=output*.xml "
59               "--exclude=./tmp --exclude=*.deb --exclude=*.rpm -zcf {0} .".
60               format(file_name)), stdout=PIPE, stderr=PIPE)
61     (stdout, stderr) = proc.communicate()
62
63     logger.debug(stdout)
64     logger.debug(stderr)
65
66     return_code = proc.wait()
67     if return_code != 0:
68         raise Exception("Could not pack testing framework.")
69
70     return file_name
71
72
73 def copy_tarball_to_node(tarball, node):
74     """Copy tarball file from local host to remote node.
75
76     :param tarball: Path to tarball to upload.
77     :param node: Dictionary created from topology.
78     :type tarball: str
79     :type node: dict
80     :returns: nothing
81     """
82     logger.console('Copying tarball to {0}'.format(node['host']))
83     ssh = SSH()
84     ssh.connect(node)
85
86     ssh.scp(tarball, "/tmp/")
87
88
89 def extract_tarball_at_node(tarball, node):
90     """Extract tarball at given node.
91
92     Extracts tarball using tar on given node to specific CSIT location.
93
94     :param tarball: Path to tarball to upload.
95     :param node: Dictionary created from topology.
96     :type tarball: str
97     :type node: dict
98     :returns: nothing
99     :raises Excpetion: When failed to unpack tarball.
100     """
101     logger.console('Extracting tarball to {0} on {1}'.format(
102         con.REMOTE_FW_DIR, node['host']))
103     ssh = SSH()
104     ssh.connect(node)
105
106     cmd = 'sudo rm -rf {1}; mkdir {1} ; tar -zxf {0} -C {1}; ' \
107         'rm -f {0}'.format(tarball, con.REMOTE_FW_DIR)
108     (ret_code, _, stderr) = ssh.exec_command(cmd, timeout=30)
109     if ret_code != 0:
110         logger.error('Unpack error: {0}'.format(stderr))
111         raise Exception('Failed to unpack {0} at node {1}'.format(
112             tarball, node['host']))
113
114
115 def create_env_directory_at_node(node):
116     """Create fresh virtualenv to a directory, install pip requirements.
117
118     :param node: Node to create virtualenv on.
119     :type node: dict
120     :returns: nothing
121     :raises Exception: When failed to setup virtualenv.
122     """
123     logger.console('Extracting virtualenv, installing requirements.txt '
124                    'on {0}'.format(node['host']))
125     ssh = SSH()
126     ssh.connect(node)
127     (ret_code, stdout, stderr) = ssh.exec_command(
128         'cd {0} && rm -rf env && '
129         'virtualenv --system-site-packages --never-download env && '
130         '. env/bin/activate && '
131         'pip install -r requirements.txt'
132         .format(con.REMOTE_FW_DIR), timeout=100)
133     if ret_code != 0:
134         logger.error('Virtualenv creation error: {0}'.format(stdout + stderr))
135         raise Exception('Virtualenv setup failed')
136     else:
137         logger.console('Virtualenv created on {0}'.format(node['host']))
138
139
140 # pylint: disable=broad-except
141 def setup_node(args):
142     """Run all set-up methods for a node.
143
144     This method is used as map_async parameter. It receives tuple with all
145     parameters as passed to map_async function.
146
147     :param args: All parameters needed to setup one node.
148     :type args: tuple
149     :returns: True - success, False - error
150     :rtype: bool
151     """
152     tarball, remote_tarball, node = args
153     try:
154         copy_tarball_to_node(tarball, node)
155         extract_tarball_at_node(remote_tarball, node)
156         if node['type'] == NodeType.TG:
157             create_env_directory_at_node(node)
158     except Exception as exc:
159         logger.error("Node {0} setup failed, error:'{1}'".format(node['host'],
160                                                                  exc.message))
161         return False
162     else:
163         logger.console('Setup of node {0} done'.format(node['host']))
164         return True
165
166
167 def delete_local_tarball(tarball):
168     """Delete local tarball to prevent disk pollution.
169
170     :param tarball: Path to tarball to upload.
171     :type tarball: str
172     :returns: nothing
173     """
174     call(split('sh -c "rm {0} > /dev/null 2>&1"'.format(tarball)))
175
176
177 def delete_openvpp_testing_stuff(node):
178     """Delete openvpp-testing directory and tarball in /tmp/ on given node.
179
180     :param node: Node to delete openvpp-testing stuff on.
181     :type node: dict
182     """
183     logger.console('Deleting openvpp-testing directory and tarball on {0}'
184                    .format(node['host']))
185     ssh = SSH()
186     ssh.connect(node)
187     (ret_code, stdout, stderr) = ssh.exec_command(
188         'cd {0} && sudo rm -rf openvpp-testing*'.format(
189             con.REMOTE_FW_DIR), timeout=100)
190     if ret_code != 0:
191         logger.console('Deleting opvenvpp-testing stuff failed on node {0}: {1}'
192                        .format(node, stdout + stderr))
193
194
195 def remove_env_directory_at_node(node):
196     """Remove virtualenv directory on given node.
197
198     :param node: Node to remove virtualenv on.
199     :type node: dict
200     """
201     logger.console('Removing virtualenv directory on {0}'.format(node['host']))
202     ssh = SSH()
203     ssh.connect(node)
204     (ret_code, stdout, stderr) = ssh.exec_command(
205         'cd {0} && sudo rm -rf openvpp-testing*'
206         .format(con.REMOTE_FW_DIR), timeout=100)
207     if ret_code != 0:
208         logger.console('Virtualenv removing failed on node {0}: {1}'.format(
209             node, stdout + stderr))
210
211
212 # pylint: disable=broad-except
213 def cleanup_node(node):
214     """Run all clean-up methods for a node.
215
216     This method is used as map_async parameter. It receives tuple with all
217     parameters as passed to map_async function.
218
219     :param node: Node to do cleanup on.
220     :type node: dict
221     :returns: True - success, False - error
222     :rtype: bool
223     """
224     try:
225         delete_openvpp_testing_stuff(node)
226         if node['type'] == NodeType.TG:
227             remove_env_directory_at_node(node)
228     except Exception as exc:
229         logger.error("Node {0} cleanup failed, error:'{1}'".format(
230             node['host'], exc.message))
231         return False
232     else:
233         logger.console('Cleanup of node {0} done'.format(node['host']))
234         return True
235
236
237 class SetupFramework(object):
238     """Setup suite run on topology nodes.
239
240     Many VAT/CLI based tests need the scripts at remote hosts before executing
241     them. This class packs the whole testing directory and copies it over
242     to all nodes in topology under /tmp/
243     """
244
245     @staticmethod
246     def setup_framework(nodes):
247         """Pack the whole directory and extract in temp on each node.
248
249         :param nodes: Topology nodes.
250         :type nodes: dict
251         """
252
253         tarball = pack_framework_dir()
254         msg = 'Framework packed to {0}'.format(tarball)
255         logger.console(msg)
256         logger.trace(msg)
257         remote_tarball = "/tmp/{0}".format(basename(tarball))
258
259         # Turn off logging since we use multiprocessing
260         log_level = BuiltIn().set_log_level('NONE')
261         params = ((tarball, remote_tarball, node) for node in nodes.values())
262         pool = Pool(processes=len(nodes))
263         result = pool.map_async(setup_node, params)
264         pool.close()
265         pool.join()
266
267         # Turn on logging
268         BuiltIn().set_log_level(log_level)
269
270         logger.info(
271             'Executing node setups in parallel, waiting for processes to end')
272         result.wait()
273
274         logger.info('Results: {0}'.format(result.get()))
275
276         logger.trace('Test framework copied to all topology nodes')
277         delete_local_tarball(tarball)
278         logger.console('All nodes are ready')
279
280
281 class CleanupFramework(object):
282     """Clean up suite run on topology nodes."""
283
284     @staticmethod
285     def cleanup_framework(nodes):
286         """Perform cleaning on each node.
287
288         :param nodes: Topology nodes.
289         :type nodes: dict
290         """
291         # Turn off logging since we use multiprocessing
292         log_level = BuiltIn().set_log_level('NONE')
293         params = (node for node in nodes.values())
294         pool = Pool(processes=len(nodes))
295         result = pool.map_async(cleanup_node, params)
296         pool.close()
297         pool.join()
298
299         # Turn on logging
300         BuiltIn().set_log_level(log_level)
301
302         logger.info(
303             'Executing node cleanups in parallel, waiting for processes to end')
304         result.wait()
305
306         logger.info('Results: {0}'.format(result.get()))
307
308         logger.trace('All topology nodes cleaned up')
309         logger.console('All nodes cleaned up')