CSIT-845 Capture VPP core-dump from vpp crash on DUTs
[csit.git] / resources / libraries / python / CoreDumpUtil.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 """Core dump library."""
15
16 from time import time
17
18 from resources.libraries.python.constants import Constants
19 from resources.libraries.python.DUTSetup import DUTSetup
20 from resources.libraries.python.LimitUtil import LimitUtil
21 from resources.libraries.python.SysctlUtil import SysctlUtil
22 from resources.libraries.python.ssh import exec_cmd_no_error, scp_node
23 from resources.libraries.python.topology import NodeType
24
25 __all__ = ["CoreDumpUtil"]
26
27
28 class CoreDumpUtil(object):
29     """Class contains methods for processing core dumps."""
30
31     # Use one instance of class for all tests. If the functionality should
32     # be enabled per suite or per test case, change the scope to "TEST SUITE" or
33     # "TEST CASE" respectively.
34     ROBOT_LIBRARY_SCOPE = 'GLOBAL'
35
36     def __init__(self):
37         """Initialize CoreDumpUtil class."""
38         # Corekeeper is configured.
39         self._corekeeper_configured = False
40         # Enable setting core limit for process. This can be used to prevent
41         # library to further set the core limit for unwanted behavior.
42         self._core_limit_enabled = True
43
44     def set_core_limit_enabled(self):
45         """Enable setting of core limit for PID."""
46         self._core_limit_enabled = True
47
48     def set_core_limit_disabled(self):
49         """Disable setting of core limit for PID."""
50         self._core_limit_enabled = False
51
52     def is_core_limit_enabled(self):
53         """Check if core limit is set for process.
54
55         :returns: True if core limit is set for process.
56         :rtype: bool
57         """
58         return self._corekeeper_configured and self._core_limit_enabled
59
60     def setup_corekeeper_on_all_nodes(self, nodes):
61         """Setup core dumps system wide on all nodes.
62
63         :param nodes: Nodes in the topology.
64         :type nodes: dict
65         """
66         for node in nodes.values():
67             # Any binary which normally would not be dumped is dumped anyway,
68             # but only if the "core_pattern" kernel sysctl is set to either a
69             # pipe handler or a fully qualified path. (For more details on this
70             # limitation, see CVE-2006-2451.) This mode is appropriate when
71             # administrators are attempting to debug problems in a normal
72             # environment, and either have a core dump pipe handler that knows
73             # to treat privileged core dumps with care, or specific directory
74             # defined for catching core dumps. If a core dump happens without a
75             # pipe handler or fully qualifid path, a message will be emitted to
76             # syslog warning about the lack of a correct setting.
77             SysctlUtil.set_sysctl_value(node, 'fs.suid_dumpable', 2)
78
79             # Specify a core dumpfile pattern name (for the output filename).
80             # %p    pid
81             # %u    uid (in initial user namespace)
82             # %g    gid (in initial user namespace)
83             # %s    signal number
84             # %t    UNIX time of dump
85             # %h    hostname
86             # %e    executable filename (may be shortened)
87             SysctlUtil.set_sysctl_value(node, 'kernel.core_pattern',
88                                         Constants.KERNEL_CORE_PATTERN)
89
90         self._corekeeper_configured = True
91
92     @staticmethod
93     def enable_coredump_limit(node, pid):
94         """Enable coredump for PID(s) by setting no core limits.
95
96         :param node: Node in the topology.
97         :param pid: Process ID(s) to set core dump limit to unlimited.
98         :type node: dict
99         :type pid: list or int
100         """
101         if isinstance(pid, list):
102             for item in pid:
103                 LimitUtil.set_pid_limit(node, item, 'core', 'unlimited')
104                 LimitUtil.get_pid_limit(node, item)
105         else:
106             LimitUtil.set_pid_limit(node, pid, 'core', 'unlimited')
107             LimitUtil.get_pid_limit(node, pid)
108
109     def enable_coredump_limit_vpp_on_all_duts(self, nodes):
110         """Enable coredump for all VPP PIDs by setting no core limits on all
111         DUTs if setting of core limit by this library is enabled.
112
113         :param nodes: Nodes in the topology.
114         :type nodes: dict
115         """
116         for node in nodes.values():
117             if node['type'] == NodeType.DUT and self.is_core_limit_enabled():
118                 vpp_pid = DUTSetup.get_vpp_pid(node)
119                 self.enable_coredump_limit(node, vpp_pid)
120
121     def get_core_files_on_all_nodes(self, nodes, disable_on_success=True):
122         """Compress all core files into single file and remove the original
123         core files on all nodes.
124
125         :param nodes: Nodes in the topology.
126         :param disable_on_success: If True, disable setting of core limit by
127             this instance of library. Default: True
128         :type nodes: dict
129         :type disable_on_success: bool
130         """
131         for node in nodes.values():
132             uuid = str(time()).replace('.', '')
133             name = '{uuid}.tar.lzo.lrz.xz'.format(uuid=uuid)
134
135             command = ('[ -e {dir}/*.core ] && sudo tar c {dir}/*.core | '
136                        'lzop -1 | '
137                        'lrzip -n -T -p 1 -w 5 | '
138                        'xz -9e > {dir}/{name} && '
139                        'sudo rm -f {dir}/*.core'
140                        .format(dir=Constants.CORE_DUMP_DIR, name=name))
141             try:
142                 exec_cmd_no_error(node, command, timeout=3600)
143                 if disable_on_success:
144                     self.set_core_limit_disabled()
145             except RuntimeError:
146                 # If compress was not sucessfull ignore error and skip further
147                 # processing.
148                 continue
149
150             local_path = 'archive/{name}'.format(name=name)
151             remote_path = '{dir}/{name}'.format(dir=Constants.CORE_DUMP_DIR,
152                                                 name=name)
153             try:
154                 scp_node(node, local_path, remote_path, get=True, timeout=3600)
155                 command = 'rm -f {dir}/{name}'\
156                            .format(dir=Constants.CORE_DUMP_DIR, name=name)
157                 exec_cmd_no_error(node, command, sudo=True)
158             except RuntimeError:
159                 pass