Fix various pylint 1.5.4 warnings
[csit.git] / resources / libraries / python / DPDK / SetupDPDKTest.py
1 # Copyright (c) 2018 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
25 from robot.api import logger
26 from robot.libraries.BuiltIn import BuiltIn
27
28 from resources.libraries.python.ssh import SSH
29 from resources.libraries.python.constants import Constants as con
30 from resources.libraries.python.topology import NodeType
31 from resources.libraries.python.topology import Topology
32
33 __all__ = ["SetupDPDKTest"]
34
35
36 def pack_framework_dir():
37     """Pack the testing WS into temp file, return its name.
38
39     :raise RuntimeError: If command returns nonzero return code."""
40
41     tmpfile = NamedTemporaryFile(suffix=".tgz", prefix="DPDK-testing-")
42     file_name = tmpfile.name
43     tmpfile.close()
44
45     proc = Popen(
46         split("tar --exclude-vcs -zcf {0} .".format(file_name)),
47         stdout=PIPE, stderr=PIPE)
48     (stdout, stderr) = proc.communicate()
49
50     logger.debug(stdout)
51     logger.debug(stderr)
52
53     return_code = proc.wait()
54     if return_code != 0:
55         raise RuntimeError("Could not pack testing framework.")
56
57     return file_name
58
59
60 def copy_tarball_to_node(tarball, node):
61     """Copy tarball file from local host to remote node.
62
63     :param tarball: Path to tarball to upload.
64     :param node: Dictionary created from topology.
65     :type tarball: str
66     :type node: dict
67     :returns: nothing
68     """
69     logger.console('Copying tarball to {0}'.format(node['host']))
70     ssh = SSH()
71     ssh.connect(node)
72
73     ssh.scp(tarball, "/tmp/")
74
75
76 def extract_tarball_at_node(tarball, node):
77     """Extract tarball at given node.
78
79     Extracts tarball using tar on given node to specific CSIT loocation.
80
81     :param tarball: Path to tarball to upload.
82     :param node: Dictionary created from topology.
83     :type tarball: str
84     :type node: dict
85     :returns: nothing
86     :raise RuntimeError: If command returns nonzero return code.
87     """
88     logger.console('Extracting tarball to {0} on {1}'.format(
89         con.REMOTE_FW_DIR, node['host']))
90     ssh = SSH()
91     ssh.connect(node)
92
93     cmd = 'sudo rm -rf {1}; mkdir {1} ; tar -zxf {0} -C {1}; ' \
94         'rm -f {0}'.format(tarball, con.REMOTE_FW_DIR)
95     (ret_code, _, stderr) = ssh.exec_command(cmd, timeout=30)
96     if ret_code != 0:
97         logger.error('Unpack error: {0}'.format(stderr))
98         raise RuntimeError('Failed to unpack {0} at node {1}'.format(
99             tarball, node['host']))
100
101
102 def create_env_directory_at_node(node):
103     """
104     Create fresh virtualenv to a directory, install pip requirements.
105
106     :param node: Dictionary created from topology, will only install in the TG
107     :type node: dict
108     :returns: nothing
109     :raise RuntimeError: If command returns nonzero return code.
110     """
111     logger.console('Extracting virtualenv, installing requirements.txt '
112                    'on {0}'.format(node['host']))
113     ssh = SSH()
114     ssh.connect(node)
115     (ret_code, stdout, stderr) = ssh.exec_command(
116         'cd {0} && rm -rf env && virtualenv env && '
117         '. env/bin/activate && pip install -r requirements.txt'
118         .format(con.REMOTE_FW_DIR), timeout=100)
119     if ret_code != 0:
120         logger.error('Virtualenv creation error: {0}'.format(stdout + stderr))
121         raise RuntimeError('Virtualenv setup failed')
122     else:
123         logger.console('Virtualenv created on {0}'.format(node['host']))
124
125 def install_dpdk_test(node):
126     """
127     Prepare the DPDK test envrionment
128
129     :param node: Dictionary created from topology
130     :type node: dict
131     :returns: nothing
132     :raise RuntimeError: If command returns nonzero return code.
133     """
134     arch = Topology.get_node_arch(node)
135     logger.console('Install the DPDK on {0} ({1})'.format(node['host'],
136                                                           arch))
137
138     ssh = SSH()
139     ssh.connect(node)
140
141     (ret_code, _, stderr) = ssh.exec_command(
142         'cd {0}/tests/dpdk/dpdk_scripts/ && ./install_dpdk.sh {1}'
143         .format(con.REMOTE_FW_DIR, arch), timeout=600)
144
145     if ret_code != 0:
146         logger.error('Install the DPDK error: {0}'.format(stderr))
147         raise RuntimeError('Install the DPDK failed')
148     else:
149         logger.console('Install the DPDK on {0} success!'.format(node['host']))
150
151 def setup_node(args):
152     """Run all set-up methods for a node.
153
154     This method is used as map_async parameter. It receives tuple with all
155     parameters as passed to map_async function.
156
157     :param args: All parameters needed to setup one node.
158     :type args: tuple
159     :returns: True - success, False - error
160     :rtype: bool
161     """
162     tarball, remote_tarball, node = args
163
164     # if unset, arch defaults to x86_64
165     if 'arch' not in node or not node['arch']:
166         node['arch'] = 'x86_64'
167
168     try:
169         copy_tarball_to_node(tarball, node)
170         extract_tarball_at_node(remote_tarball, node)
171         if node['type'] == NodeType.DUT:
172             install_dpdk_test(node)
173         if node['type'] == NodeType.TG:
174             create_env_directory_at_node(node)
175     except RuntimeError as exc:
176         logger.error("Node setup failed, error:'{0}'".format(exc.message))
177         return False
178     else:
179         logger.console('Setup of node {0} done'.format(node['host']))
180         return True
181
182 def delete_local_tarball(tarball):
183     """Delete local tarball to prevent disk pollution.
184
185     :param tarball: Path to tarball to upload.
186     :type tarball: str
187     :returns: nothing
188     """
189     call(split('sh -c "rm {0} > /dev/null 2>&1"'.format(tarball)))
190
191
192 class SetupDPDKTest(object):
193     """Setup suite run on topology nodes.
194
195     Many VAT/CLI based tests need the scripts at remote hosts before executing
196     them. This class packs the whole testing directory and copies it over
197     to all nodes in topology under /tmp/
198     """
199
200     @staticmethod
201     def setup_dpdk_test(nodes):
202         """Pack the whole directory and extract in temp on each node."""
203
204         tarball = pack_framework_dir()
205         msg = 'Framework packed to {0}'.format(tarball)
206         logger.console(msg)
207         logger.trace(msg)
208         remote_tarball = "/tmp/{0}".format(basename(tarball))
209
210         # Turn off logging since we use multiprocessing
211         log_level = BuiltIn().set_log_level('NONE')
212         params = ((tarball, remote_tarball, node) for node in nodes.values())
213         pool = Pool(processes=len(nodes))
214         result = pool.map_async(setup_node, params)
215         pool.close()
216         pool.join()
217
218         # Turn on logging
219         BuiltIn().set_log_level(log_level)
220
221         logger.info(
222             'Executed node setups in parallel, waiting for processes to end')
223         result.wait()
224
225         results = result.get()
226         node_setup_success = all(results)
227         logger.info('Results: {0}'.format(results))
228
229         logger.trace('Test framework copied to all topology nodes')
230         delete_local_tarball(tarball)
231         if node_setup_success:
232             logger.console('All nodes are ready')
233         else:
234             logger.console('Failed to setup dpdk on all the nodes')