aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/tracedump/graph_test.c
blob: 79e1df61c5f84bcd3aa0dffe73cf187dee7fb9e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
 * Copyright (c) 2020 cisco
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <vat/vat.h>
#include <vlibapi/api.h>
#include <vlibmemory/api.h>
#include <vppinfra/error.h>
#include <vppinfra/time_range.h>
#include <vnet/ethernet/ethernet.h>
#include <vpp-api/client/stat_client.h>

#define __plugin_msg_base graph_test_main.msg_id_base
#include <vlibapi/vat_helper_macros.h>

#include <vnet/format_fns.h>
#include <tracedump/graph.api_enum.h>
#include <tracedump/graph.api_types.h>
#include <vpp/api/vpe.api_types.h>

typedef struct
{
  u16 msg_id_base;
  u32 ping_id;
  vat_main_t *vat_main;
} graph_test_main_t;

graph_test_main_t graph_test_main;


uword
api_unformat_node_index (unformat_input_t * input, va_list * args)
{
  u32 *result = va_arg (*args, u32 *);

  return unformat (input, "%u", result);
}


static void
vl_api_graph_node_get_reply_t_handler (vl_api_graph_node_get_reply_t * mp)
{
  vat_main_t *vam = &vat_main;

  clib_warning ("Next node index: %u\n", mp->cursor);
  vam->result_ready = 1;
}

int
api_graph_node_get (vat_main_t * vam)
{
  graph_test_main_t *gtm = &graph_test_main;
  unformat_input_t *i = vam->input;
  vl_api_graph_node_get_t *mp;
  vl_api_control_ping_t *mp_ping;
  u32 node_index;
  char *node_name;
  u32 flags;
  bool want_arcs;

  if (vam->json_output)
    {
      clib_warning ("JSON output not supported for graph_node_get");
      return -99;
    }

  node_index = ~0;
  node_name = 0;
  flags = 0;
  want_arcs = false;

  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (i, "node_index %u", &node_index))
	;
      else if (unformat (i, "node_name %s", &node_name))
	;
      else if (unformat (i, "want_arcs"))
	want_arcs = true;
      else if (unformat (i, "trace_supported"))
	flags |= NODE_FLAG_TRACE_SUPPORTED;
      else if (unformat (i, "input"))
	flags |= NODE_FLAG_TRACE_SUPPORTED;
      else if (unformat (i, "drop"))
	flags |= NODE_FLAG_IS_DROP;
      else if (unformat (i, "ouptput"))
	flags |= NODE_FLAG_IS_OUTPUT;
      else if (unformat (i, "punt"))
	flags |= NODE_FLAG_IS_PUNT;
      else if (unformat (i, "handoff"))
	flags |= NODE_FLAG_IS_HANDOFF;
      else if (unformat (i, "no_free"))
	flags |= NODE_FLAG_FRAME_NO_FREE_AFTER_DISPATCH;
      else if (unformat (i, "polling"))
	flags |= NODE_FLAG_SWITCH_FROM_INTERRUPT_TO_POLLING_MODE;
      else if (unformat (i, "interrupt"))
	flags |= NODE_FLAG_SWITCH_FROM_POLLING_TO_INTERRUPT_MODE;
      else
	{
	  clib_warning ("Unknown input: %U\n", format_unformat_error, i);
	  return -99;
	}
    }

  M (GRAPH_NODE_GET, mp);
  mp->index = htonl (node_index);
  mp->flags = htonl (flags);
  mp->want_arcs = want_arcs;

  if (node_name && node_name[0])
    clib_strncpy ((char *) mp->name, node_name, sizeof (mp->name) - 1);

  int ret = 0;
  S (mp);

  if (!gtm->ping_id)
    gtm->ping_id =
      vl_msg_api_get_msg_index ((u8 *) (VL_API_CONTROL_PING_CRC));

  mp_ping = vl_msg_api_alloc_as_if_client (sizeof (*mp_ping));
  mp_ping->_vl_msg_id = htons (gtm->ping_id);
  mp_ping->client_index = vam->my_client_index;

  S (mp_ping);
  W (ret);

  return ret;
}

void
vl_api_graph_node_details_t_handler (vl_api_graph_node_details_t * mp)
{
  vat_main_t *vam = &vat_main;
  u32 n_arcs;
  int i;

  fformat (vam->ofp,
	   "Node: %s  Index:%d  Flags:0x%x\n",
	   mp->name, ntohl (mp->index), ntohl (mp->flags));

  n_arcs = ntohl (mp->n_arcs);
  for (i = 0; i < n_arcs; ++i)
    {
      u32 node_index = ntohl (mp->arcs_out[i]);
      fformat (vam->ofp, "    next: %d\n", node_index);
    }
}

void
vl_api_graph_node_details_t_handler_json (vl_api_graph_node_details_t * mp)
{
  clib_error ("graph_node_details JSON not supported");
}

/* Override generated plugin register symbol */
#define vat_plugin_register graph_test_vat_plugin_register
#include <tracedump/graph.api_test.c>

static clib_error_t *
graph_api_hookup_shim (vlib_main_t * vm)
{
  graph_test_vat_plugin_register (&vat_main);
  return 0;
}

VLIB_API_INIT_FUNCTION (graph_api_hookup_shim);

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
s.REMOTE_FW_DIR}/" \ f"{Constants.RESOURCES_TPL_VCL}/" \ f"{iperf3_attributes[u'vcl_config']}" if iperf3_attributes[u"ld_preload"]: iperf3_cmd[u"env_vars"] += \ f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}" if iperf3_attributes[u'transparent_tls']: iperf3_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1" json_results = u" --json" if iperf3_attributes[u'json'] else u"" ip_address = f" {iperf3_attributes[u'ip_address']}" if u"ip_address" \ in iperf3_attributes else u"" iperf3_cmd[u"name"] = u"iperf3" iperf3_cmd[u"args"] = f"--{iperf3_attributes[u'role']}{ip_address} " \ f"--interval 0{json_results} " \ f"--version{iperf3_attributes[u'ip_version']}" if iperf3_attributes[u"role"] == u"server": iperf3_cmd[u"args"] += u" --one-off" else: iperf3_cmd[u"args"] += u" --get-server-output" if u"parallel" in iperf3_attributes: iperf3_cmd[u"args"] += \ f" --parallel {iperf3_attributes[u'parallel']}" if u"time" in iperf3_attributes: iperf3_cmd[u"args"] += \ f" --time {iperf3_attributes[u'time']}" return iperf3_cmd @staticmethod def set_hoststack_quic_fifo_size(node, fifo_size): """Set the QUIC protocol fifo size. :param node: Node to set the QUIC fifo size on. :param fifo_size: fifo size, passed to the quic set fifo-size command. :type node: dict :type fifo_size: str """ cmd = f"quic set fifo-size {fifo_size}" PapiSocketExecutor.run_cli_cmd(node, cmd) @staticmethod def set_hoststack_quic_crypto_engine(node, quic_crypto_engine, fail_on_error=False): """Set the Hoststack QUIC crypto engine on node :param node: Node to enable/disable HostStack. :param quic_crypto_engine: type of crypto engine :type node: dict :type quic_crypto_engine: str """ vpp_crypto_engines = {u"openssl", u"native", u"ipsecmb"} if quic_crypto_engine == u"nocrypto": logger.trace(u"No QUIC crypto engine.") return if quic_crypto_engine in vpp_crypto_engines: cmds = [u"quic set crypto api vpp", f"set crypto handler aes-128-gcm {quic_crypto_engine}", f"set crypto handler aes-256-gcm {quic_crypto_engine}"] elif quic_crypto_engine == u"picotls": cmds = [u"quic set crypto api picotls"] else: raise ValueError(f"Unknown QUIC crypto_engine {quic_crypto_engine}") for cmd in cmds: try: PapiSocketExecutor.run_cli_cmd(node, cmd) except AssertionError: if fail_on_error: raise @staticmethod def get_hoststack_test_program_logs(node, program): """Get HostStack test program stdout log. :param node: DUT node. :param program: test program. :type node: dict :type program: dict """ program_name = program[u"name"] cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'" stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \ message=f"Get {program_name} stdout log failed!") cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'" stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \ message=f"Get {program_name} stderr log failed!") return stdout_log, stderr_log @staticmethod def start_hoststack_test_program(node, namespace, core_list, program): """Start the specified HostStack test program. :param node: DUT node. :param namespace: Net Namespace to run program in. :param core_list: List of cpu's to pass to taskset to pin the test program to a different set of cores on the same numa node as VPP. :param program: Test program. :type node: dict :type namespace: str :type core_list: str :type program: dict :returns: Process ID :rtype: int :raises RuntimeError: If node subtype is not a DUT or startup failed. """ if node[u"type"] != u"DUT": raise RuntimeError(u"Node type is not a DUT!") program_name = program[u"name"] DUTSetup.kill_program(node, program_name, namespace) if namespace == u"default": shell_cmd = u"sh -c" else: shell_cmd = f"ip netns exec {namespace} sh -c" env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u"" args = program[u"args"] cmd = f"nohup {shell_cmd} \'{env_vars}taskset --cpu-list {core_list} " \ f"{program_name} {args} >/tmp/{program_name}_stdout.log " \ f"2>/tmp/{program_name}_stderr.log &\'" try: exec_cmd_no_error(node, cmd, sudo=True) return DUTSetup.get_pid(node, program_name)[0] except RuntimeError: stdout_log, stderr_log = \ HoststackUtil.get_hoststack_test_program_logs(node, program) raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \ f"{stderr_log}\nSTDOUT:\n{stdout_log}") return None @staticmethod def stop_hoststack_test_program(node, program, pid): """Stop the specified Hoststack test program. :param node: DUT node. :param program: Test program. :param pid: Process ID of test program. :type node: dict :type program: dict :type pid: int """ program_name = program[u"name"] if program_name == u"nginx": cmd = u"nginx -s quit" errmsg = u"Quit nginx failed!" else: cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \ f'then kill -s SIGTERM {pid}; fi' errmsg = f"Kill {program_name} ({pid}) failed!" exec_cmd_no_error(node, cmd, message=errmsg, sudo=True) @staticmethod def hoststack_test_program_finished(node, program_pid): """Wait for the specified HostStack test program process to complete. :param node: DUT node. :param program_pid: test program pid. :type node: dict :type program_pid: str :raises RuntimeError: If node subtype is not a DUT. """ if node[u"type"] != u"DUT": raise RuntimeError(u"Node type is not a DUT!") cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'" exec_cmd(node, cmd, sudo=True) # Wait a bit for stdout/stderr to be flushed to log files # TODO: see if sub-second sleep works e.g. sleep(0.1) sleep(1) @staticmethod def analyze_hoststack_test_program_output( node, role, nsim_attr, program): """Gather HostStack test program output and check for errors. The [defer_fail] return bool is used instead of failing immediately to allow the analysis of both the client and server instances of the test program for debugging a test failure. When [defer_fail] is true, then the string returned is debug output instead of JSON formatted test program results. :param node: DUT node. :param role: Role (client|server) of test program. :param nsim_attr: Network Simulation Attributes. :param program: Test program. :param program_args: List of test program args. :type node: dict :type role: str :type nsim_attr: dict :type program: dict :returns: tuple of [defer_fail] bool and either JSON formatted hoststack test program output or failure debug output. :rtype: bool, str :raises RuntimeError: If node subtype is not a DUT. """ if node[u"type"] != u"DUT": raise RuntimeError(u"Node type is not a DUT!") program_name = program[u"name"] program_stdout, program_stderr = \ HoststackUtil.get_hoststack_test_program_logs(node, program) if len(program_stdout) == 0 and len(program_stderr) == 0: logger.trace(f"Retrying {program_name} log retrieval") program_stdout, program_stderr = \ HoststackUtil.get_hoststack_test_program_logs(node, program) env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u"" program_cmd = f"{env_vars}{program_name} {program[u'args']}" test_results = f"Test Results of '{program_cmd}':\n" if nsim_attr[u"output_nsim_enable"] or \ nsim_attr[u"xc_nsim_enable"]: if nsim_attr[u"output_nsim_enable"]: feature_name = u"output" else: feature_name = u"cross-connect" test_results += \ f"NSIM({feature_name}): delay " \ f"{nsim_attr[u'delay_in_usec']} usecs, " \ f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \ f"bandwidth {nsim_attr[u'bw_in_bits_per_second']} " \ f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \ f"pkts/drop\n" # TODO: Incorporate show error stats into results analysis test_results += \ f"\n{role} VPP 'show errors' on host {node[u'host']}:\n" \ f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n" if u"error" in program_stderr.lower(): test_results += f"ERROR DETECTED:\n{program_stderr}" return (True, test_results) if not program_stdout: test_results += f"\nNo {program} test data retrieved!\n" ls_stdout, _ = exec_cmd_no_error(node, u"ls -l /tmp/*.log", sudo=True) test_results += f"{ls_stdout}\n" return (True, test_results) if program[u"name"] == u"vpp_echo": if u"JSON stats" in program_stdout and \ u'"has_failed": "0"' in program_stdout: json_start = program_stdout.find(u"{") #TODO: Fix parsing once vpp_echo produces valid # JSON output. Truncate for now. json_end = program_stdout.find(u',\n "closing"') json_results = f"{program_stdout[json_start:json_end]}\n}}" program_json = json.loads(json_results) else: test_results += u"Invalid test data output!\n" + program_stdout return (True, test_results) elif program[u"name"] == u"iperf3": test_results += program_stdout iperf3_json = json.loads(program_stdout) program_json = iperf3_json[u"intervals"][0][u"sum"] else: test_results += u"Unknown HostStack Test Program!\n" + \ program_stdout return (True, program_stdout) return (False, json.dumps(program_json)) @staticmethod def hoststack_test_program_defer_fail(server_defer_fail, client_defer_fail): """Return True if either HostStack test program fail was deferred. :param server_defer_fail: server no results value. :param client_defer_fail: client no results value. :type server_defer_fail: bool :type client_defer_fail: bool :rtype: bool """ return server_defer_fail and client_defer_fail