diff --git a/crmsh/constants.py b/crmsh/constants.py index 5df974c854..23576d38ab 100644 --- a/crmsh/constants.py +++ b/crmsh/constants.py @@ -230,6 +230,7 @@ "timeout", "timestamp-format" ) trace_ra_attr = "trace_ra" +trace_dir_attr = "trace_dir" score_types = {'advisory': '0', 'mandatory': 'INFINITY'} boolean_ops = ('or', 'and') binary_ops = ('lt', 'gt', 'lte', 'gte', 'eq', 'ne') diff --git a/crmsh/ui_resource.py b/crmsh/ui_resource.py index 0fa167e03e..e79e02a15a 100644 --- a/crmsh/ui_resource.py +++ b/crmsh/ui_resource.py @@ -635,15 +635,17 @@ def _get_trace_rsc(self, rsc_id): return None return rsc - def _add_trace_op(self, rsc, op, interval): + def _add_trace_op(self, rsc, op, interval, dir): from lxml import etree n = etree.Element('op') n.set('name', op) n.set('interval', interval) n.set(constants.trace_ra_attr, '1') + if dir is not None: + n.set(constants.trace_dir_attr, dir) return rsc.add_operation(n) - def _trace_resource(self, context, rsc_id, rsc): + def _trace_resource(self, context, rsc_id, rsc, dir): """Enable RA tracing for a specified resource.""" op_nodes = rsc.node.xpath('operations/op') @@ -651,7 +653,7 @@ def trace(name): for o in op_nodes: if o.get('name') == name: return - if not self._add_trace_op(rsc, name, '0'): + if not self._add_trace_op(rsc, name, '0', dir): context.fatal_error("Failed to add trace for %s:%s" % (rsc_id, name)) trace('start') trace('stop') @@ -659,46 +661,75 @@ def trace(name): trace('promote') trace('demote') for op_node in op_nodes: - rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + op_node = rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + if dir is not None: + rsc.set_op_attr(op_node, constants.trace_dir_attr, dir) - def _trace_op(self, context, rsc_id, rsc, op): + def _trace_op(self, context, rsc_id, rsc, op, dir): """Enable RA tracing for a specified operation.""" op_nodes = rsc.node.xpath('operations/op[@name="%s"]' % (op)) if not op_nodes: if op == 'monitor': context.fatal_error("No monitor operation configured for %s" % (rsc_id)) - if not self._add_trace_op(rsc, op, '0'): + if not self._add_trace_op(rsc, op, '0', dir): context.fatal_error("Failed to add trace for %s:%s" % (rsc_id, op)) for op_node in op_nodes: - rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + op_node = rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + if dir is not None: + rsc.set_op_attr(op_node, constants.trace_dir_attr, dir) - def _trace_op_interval(self, context, rsc_id, rsc, op, interval): + def _trace_op_interval(self, context, rsc_id, rsc, op, interval, dir): """Enable RA tracing for an operation with the exact interval.""" op_node = xmlutil.find_operation(rsc.node, op, interval) if op_node is None and utils.crm_msec(interval) != 0: context.fatal_error("Operation %s with interval %s not found in %s" % (op, interval, rsc_id)) if op_node is None: - if not self._add_trace_op(rsc, op, interval): + if not self._add_trace_op(rsc, op, interval, dir): context.fatal_error("Failed to add trace for %s:%s" % (rsc_id, op)) else: - rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + op_node = rsc.set_op_attr(op_node, constants.trace_ra_attr, "1") + if dir is not None: + rsc.set_op_attr(op_node, constants.trace_dir_attr, dir) @command.completers(compl.primitives, _raoperations) - def do_trace(self, context, rsc_id, op=None, interval=None): - 'usage: trace [] []' + def do_trace(self, context, rsc_id, *args): + 'usage: trace [] [] []' + usage='usage: trace [] [] []' rsc = self._get_trace_rsc(rsc_id) if not rsc: return False + + argl = list(args) + force = "force" in utils.fetch_opts(argl, ["force"]) or config.core.force + if len(argl) > 3: + context.fatal_error(usage) + op=None + interval=None + dir=None + for arg in argl: + if arg[0] == '/': + if dir is not None: + context.fatal_error(usage) + dir = arg + elif arg.isnumeric(): + if interval is not None: + context.fatal_error(usage) + interval = arg + else: + if op is not None: + context.fatal_error(usage) + op = arg + if op == "probe": op = "monitor" if interval is None: interval = "0" if op is None: - self._trace_resource(context, rsc_id, rsc) + self._trace_resource(context, rsc_id, rsc, dir) elif interval is None: - self._trace_op(context, rsc_id, rsc, op) + self._trace_op(context, rsc_id, rsc, op, dir) else: - self._trace_op_interval(context, rsc_id, rsc, op, interval) + self._trace_op_interval(context, rsc_id, rsc, op, interval, dir) if not cib_factory.commit(): return False rsc_type = rsc.node.get("type") @@ -712,6 +743,7 @@ def do_trace(self, context, rsc_id, op=None, interval=None): def _remove_trace(self, rsc, op_node): logger.debug("op_node: %s", xmlutil.xml_tostring(op_node)) op_node = rsc.del_op_attr(op_node, constants.trace_ra_attr) + op_node = rsc.del_op_attr(op_node, constants.trace_dir_attr) if rsc.is_dummy_operation(op_node): rsc.del_operation(op_node) diff --git a/doc/crm.8.adoc b/doc/crm.8.adoc index d8d82ff0e7..6ea455adb4 100644 --- a/doc/crm.8.adoc +++ b/doc/crm.8.adoc @@ -1963,10 +1963,10 @@ stop [ ...] [[cmdhelp_resource_trace,start RA tracing]] ==== `trace` -Start tracing RA for the given operation. The trace files are -stored in `$HA_VARLIB/trace_ra`. If the operation to be traced is -monitor, note that the number of trace files can grow very -quickly. +Start tracing RA for the given operation. When `[]` +is not specified the trace files are stored in `$HA_VARLIB/trace_ra`. +If the operation to be traced is monitor, note that the number +of trace files can grow very quickly. If no operation name is given, crmsh will attempt to trace all operations for the RA. This includes any configured operations, start @@ -1982,14 +1982,14 @@ unless an error occurred. Usage: ............... -trace [ [] ] +trace [ [] []] ............... Example: ............... trace fs start trace webserver trace webserver probe -trace fs monitor 0 +trace fs monitor 0 /var/log/foo/bar ............... [[cmdhelp_resource_unmanage,put a resource into unmanaged mode]] diff --git a/test/unittests/test_ratrace.py b/test/unittests/test_ratrace.py index 90e3ae7759..6734b89ef3 100644 --- a/test/unittests/test_ratrace.py +++ b/test/unittests/test_ratrace.py @@ -29,7 +29,7 @@ def test_ratrace_resource(self, mock_error): obj = self.factory.create_from_node(etree.fromstring(xml)) # Trace the resource. - RscMgmt()._trace_resource(self.context, obj.obj_id, obj) + RscMgmt()._trace_resource(self.context, obj.obj_id, obj, '/var/lib/heartbeat/trace_ra') self.assertEqual(obj.node.xpath('operations/op/@id'), ['r1-start-0', 'r1-stop-0']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-start-0"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-stop-0"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) @@ -50,7 +50,7 @@ def test_ratrace_op(self, mock_error): obj = self.factory.create_from_node(etree.fromstring(xml)) # Trace the operation. - RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor') + RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor', '/var/lib/heartbeat/trace_ra') self.assertEqual(obj.node.xpath('operations/op/@id'), ['r1-monitor-10']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-monitor-10"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) @@ -73,14 +73,14 @@ def test_ratrace_new(self, mock_error): # Trace a regular operation that is not yet defined in CIB. The request # should succeed and introduce an op node for the operation. - RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'start') + RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'start', '/var/lib/heartbeat/trace_ra') self.assertEqual(obj.node.xpath('operations/op/@id'), ['r1-start-0']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-start-0"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) # Try tracing the monitor operation in the same way. The request should # get rejected because no explicit interval is specified. with self.assertRaises(ValueError) as err: - RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor') + RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor', '/var/lib/heartbeat/trace_ra') self.assertEqual(str(err.exception), "No monitor operation configured for r1") @mock.patch('logging.Logger.error') @@ -95,7 +95,7 @@ def test_ratrace_op_stateful(self, mock_error): obj = self.factory.create_from_node(etree.fromstring(xml)) # Trace the operation. - RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor') + RscMgmt()._trace_op(self.context, obj.obj_id, obj, 'monitor', '/var/lib/heartbeat/trace_ra') self.assertEqual(obj.node.xpath('operations/op/@id'), ['r1-monitor-10', 'r1-monitor-11']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-monitor-10"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-monitor-11"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1']) @@ -116,7 +116,7 @@ def test_ratrace_op_interval(self, mock_error): obj = self.factory.create_from_node(etree.fromstring(xml)) # Trace the operation. - RscMgmt()._trace_op_interval(self.context, obj.obj_id, obj, 'monitor', '10') + RscMgmt()._trace_op_interval(self.context, obj.obj_id, obj, 'monitor', '10', '/var/lib/heartbeat/trace_ra') self.assertEqual(obj.node.xpath('operations/op/@id'), ['r1-monitor-10']) self.assertEqual(obj.node.xpath('operations/op[@id="r1-monitor-10"]/instance_attributes/nvpair[@name="trace_ra"]/@value'), ['1'])