diff --git a/.github/buildomat/common.sh b/.github/buildomat/common.sh index b02113c2..322ddcd2 100644 --- a/.github/buildomat/common.sh +++ b/.github/buildomat/common.sh @@ -11,9 +11,9 @@ TOFINO_STAGES=20 # These describe which version of the SDE to download and where to find it -SDE_COMMIT=e61fe02c3c1c384b2e212c90177fcea76a31fd4e -SDE_PKG_SHA256=8a87a9b0bed3c5440a173a7a41361bdeb5e7a848882da6b4aa48c8fb0043f3bd -SDE_DEB_SHA256=a292e2dd5311647c4852bb41f2532dd1fdf30325b6d04cccb7e85b873e521d5f +SDE_COMMIT=53519b8cf74fe832cc7838ea92683564ce4026f2 +SDE_PKG_SHA256=ed783a1e7c8d59c392e8cc89114fb0d495b5475373b762068a719e0fb215f5a0 +SDE_DEB_SHA256=90a18b65a6c65f4d15d5f75a00e42ae55a27ffaff2066061aa95feefbe85e163 [ `uname -s` == "SunOS" ] && SERIES=illumos [ `uname -s` == "SunOS" ] || SERIES=linux diff --git a/.github/buildomat/packet-test-common.sh b/.github/buildomat/packet-test-common.sh index bf6b2b47..e8d81cd6 100755 --- a/.github/buildomat/packet-test-common.sh +++ b/.github/buildomat/packet-test-common.sh @@ -53,7 +53,8 @@ if [[ $JUST_TEST -ne 1 ]]; then libssl-dev \ pkg-config \ libcli-dev \ - sysvbanner + sysvbanner \ + libboost-all-dev fi export SDE=/opt/oxide/tofino_sde diff --git a/.helix/languages.toml b/.helix/languages.toml index 83ce5261..d831603b 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -4,4 +4,4 @@ name = "rust" # default to tofino_sde, can change this to any other feature such as softnpu # as needed during development but in source control this should probably # remain as tofino_sde -features = [ "tofino_sde" ] +features = [ "tofino_asic" ] diff --git a/Cargo.lock b/Cargo.lock index 22fcefcf..bd30b2a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6993,6 +6993,7 @@ dependencies = [ "futures", "oxide-tokio-rt", "oxnet", + "reqwest 0.13.2", "slog", "tabwriter", "tokio", diff --git a/aal/src/lib.rs b/aal/src/lib.rs index d871e768..010d3be7 100644 --- a/aal/src/lib.rs +++ b/aal/src/lib.rs @@ -285,6 +285,7 @@ pub trait TableOps { fn get_entries( &self, hdl: &H, + from_hardware: bool, ) -> AsicResult>; fn get_counters( &self, diff --git a/asic/build.rs b/asic/build.rs index b75a0bce..01212d48 100644 --- a/asic/build.rs +++ b/asic/build.rs @@ -37,6 +37,7 @@ fn gen_bindings(sde_dir: &str) -> Result<()> { format!("{sde_includes}/bf_rt/bf_rt_session.h"), format!("{sde_includes}/lld/lld_sku.h"), format!("{sde_includes}/tofino/bf_pal/bf_pal_port_intf.h"), + format!("{sde_includes}/pipe_mgr/pipe_mgr_intf.h"), ]; // There are several headers we only use (and _can_ only use) when running diff --git a/asic/src/chaos/table.rs b/asic/src/chaos/table.rs index 20bf8bcb..1cb7030d 100644 --- a/asic/src/chaos/table.rs +++ b/asic/src/chaos/table.rs @@ -137,6 +137,7 @@ impl TableOps for Table { fn get_entries( &self, _hdl: &Handle, + _from_hardware: bool, ) -> AsicResult> { Err(AsicError::OperationUnsupported) } diff --git a/asic/src/softnpu/table.rs b/asic/src/softnpu/table.rs index 68f9975d..a356afee 100644 --- a/asic/src/softnpu/table.rs +++ b/asic/src/softnpu/table.rs @@ -616,9 +616,11 @@ impl TableOps for Table { fn get_entries( &self, _hdl: &Handle, + _from_hardware: bool, ) -> AsicResult> { Err(aal::AsicError::OperationUnsupported) } + fn get_counters( &self, _hdl: &Handle, diff --git a/asic/src/tofino_asic/c/bf_wrapper.h b/asic/src/tofino_asic/c/bf_wrapper.h index c3fcd444..7d532375 100644 --- a/asic/src/tofino_asic/c/bf_wrapper.h +++ b/asic/src/tofino_asic/c/bf_wrapper.h @@ -43,4 +43,46 @@ struct driver_version { uint32_t patch; }; +bf_status_t +lld_enable_all_ints( + bf_dev_id_t dev_id, + bf_subdev_id_t subdev_id +); + +bf_status_t +lld_dump_new_ints( + bf_dev_id_t dev_id, + bf_subdev_id_t subdev_id +); + +bf_status_t +lld_int_poll( + bf_dev_id_t dev_id, + bf_subdev_id_t subdev_id, + bool all_ints +); + +bf_status_t +bf_err_interrupt_handling_mode_set( + bf_dev_id_t dev_id, + bool enable +); + +pipe_status_t +pipe_mgr_tcam_scrub_timer_set( + bf_dev_id_t dev, + uint32_t msec_timer +); + +uint32_t +pipe_mgr_tcam_scrub_timer_get( + bf_dev_id_t dev +); + +bool +pipe_mgr_is_device_locked( + bf_dev_id_t dev_id +); + + #endif /* BF_WRAPPER_H */ diff --git a/asic/src/tofino_asic/imported_bf_functions b/asic/src/tofino_asic/imported_bf_functions index 54775dd2..0b97f67e 100644 --- a/asic/src/tofino_asic/imported_bf_functions +++ b/asic/src/tofino_asic/imported_bf_functions @@ -160,3 +160,34 @@ bf_pm_fsm_transition_callback bf_sys_log_callback lld_sku_map_dev_port_id_to_mac_ch + +# Snapshot +bf_snapshot_create +bf_snapshot_delete +bf_snapshot_clear +bf_snapshot_state_set +bf_snapshot_state_get +bf_snapshot_cfg_set +bf_snapshot_capture_trigger_field_add +bf_snapshot_capture_trigger_field_add_bytes +bf_snapshot_capture_trigger_fields_clr +bf_snapshot_capture_phv_fields_dict_size +bf_snapshot_capture_get +bf_snapshot_capture_decode_field_value +bf_snapshot_capture_decode_field_value_bytes +bf_snapshot_raw_capture_get +bf_snapshot_total_phv_count_get +bf_snapshot_field_in_scope +bf_snapshot_trigger_field_in_scope +bf_snapshot_do_polling +bf_snapshot_entry_params_get +bf_snapshot_handle_get + +# Interrupt management +lld_enable_all_ints +lld_dump_new_ints +lld_int_poll +bf_err_interrupt_handling_mode_set +pipe_mgr_is_device_locked +pipe_mgr_tcam_scrub_timer_set +pipe_mgr_tcam_scrub_timer_get diff --git a/asic/src/tofino_asic/imported_bf_types b/asic/src/tofino_asic/imported_bf_types index 1d31c80b..b18afa9f 100644 --- a/asic/src/tofino_asic/imported_bf_types +++ b/asic/src/tofino_asic/imported_bf_types @@ -5,3 +5,7 @@ pm_intf_fsm_states_t bf_port_ber_t qsfp_fsm_state_t qsfp_fsm_ch_en_state_t +bf_snapshot_capture_ctrl_info_t +bf_snapshot_tables_info_t +bf_snapshot_capture_ctrl_info_arr_t +bf_snapshot_trigger_field_t diff --git a/asic/src/tofino_asic/interrupt_monitor.rs b/asic/src/tofino_asic/interrupt_monitor.rs new file mode 100644 index 00000000..096e79ae --- /dev/null +++ b/asic/src/tofino_asic/interrupt_monitor.rs @@ -0,0 +1,131 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use std::{thread::sleep, time::Duration}; + +use crate::tofino_asic::{ + BF_INVALID_ARG, BF_SUCCESS, + bf_wrapper::bf_error_str, + genpd::{ + bf_dev_id_t, bf_err_interrupt_handling_mode_set, bf_subdev_id_t, + lld_dump_new_ints, lld_enable_all_ints, lld_int_poll, + pipe_mgr_is_device_locked, pipe_mgr_tcam_scrub_timer_set, + }, +}; +use slog::{Logger, info, warn}; + +const DEV_ID: bf_dev_id_t = 0; +const SUBDEV_ID: bf_subdev_id_t = 0; +const INTERVAL: Duration = Duration::from_secs(5); + +/// Monitoring interrupts requires a number of precursory steps to set things up +/// in the SDE and on the ASIC. The `monitor_interrupts` function is designed as +/// a state machine that will drive forward toward the termial state of actively +/// running the monitoring loop. +/// +/// There are a number of errors that can happen in the SDE in the precursory +/// states. The code in the SDE is pretty twisty and the error model is not +/// totally clear, but it does appear to me that some of the errors that can +/// happen are transient in nature. In particular errors associated with timer +/// initialization and locking. With that in mind, each precursory state is +/// a loop that will go forever until the required SDE functions have been +/// called successfully. The loops have a 5 second interval to avoid excessive +/// iteration. +/// +/// The idea here is to get to the terminal state as soon as possible. Because +/// the terminal state is an infinite loop, it affords us the opportunity to +/// make each precursory loop run until success. +pub fn monitor_interrupts(log: Logger) -> ! { + let log = log.new(slog::o!("unit" => "interrupt monitor")); + info!(log, "starring interrupt monitor"); + + wait_for_unlock(&log); + enable_tcam_scrub(&log); + set_interrupt_handling_mode(&log); + enable_lld_interrupts(&log); + + monitoring_loop(&log) +} + +fn wait_for_unlock(log: &Logger) { + loop { + if unsafe { pipe_mgr_is_device_locked(DEV_ID) } { + warn!(log, "asic is locked, cannot start"); + sleep(INTERVAL); + continue; + } + info!(log, "asic is unlocked - starting interrupt monitor"); + break; + } +} + +fn enable_tcam_scrub(log: &Logger) { + loop { + // This value is in milliseconds so this is 2 minutes. This is the + // default SDE value. + let rc = unsafe { pipe_mgr_tcam_scrub_timer_set(DEV_ID, 120000) }; + if rc == BF_SUCCESS { + info!(log, "tcam scrub set to 2 minute interval"); + break; + } + warn!( + log, + "failed to enable tcam scrub"; + "error" => bf_error_str(rc) + ); + sleep(INTERVAL) + } +} + +fn set_interrupt_handling_mode(log: &Logger) { + loop { + let rc = unsafe { bf_err_interrupt_handling_mode_set(DEV_ID, true) }; + if rc == BF_SUCCESS { + info!(log, "interrupt handling mode set"); + break; + } + warn!( + log, + "failed to set interrupt handling mode"; + "error" => bf_error_str(rc) + ); + sleep(INTERVAL); + } +} + +fn enable_lld_interrupts(log: &Logger) { + loop { + let rc = unsafe { lld_enable_all_ints(DEV_ID, SUBDEV_ID) }; + if rc == BF_SUCCESS { + info!(log, "enabled lld interrupts"); + break; + } + warn!( + log, + "failed to enable lld interrupts"; + "error" => bf_error_str(rc) + ); + sleep(INTERVAL); + } +} + +fn monitoring_loop(log: &Logger) -> ! { + loop { + let rc = unsafe { lld_int_poll(DEV_ID, SUBDEV_ID, true) }; + if rc != BF_SUCCESS { + warn!(log, "lld_int_poll: {}", bf_error_str(rc)); + } + + let rc = unsafe { lld_dump_new_ints(DEV_ID, SUBDEV_ID) }; + // BF_INVALID_ARG means interrupts were found and dumped (yes, really) + // BF_SUCCESS means no new interrupts + // Anything else is an actual error + if rc != BF_SUCCESS && rc != BF_INVALID_ARG { + warn!(log, "lld_dump_new_ints: {}", bf_error_str(rc)); + } + sleep(INTERVAL); + } +} diff --git a/asic/src/tofino_asic/mod.rs b/asic/src/tofino_asic/mod.rs index 3453d566..50ce71a6 100644 --- a/asic/src/tofino_asic/mod.rs +++ b/asic/src/tofino_asic/mod.rs @@ -17,6 +17,7 @@ use common::ports::*; mod bf_wrapper; mod genpd; +pub mod interrupt_monitor; mod link_fsm; #[cfg(feature = "multicast")] pub mod mcast; @@ -24,6 +25,7 @@ pub mod ports; pub mod qsfp; mod sde_log; pub mod serdes; +pub mod snapshot; pub mod stats; pub mod table; diff --git a/asic/src/tofino_asic/snapshot.rs b/asic/src/tofino_asic/snapshot.rs new file mode 100644 index 00000000..0ee945e9 --- /dev/null +++ b/asic/src/tofino_asic/snapshot.rs @@ -0,0 +1,408 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Rust wrappers around the SDE snapshot C APIs. + +use std::ffi::{CStr, CString, c_char}; + +use aal::{AsicError, AsicResult}; + +use crate::tofino_asic::genpd::*; +use crate::tofino_asic::{CheckError, Handle}; + +/// Normalize a P4 field name to match the SDE's internal convention. +/// +/// The SDE replaces `.`, `[`, `]`, and `$` with `_` when storing field names +/// from context.json (see `normalize_name` in `pipe_mgr_ctx_json_entry_format.c`). +/// We must apply the same transformation before passing field names to any +/// snapshot API that does a strcmp against the stored names. +fn normalize_field_name(name: &str) -> String { + name.chars() + .map(|c| match c { + '.' | '[' | ']' | '$' => '_', + other => other, + }) + .collect() +} + +/// Opaque snapshot handle returned by the SDE. +pub type SnapshotHdl = u32; + +/// Direction of the snapshot (ingress or egress pipeline). +#[derive(Debug, Clone, Copy)] +pub enum SnapshotDir { + Ingress, + Egress, +} + +impl SnapshotDir { + fn as_raw(self) -> u32 { + match self { + SnapshotDir::Ingress => 0, // BF_SNAPSHOT_DIR_INGRESS + SnapshotDir::Egress => 1, // BF_SNAPSHOT_DIR_EGRESS + } + } +} + +/// FSM state of a snapshot per-pipe. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PipeState { + Passive, + Armed, + TriggerHappy, + Full, + Unknown(u32), +} + +impl PipeState { + fn from_raw(v: u32) -> Self { + match v { + 0 => PipeState::Passive, // PIPE_SNAPSHOT_FSM_ST_PASSIVE + 1 => PipeState::Armed, // PIPE_SNAPSHOT_FSM_ST_ARMED + 2 => PipeState::TriggerHappy, // PIPE_SNAPSHOT_FSM_ST_TRIGGER_HAPPY + 3 => PipeState::Full, // PIPE_SNAPSHOT_FSM_ST_FULL + other => PipeState::Unknown(other), + } + } +} + +/// Table hit/miss information from a capture. +#[derive(Debug)] +pub struct TableInfo { + pub name: String, + pub hit: bool, + pub inhibited: bool, + pub executed: bool, + pub match_hit_address: u32, +} + +/// Per-stage capture control information. +#[derive(Debug)] +pub struct CaptureCtrl { + pub stage_id: u8, + pub prev_stage_trigger: bool, + pub timer_trigger: bool, + pub local_stage_trigger: bool, + pub tables: Vec, + pub next_table: String, + pub ingress_dp_error: bool, + pub egress_dp_error: bool, +} + +/// Captured data from a snapshot. +pub struct SnapshotCapture { + pub capture_buf: Vec, + pub ctrls: Vec, + pub num_captures: i32, +} + +/// Create a new snapshot on the ASIC. +pub fn snapshot_create( + hdl: &Handle, + pipe: u32, + start_stage: u8, + end_stage: u8, + dir: SnapshotDir, +) -> AsicResult { + let mut snap_hdl: SnapshotHdl = 0; + unsafe { + bf_snapshot_create( + hdl.dev_id, + pipe, + start_stage, + end_stage, + dir.as_raw(), + &mut snap_hdl, + ) + .check_error("bf_snapshot_create")?; + } + Ok(snap_hdl) +} + +/// Delete a snapshot. +pub fn snapshot_delete(_hdl: &Handle, snap_hdl: SnapshotHdl) -> AsicResult<()> { + unsafe { bf_snapshot_delete(snap_hdl).check_error("bf_snapshot_delete") } +} + +/// Arm a snapshot (enable it and begin waiting for a trigger). +pub fn snapshot_arm( + _hdl: &Handle, + snap_hdl: SnapshotHdl, + timeout_usec: u32, +) -> AsicResult<()> { + unsafe { + bf_snapshot_state_set( + snap_hdl, + 1, // BF_SNAPSHOT_ST_ENABLED + timeout_usec, + ) + .check_error("bf_snapshot_state_set(ENABLED)") + } +} + +/// Get the FSM state per pipe. +pub fn snapshot_state_get( + _hdl: &Handle, + snap_hdl: SnapshotHdl, +) -> AsicResult> { + let size = 4u32; + let mut fsm_raw = [0u32; 4]; + let mut enabled = [false; 4]; + unsafe { + bf_snapshot_state_get( + snap_hdl, + size, + fsm_raw.as_mut_ptr(), + enabled.as_mut_ptr(), + ) + .check_error("bf_snapshot_state_get")?; + } + Ok((0..usize::try_from(size).expect("pipeline count as usize")) + .map(|i| PipeState::from_raw(fsm_raw[i])) + .collect()) +} + +/// Poll for triggered snapshots (drives the FSM). +pub fn snapshot_poll(hdl: &Handle) -> AsicResult<()> { + unsafe { + bf_snapshot_do_polling(hdl.dev_id).check_error("bf_snapshot_do_polling") + } +} + +/// Add a trigger field (value/mask pair). +/// +/// `value` and `mask` are big-endian byte slices (MSB first). +/// They are right-aligned into the 16-byte trigger struct, so for a +/// 4-byte field you can pass a 4-byte slice and it ends up in the +/// correct position. +pub fn snapshot_add_trigger( + _hdl: &Handle, + snap_hdl: SnapshotHdl, + field: &str, + value: &[u8], + mask: &[u8], +) -> AsicResult<()> { + let normalized = normalize_field_name(field); + + let mut trig: bf_snapshot_trigger_field_t = unsafe { std::mem::zeroed() }; + + // Copy the normalized name into the fixed-size name array. + let name_bytes = normalized.as_bytes(); + let copy_len = name_bytes.len().min(trig.name.len() - 1); + for (i, &b) in name_bytes[..copy_len].iter().enumerate() { + trig.name[i] = b as c_char; + } + + // Right-align value and mask into the 16-byte arrays. + let vlen = value.len().min(16); + let mlen = mask.len().min(16); + trig.value[16 - vlen..].copy_from_slice(&value[value.len() - vlen..]); + trig.mask[16 - mlen..].copy_from_slice(&mask[mask.len() - mlen..]); + + unsafe { + bf_snapshot_capture_trigger_field_add_bytes(snap_hdl, trig) + .check_error(&format!("trigger_field_add({field})")) + } +} + +/// Clear all trigger fields. +pub fn snapshot_clear_triggers( + _hdl: &Handle, + snap_hdl: SnapshotHdl, +) -> AsicResult<()> { + unsafe { + bf_snapshot_capture_trigger_fields_clr(snap_hdl) + .check_error("trigger_fields_clr") + } +} + +/// Read captured data from a snapshot. +pub fn snapshot_capture( + _hdl: &Handle, + snap_hdl: SnapshotHdl, + pipe: u32, +) -> AsicResult { + // Figure out how big the capture buffer needs to be. + let mut total_size: u32 = 0; + let mut per_stage_size: u32 = 0; + unsafe { + bf_snapshot_capture_phv_fields_dict_size( + snap_hdl, + &mut total_size, + &mut per_stage_size, + ) + .check_error("capture_phv_fields_dict_size")?; + } + + let mut capture_buf = vec![ + 0u8; + usize::try_from(total_size) + .expect("total capture size as usize") + ]; + let mut ctrl_arr = Box::new(unsafe { + std::mem::zeroed::() + }); + let mut num_captures: i32 = 0; + + unsafe { + bf_snapshot_capture_get( + snap_hdl, + pipe, + capture_buf.as_mut_ptr(), + ctrl_arr.as_mut(), + &mut num_captures, + ) + .check_error("snapshot_capture_get")?; + } + + // Convert ctrl info to Rust structs. + let mut ctrls = Vec::new(); + for i in 0..num_captures as usize { + let c = &ctrl_arr.ctrl[i]; + if !c.valid { + continue; + } + + let mut tables = Vec::new(); + for t in &c.tables_info { + let name = c_char_array_to_string(&t.table_name); + if name.is_empty() { + continue; + } + tables.push(TableInfo { + name, + hit: t.table_hit, + inhibited: t.table_inhibited, + executed: t.table_executed, + match_hit_address: t.match_hit_address, + }); + } + + ctrls.push(CaptureCtrl { + stage_id: c.stage_id, + prev_stage_trigger: c.prev_stage_trigger, + timer_trigger: c.timer_trigger, + local_stage_trigger: c.local_stage_trigger, + tables, + next_table: c_char_array_to_string(&c.next_table), + ingress_dp_error: c.ingr_dp_error, + egress_dp_error: c.egr_dp_error, + }); + } + + Ok(SnapshotCapture { capture_buf, ctrls, num_captures }) +} + +/// Decode a named field from a capture buffer at a given stage. +/// +/// Returns the field value as a big-endian byte vector, or `None` if +/// the field is not valid at this stage. +pub fn snapshot_decode_field( + _hdl: &Handle, + snap_hdl: SnapshotHdl, + pipe: u32, + stage: u8, + capture: &mut [u8], + num_captures: i32, + field: &str, +) -> AsicResult>> { + let normalized = normalize_field_name(field); + let c_name = CString::new(normalized).map_err(|_| { + AsicError::Internal(format!("invalid field name: {field}")) + })?; + let mut buf = [0u8; 16]; + let mut width: i32 = 0; + let mut valid: bool = false; + + unsafe { + bf_snapshot_capture_decode_field_value_bytes( + snap_hdl, + pipe, + stage, + capture.as_mut_ptr(), + num_captures, + c_name.as_ptr() as *mut c_char, + buf.as_mut_ptr(), + i32::try_from(buf.len()).expect("snapshot buffer length as i32"), + &mut width, + &mut valid, + ) + .check_error(&format!("decode_field({field})"))?; + } + + if valid { + Ok(Some( + buf[..usize::try_from(width) + .expect("snapshot buffer width as usize")] + .to_vec(), + )) + } else { + Ok(None) + } +} + +/// Check if a field is in scope for capture at a given stage. +pub fn snapshot_field_in_scope( + hdl: &Handle, + pipe: u32, + stage: u8, + dir: SnapshotDir, + field: &str, +) -> AsicResult { + let normalized = normalize_field_name(field); + let c_name = CString::new(normalized).map_err(|_| { + AsicError::Internal(format!("invalid field name: {field}")) + })?; + let mut exists = false; + unsafe { + bf_snapshot_field_in_scope( + hdl.dev_id, + pipe, + stage, + dir.as_raw(), + c_name.as_ptr() as *mut c_char, + &mut exists, + ) + .check_error(&format!("field_in_scope({field})"))?; + } + Ok(exists) +} + +/// Check if a field can be used as a trigger at a given stage. +pub fn snapshot_trigger_field_in_scope( + hdl: &Handle, + pipe: u32, + stage: u8, + dir: SnapshotDir, + field: &str, +) -> AsicResult { + let normalized = normalize_field_name(field); + let c_name = CString::new(normalized).map_err(|_| { + AsicError::Internal(format!("invalid field name: {field}")) + })?; + let mut exists = false; + unsafe { + bf_snapshot_trigger_field_in_scope( + hdl.dev_id, + pipe, + stage, + dir.as_raw(), + c_name.as_ptr() as *mut c_char, + &mut exists, + ) + .check_error(&format!("trigger_field_in_scope({field})"))?; + } + Ok(exists) +} + +/// Helper: convert a fixed-size c_char array to a String, stopping at the +/// first NUL. +fn c_char_array_to_string(arr: &[c_char]) -> String { + let ptr = arr.as_ptr(); + // Safety: the array comes from the SDE which NUL-terminates its strings, + // and we bound the search to the array length. + unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() +} diff --git a/asic/src/tofino_asic/table.rs b/asic/src/tofino_asic/table.rs index d5fd811b..c129fbcc 100644 --- a/asic/src/tofino_asic/table.rs +++ b/asic/src/tofino_asic/table.rs @@ -61,6 +61,7 @@ pub trait TofinoTableOps { hdl: &Handle, last_key: Option, max: usize, + from_hardware: bool, ) -> AsicResult<(Vec, Vec)>; } @@ -613,6 +614,7 @@ impl TofinoTableOps for Table { hdl: &Handle, last_key: Option, mut max: usize, + from_hardware: bool, ) -> AsicResult<(Vec, Vec)> { if last_key.is_none() { max = 1; @@ -654,7 +656,7 @@ impl TofinoTableOps for Table { data_hdls.as_mut_ptr(), max as u32, &mut n, - 0, // read from cache + if from_hardware { 1 } else { 0 }, ) } { // If the table isn't full, we can get an BF_OBJECT_NOT_FOUND error @@ -808,12 +810,14 @@ impl aal::TableOps for Table { fn get_entries( &self, hdl: &Handle, + from_hardware: bool, ) -> AsicResult> { let mut last_key: Option = None; let mut rval = Vec::new(); loop { - let (mut keys, data) = self.entries_get(hdl, last_key, 256)?; + let (mut keys, data) = + self.entries_get(hdl, last_key, 256, from_hardware)?; assert_eq!(keys.len(), data.len()); if keys.is_empty() { @@ -863,7 +867,8 @@ impl aal::TableOps for Table { let mut values: Vec<(M, CounterData)> = Vec::new(); let mut last_key: Option = None; loop { - let (mut keys, data) = self.entries_get(hdl, last_key, 256)?; + let (mut keys, data) = + self.entries_get(hdl, last_key, 256, false)?; assert_eq!(keys.len(), data.len()); if keys.is_empty() { break; diff --git a/asic/src/tofino_stub/table.rs b/asic/src/tofino_stub/table.rs index 90c39d4b..a2444d9e 100644 --- a/asic/src/tofino_stub/table.rs +++ b/asic/src/tofino_stub/table.rs @@ -99,6 +99,7 @@ impl TableOps for Table { fn get_entries( &self, _s: &StubHandle, + _from_hardware: bool, ) -> AsicResult> { Err(AsicError::OperationUnsupported) } diff --git a/dpd-api/src/lib.rs b/dpd-api/src/lib.rs index 3f8d491c..3ccec5ae 100644 --- a/dpd-api/src/lib.rs +++ b/dpd-api/src/lib.rs @@ -63,6 +63,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (9, SNAPSHOT), (8, MCAST_STRICT_UNDERLAY), (7, MCAST_SOURCE_FILTER_ANY), (6, CONSOLIDATED_V4_ROUTES), @@ -1721,13 +1722,32 @@ pub trait DpdApi { */ #[endpoint { method = GET, - path = "/table/{table}/dump" + path = "/table/{table}/dump", + versions = VERSION_SNAPSHOT.. }] async fn table_dump( rqctx: RequestContext, + query: Query, path: Path, ) -> Result, HttpError>; + #[endpoint { + method = GET, + path = "/table/{table}/dump", + versions = ..VERSION_SNAPSHOT + }] + async fn table_dump_v1( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError> { + Self::table_dump( + rqctx, + Query::from(TableDumpOptions { from_hardware: false }), + path, + ) + .await + } + /** * Get any counter data from a single P4 match-action table. * The name of the table should match one of those returned by the @@ -2518,6 +2538,29 @@ pub trait DpdApi { rqctx: RequestContext, path: Path, ) -> Result, HttpError>; + + /// Capture a PHV snapshot: create snapshot, set triggers, arm, wait + /// for trigger, read capture, decode fields, and clean up. + #[endpoint { + method = POST, + path = "/snapshot/capture", + versions = VERSION_SNAPSHOT.. + }] + async fn snapshot_capture( + rqctx: RequestContext, + body: TypedBody, + ) -> Result, HttpError>; + + /// Check which fields are in scope at a given stage. + #[endpoint { + method = POST, + path = "/snapshot/scope", + versions = VERSION_SNAPSHOT.. + }] + async fn snapshot_scope( + rqctx: RequestContext, + body: TypedBody, + ) -> Result>, HttpError>; } /// Parameter used to create a port. @@ -3015,6 +3058,11 @@ pub struct TableParam { pub table: String, } +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct TableDumpOptions { + pub from_hardware: bool, +} + #[derive(Deserialize, Serialize, JsonSchema)] pub struct CounterPath { pub counter: String, @@ -3354,3 +3402,198 @@ pub struct Ber { /// Aggregate BER on the link. pub total_ber: f32, } + +// --- Snapshot types --- + +/// Direction of a PHV snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub enum SnapshotDirection { + /// Take snapshot of ingress pipeline + Ingress, + /// Take snapshot of egress pipeline + Egress, +} + +/// A trigger field for a snapshot, with hex-encoded value and mask. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotTrigger { + /// Name of the field to capture. + /// + /// Must match what's in the phv ingress or phv egress section of + /// /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa. + pub field: String, + /// Hex-encoded value (e.g. "0x112233445566") + pub value: String, + /// Hex-encoded mask (e.g. "0xffffffffffff") + pub mask: String, +} + +/// Request body for creating and capturing a PHV snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotCreate { + /// Index of the pipeline to capture. Typically this will be 0 through 3. + /// Different ports map to different pipelines. + pub pipe: u32, + /// Tofino hardware stage to start capturing at. + /// + /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of + /// stage layout. + pub start_stage: u8, + /// Tofino hardware stage to stop capturing at. + /// + /// See /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of + /// stage layout. + pub end_stage: u8, + /// Whether to capture on the ingress or egress pipeline. + pub dir: SnapshotDirection, + /// Fields and masks to use as snapshot trigger. Triggers are combined as + /// a logical `and`. + pub triggers: Vec, + /// Field names to decode from the capture. + pub fields: Vec, + /// Timeout in seconds to wait for trigger. + pub timeout_secs: u64, +} + +/// Table hit/miss result from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotTableResult { + /// Name of the table + pub name: String, + /// Whether the match lookup found a matching entry. + /// + /// Only meaningful when `executed` is true and `inhibited` is false. + /// The absence of `hit` does not necessarily mean that a lookup was + /// attempted. It simply means there was no hit, which could mean no lookup + /// was attempted or that the table's gateway inhibited it. + pub hit: bool, + /// Whether the table's gateway inhibited the match lookup from + /// proceeding. Gateways are conditional guards attached to tables that + /// can skip the lookup entirely. When inhibited, `hit` and + /// `match_hit_address` will be 0. Only applicable to tables that have + /// an attached gateway. + pub inhibited: bool, + /// Whether the table was active in this stage. This is the primary + /// gate: if a table was not executed, `hit`, `inhibited`, and + /// `match_hit_address` are all meaningless (zeroed by the SDE). + pub executed: bool, + /// The physical address of the entry that matched, sourced from the + /// exact-match or TCAM hit-address register depending on table type. + /// Zero when the table was not executed or was inhibited. + pub match_hit_address: u32, +} + +/// Per-stage result from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotStageResult { + /// The index of the stage this result came from. + pub stage_id: u8, + /// Whether this stage's own PHV match criteria fired the snapshot. + /// This is the primary trigger: the PHV contents at this stage matched + /// the key/mask programmed via the snapshot trigger configuration. + pub local_stage_trigger: bool, + /// Whether the snapshot was triggered because the previous stage was + /// already triggered and propagated its trigger signal forward. A + /// `prev_stage_trigger` with no `local_stage_trigger` means this stage + /// did not match the trigger criteria itself -- it was captured solely + /// because an adjacent stage matched. + pub prev_stage_trigger: bool, + /// Whether the snapshot was triggered by the timer mechanism rather + /// than a PHV field match. Useful for capturing pipeline state at a + /// specific time regardless of packet contents. + pub timer_trigger: bool, + /// The P4 table name that the MAU pipeline selected for execution in + /// the following stage after processing this one. + pub next_table: String, + /// Datapath error detected in the ingress pipeline at capture time. + /// Only reported on Tofino 2+; always false on Tofino 1. + pub ingress_dp_error: bool, + /// Datapath error detected in the egress pipeline at capture time. + /// Only reported on Tofino 2+; always false on Tofino 1. + pub egress_dp_error: bool, + /// Tables captured in the result. + pub tables: Vec, + /// Fields captured in the result. + pub fields: Vec, +} + +/// A decoded field value from a snapshot capture. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotFieldValue { + /// Name of the field. + pub name: String, + /// None if the field is not valid at this stage. + pub value: Option, +} + +/// Result of a snapshot capture operation. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotResult { + /// Stages captured in the result. + pub stages: Vec, +} + +/// Request body for checking field scope at a given stage. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotScopeRequest { + /// Pipeline index to check. + pub pipe: u32, + /// Stage index. + pub stage: u8, + /// Whether to check the ingress or egress pipeline. + pub dir: SnapshotDirection, + /// Fields to check. + pub fields: Vec, + /// If true, check trigger scope; otherwise check capture scope. + pub trigger: bool, +} + +/// Whether a field is in scope at a given stage. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SnapshotFieldScope { + /// Field name + pub field: String, + /// Whether or not the field is in scope. + pub in_scope: bool, +} + +/// Request body for dumping table entries. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpRequest { + /// Fully-qualified P4 table name (e.g. "Ingress.services.service"). + pub table_name: String, + /// If true, read entries from ASIC hardware via indirect register + /// reads instead of the SDE's software shadow. + pub from_hw: bool, +} + +/// A key field from a table entry dump. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpKeyField { + /// Name of the field + pub name: String, + /// Key value + pub value: String, + /// For ternary fields: the mask. For LPM: the prefix length. + pub mask: Option, +} + +/// A single entry from a table dump. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpEntry { + /// Action associated with an entry. + pub action: String, + /// Match fields for an entry. + pub match_fields: Vec, +} + +/// Result of a table dump operation. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDumpResult { + /// Name of the table. + pub table_name: String, + /// Number of entries in the table. + pub num_entries: usize, + /// Table entries. + pub entries: Vec, +} diff --git a/dpd-client/tests/integration_tests/mcast.rs b/dpd-client/tests/integration_tests/mcast.rs index 688b72db..2b56698e 100644 --- a/dpd-client/tests/integration_tests/mcast.rs +++ b/dpd-client/tests/integration_tests/mcast.rs @@ -708,7 +708,7 @@ async fn test_vlan_propagation_to_internal() -> TestResult { // Check the bitmap table to see if VLAN 42 is properly set (this is where VLAN matters for P4) let bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should clean up internal group") .into_inner(); @@ -3481,7 +3481,7 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Note: Only IPv6 has a replication table; IPv4 uses different mechanisms let ipv6_repl_table_before = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_replication_ipv6") + .table_dump("pipe.Ingress.mcast_ingress.mcast_replication_ipv6", false) .await .expect("Should be able to dump IPv6 replication table"); @@ -3493,13 +3493,13 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check route tables let ipv4_route_table_before = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump IPv4 route table"); let ipv6_route_table_before = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter6.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter6.tbl", false) .await .expect("Should be able to dump IPv6 route table"); @@ -3515,13 +3515,13 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check NAT tables let ipv4_nat_table_before = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump IPv4 NAT table"); let ipv6_nat_table_before = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv6_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv6_mcast", false) .await .expect("Should be able to dump IPv6 NAT table"); @@ -3537,13 +3537,19 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check source filter tables let ipv4_src_filter_table_before = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4", + false, + ) .await .expect("Should be able to dump IPv4 source filter table"); let ipv6_src_filter_table_before = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv6") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv6", + false, + ) .await .expect("Should be able to dump IPv6 source filter table"); @@ -3569,7 +3575,7 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Note: Only IPv6 has a replication table; IPv4 uses different mechanisms let ipv6_repl_table_after = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_replication_ipv6") + .table_dump("pipe.Ingress.mcast_ingress.mcast_replication_ipv6", false) .await .expect("Should be able to dump IPv6 replication table"); @@ -3581,13 +3587,13 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check route tables after reset let ipv4_route_table_after = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump IPv4 route table"); let ipv6_route_table_after = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter6.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter6.tbl", false) .await .expect("Should be able to dump IPv6 route table"); @@ -3603,13 +3609,13 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check NAT tables after reset let ipv4_nat_table_after = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump IPv4 NAT table"); let ipv6_nat_table_after = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv6_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv6_mcast", false) .await .expect("Should be able to dump IPv6 NAT table"); @@ -3625,13 +3631,19 @@ async fn test_multicast_reset_all_tables() -> TestResult { // Check source filter tables after reset let ipv4_src_filter_table_after = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4", + false, + ) .await .expect("Should be able to dump IPv4 source filter table"); let ipv6_src_filter_table_after = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv6") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv6", + false, + ) .await .expect("Should be able to dump IPv6 source filter table"); @@ -4521,7 +4533,7 @@ async fn test_multicast_empty_then_add_members_ipv6() -> TestResult { // Verify bitmap table is initially empty for both group IDs let bitmap_table_initial = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table initially"); // Should have no bitmap entries when groups are empty @@ -4672,7 +4684,7 @@ async fn test_multicast_empty_then_add_members_ipv6() -> TestResult { // Verify bitmap table now has entry for external group ID only (1 entry with bitmap of 2 ports) let bitmap_table_with_members = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table with members"); assert_eq!( @@ -4724,7 +4736,7 @@ async fn test_multicast_empty_then_add_members_ipv6() -> TestResult { // Verify bitmap table is empty again after removing all members let bitmap_table_final = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table after emptying"); // Should have no bitmap entries when group is empty again @@ -4875,7 +4887,7 @@ async fn test_multicast_empty_then_add_members_ipv4() -> TestResult { // Verify bitmap table is initially empty for both group IDs let bitmap_table_initial = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table initially"); // Should have no bitmap entries when groups are empty @@ -5025,7 +5037,7 @@ async fn test_multicast_empty_then_add_members_ipv4() -> TestResult { // Verify bitmap table now has entry for external group ID only (underlay doesn't need decap bitmap) let bitmap_table_with_members = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table with members"); // Should have bitmap entry for external group ID only (underlay doesn't need decap bitmap) @@ -5077,7 +5089,7 @@ async fn test_multicast_empty_then_add_members_ipv4() -> TestResult { // Verify bitmap table is empty again after removing all members let bitmap_table_final = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table after emptying"); // Should have no bitmap entries when group is empty again @@ -5143,22 +5155,22 @@ async fn test_multicast_rollback_external_group_creation_failure() -> TestResult // Get initial table states let initial_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table"); let initial_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table"); let initial_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table"); let initial_src_filter_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should be able to dump source filter table"); @@ -5225,7 +5237,7 @@ async fn test_multicast_rollback_external_group_creation_failure() -> TestResult // Table states should be unchanged let post_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table"); @@ -5237,7 +5249,7 @@ async fn test_multicast_rollback_external_group_creation_failure() -> TestResult let post_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table"); @@ -5249,7 +5261,7 @@ async fn test_multicast_rollback_external_group_creation_failure() -> TestResult let post_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table"); @@ -5261,7 +5273,7 @@ async fn test_multicast_rollback_external_group_creation_failure() -> TestResult let post_src_filter_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should be able to dump source filter table"); @@ -5426,7 +5438,7 @@ async fn test_multicast_rollback_nat_transition_failure() -> TestResult { // Get initial NAT table state let initial_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table"); @@ -5492,7 +5504,7 @@ async fn test_multicast_rollback_nat_transition_failure() -> TestResult { // Verify NAT table state is unchanged let post_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table"); @@ -5541,7 +5553,7 @@ async fn test_multicast_rollback_vlan_propagation_consistency() { // Get initial bitmap table state let _initial_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table"); @@ -5562,12 +5574,12 @@ async fn test_multicast_rollback_vlan_propagation_consistency() { // Get initial table states before attempting creation let initial_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table"); let initial_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table"); @@ -5594,12 +5606,12 @@ async fn test_multicast_rollback_vlan_propagation_consistency() { // Verify rollback worked - tables should remain unchanged let post_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table after rollback"); let post_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table after rollback"); @@ -5685,7 +5697,10 @@ async fn test_multicast_rollback_source_filter_update() -> TestResult { // Get initial source filter table state let initial_src_table = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4", + false, + ) .await .expect("Should be able to dump source filter table"); @@ -5737,7 +5752,10 @@ async fn test_multicast_rollback_source_filter_update() -> TestResult { // Verify source filter table is back to initial state (rollback worked) let post_rollback_src_table = switch .client - .table_dump("pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4") + .table_dump( + "pipe.Ingress.mcast_ingress.mcast_source_filter_ipv4", + false, + ) .await .expect("Should be able to dump source filter table after rollback"); @@ -5898,17 +5916,17 @@ async fn test_multicast_rollback_table_operation_failure() { // Get table states after internal group deletion but before external group attempt let initial_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table"); let initial_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table"); let initial_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table"); @@ -5945,19 +5963,19 @@ async fn test_multicast_rollback_table_operation_failure() { // Verify table rollback worked - all tables should be unchanged let post_route_table = switch .client - .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl") + .table_dump("pipe.Ingress.l3_router.MulticastRouter4.tbl", false) .await .expect("Should be able to dump route table after rollback"); let post_nat_table = switch .client - .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast") + .table_dump("pipe.Ingress.nat_ingress.ingress_ipv4_mcast", false) .await .expect("Should be able to dump NAT table after rollback"); let post_bitmap_table = switch .client - .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports") + .table_dump("pipe.Egress.mcast_egress.tbl_decap_ports", false) .await .expect("Should be able to dump bitmap table after rollback"); @@ -6133,7 +6151,7 @@ async fn test_source_filter_ipv4_collapses_to_any() -> TestResult { // Get baseline source filter table state let baseline_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table"); let baseline_count = baseline_table.entries.len(); @@ -6172,7 +6190,7 @@ async fn test_source_filter_ipv4_collapses_to_any() -> TestResult { // Check source filter table - should only have 1 new entry (the /0) let after_create_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table after create"); @@ -6253,7 +6271,7 @@ async fn test_source_filter_ipv6_collapses_to_any() -> TestResult { let baseline_table = switch .client - .table_dump(SOURCE_FILTER_IPV6_TABLE) + .table_dump(SOURCE_FILTER_IPV6_TABLE, false) .await .expect("Should dump IPv6 source filter table"); let baseline_count = baseline_table.entries.len(); @@ -6289,7 +6307,7 @@ async fn test_source_filter_ipv6_collapses_to_any() -> TestResult { // Should only have 1 new entry (the ::/0) let after_create_table = switch .client - .table_dump(SOURCE_FILTER_IPV6_TABLE) + .table_dump(SOURCE_FILTER_IPV6_TABLE, false) .await .expect("Should dump IPv6 source filter table after create"); @@ -6371,7 +6389,7 @@ async fn test_source_filter_update_to_any() -> TestResult { let baseline_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table"); let baseline_count = baseline_table.entries.len(); @@ -6399,7 +6417,7 @@ async fn test_source_filter_update_to_any() -> TestResult { // Should have 2 specific entries let after_create_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump table after create"); @@ -6442,7 +6460,7 @@ async fn test_source_filter_update_to_any() -> TestResult { // Should now have only 1 entry (the /0), replacing the 2 specific ones let after_update_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump table after update"); @@ -6498,7 +6516,7 @@ async fn test_source_filter_cleanup_on_delete() -> TestResult { let baseline_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table"); let baseline_count = baseline_table.entries.len(); @@ -6526,7 +6544,7 @@ async fn test_source_filter_cleanup_on_delete() -> TestResult { // Verify entries were added let after_create_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump table after create"); @@ -6544,7 +6562,7 @@ async fn test_source_filter_cleanup_on_delete() -> TestResult { // Verify source filter entries were cleaned up let after_delete_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump table after delete"); @@ -6598,7 +6616,7 @@ async fn test_source_filter_empty_vec_normalizes_to_any() -> TestResult { let baseline_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table"); let baseline_count = baseline_table.entries.len(); @@ -6631,7 +6649,7 @@ async fn test_source_filter_empty_vec_normalizes_to_any() -> TestResult { // Should have exactly 1 new entry (the /0 for any source) let after_create_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump source filter table after create"); @@ -6647,7 +6665,7 @@ async fn test_source_filter_empty_vec_normalizes_to_any() -> TestResult { // Verify the /0 entry was removed let after_delete_table = switch .client - .table_dump(SOURCE_FILTER_IPV4_TABLE) + .table_dump(SOURCE_FILTER_IPV4_TABLE, false) .await .expect("Should dump table after delete"); diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 65acc43e..ce1870dd 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -1910,13 +1910,18 @@ impl DpdApi for DpdApiImpl { async fn table_dump( rqctx: RequestContext>, + query: Query, path: Path, ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let table = path.into_inner().table; - crate::table::get_entries(switch, table) - .map(HttpResponseOk) - .map_err(HttpError::from) + crate::table::get_entries( + switch, + table, + query.into_inner().from_hardware, + ) + .map(HttpResponseOk) + .map_err(HttpError::from) } async fn table_counters( @@ -2813,6 +2818,56 @@ impl DpdApi for DpdApiImpl { "not implemented for this asic".to_string(), )) } + + #[cfg(feature = "tofino_asic")] + async fn snapshot_capture( + rqctx: RequestContext, + body: TypedBody, + ) -> Result, HttpError> { + let switch: &Switch = rqctx.context(); + let req = body.into_inner(); + Ok(HttpResponseOk( + crate::snapshot::capture(switch, req) + .await + .map_err(HttpError::from)?, + )) + } + + #[cfg(not(feature = "tofino_asic"))] + async fn snapshot_capture( + _rqctx: RequestContext, + _body: TypedBody, + ) -> Result, HttpError> { + Err(HttpError::for_unavail( + None, + "not implemented for this asic".to_string(), + )) + } + + #[cfg(feature = "tofino_asic")] + async fn snapshot_scope( + rqctx: RequestContext, + body: TypedBody, + ) -> Result>, HttpError> { + let switch: &Switch = rqctx.context(); + let req = body.into_inner(); + Ok(HttpResponseOk( + crate::snapshot::scope(switch, req) + .await + .map_err(HttpError::from)?, + )) + } + + #[cfg(not(feature = "tofino_asic"))] + async fn snapshot_scope( + _rqctx: RequestContext, + _body: TypedBody, + ) -> Result>, HttpError> { + Err(HttpError::for_unavail( + None, + "not implemented for this asic".to_string(), + )) + } } // Convert a port ID path into a `QsfpPort` if possible. This is generally used diff --git a/dpd/src/main.rs b/dpd/src/main.rs index b8a440d0..22436893 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -71,6 +71,8 @@ mod port_settings; mod ports; mod route; mod rpw; +#[cfg(feature = "tofino_asic")] +mod snapshot; mod switch_identifiers; mod switch_port; mod table; @@ -284,6 +286,9 @@ impl Switch { let route_data = route::init(&log); let mac_mgmt = Mutex::new(macaddrs::MacManagement::new(&log)); + #[cfg(feature = "tofino_asic")] + run_interrupt_monitor(log.clone()); + let ws_log = log.new(slog::o!("unit" => "workflow_server")); let workflow_server = rpw::WorkflowServer::new(ws_log); @@ -398,6 +403,7 @@ impl Switch { pub fn table_dump( &self, t: table::TableType, + from_hardware: bool, ) -> DpdResult { let t = self.table_get(t)?; @@ -405,7 +411,7 @@ impl Switch { name: t.name.to_string(), size: t.usage.size as usize, entries: t - .get_entries::(&self.asic_hdl) + .get_entries::(&self.asic_hdl, from_hardware) .map_err(|e| { error!(self.log, "failed to get table contents"; "table" => t.name.to_string(), @@ -810,3 +816,10 @@ async fn run_dpd(opt: Opt) -> anyhow::Result<()> { stub_main(switch).await } } + +#[cfg(feature = "tofino_asic")] +fn run_interrupt_monitor(log: slog::Logger) { + std::thread::spawn(move || { + asic::tofino_asic::interrupt_monitor::monitor_interrupts(log); + }); +} diff --git a/dpd/src/snapshot.rs b/dpd/src/snapshot.rs new file mode 100644 index 00000000..48b50a66 --- /dev/null +++ b/dpd/src/snapshot.rs @@ -0,0 +1,214 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +//! Tofino PHV/table snapshot implementation + +use crate::Switch; +use crate::types::DpdError; +use asic::tofino_asic::snapshot; +use dpd_api::{SnapshotCreate, SnapshotResult}; +use dpd_api::{SnapshotDirection, SnapshotFieldScope, SnapshotScopeRequest}; + +pub(crate) async fn capture( + switch: &Switch, + req: SnapshotCreate, +) -> Result { + use dpd_api::SnapshotDirection; + + let hdl = &switch.asic_hdl; + + let dir = match req.dir { + SnapshotDirection::Ingress => snapshot::SnapshotDir::Ingress, + SnapshotDirection::Egress => snapshot::SnapshotDir::Egress, + }; + + // Create the snapshot. + let snap_hdl = snapshot::snapshot_create( + hdl, + req.pipe, + req.start_stage, + req.end_stage, + dir, + )?; + + // Helper closure to ensure cleanup on any error path. + let result = (|| -> Result { + // Set up trigger fields. + for trig in &req.triggers { + let value = parse_hex_bytes(&trig.value).map_err(|e| { + DpdError::Invalid(format!( + "bad trigger value '{}': {e}", + trig.value + )) + })?; + let mask = parse_hex_bytes(&trig.mask).map_err(|e| { + DpdError::Invalid(format!( + "bad trigger mask '{}': {e}", + trig.mask + )) + })?; + snapshot::snapshot_add_trigger( + hdl, + snap_hdl, + &trig.field, + &value, + &mask, + )?; + } + + // Arm the snapshot. + snapshot::snapshot_arm(hdl, snap_hdl, 0).map_err(DpdError::from)?; + + // Wait for capture. + let timeout = std::time::Duration::from_secs(req.timeout_secs); + let poll_interval = std::time::Duration::from_millis(100); + let start = std::time::Instant::now(); + loop { + let _ = snapshot::snapshot_poll(hdl); + let states = snapshot::snapshot_state_get(hdl, snap_hdl) + .map_err(DpdError::from)?; + // For a single-pipe snapshot, bf_snapshot_state_get + // writes the FSM state to index 0 of the output array + // regardless of which pipe was specified. Check all + // entries so this works for any pipe value. + if states.contains(&snapshot::PipeState::Full) { + break; + } + if start.elapsed() > timeout { + return Err(DpdError::Invalid(String::from( + "timed out waiting for snapshot trigger", + ))); + } + std::thread::sleep(poll_interval); + } + + // Read captured data. + let mut cap = snapshot::snapshot_capture(hdl, snap_hdl, req.pipe) + .map_err(DpdError::from)?; + + // Build stage results with decoded fields. + let mut stages = Vec::new(); + for ctrl in &cap.ctrls { + use dpd_api::{SnapshotStageResult, SnapshotTableResult}; + + let tables = ctrl + .tables + .iter() + .map(|t| SnapshotTableResult { + name: t.name.clone(), + hit: t.hit, + inhibited: t.inhibited, + executed: t.executed, + match_hit_address: t.match_hit_address, + }) + .collect(); + + let mut fields = Vec::new(); + for field_name in &req.fields { + use dpd_api::SnapshotFieldValue; + + let val = snapshot::snapshot_decode_field( + hdl, + snap_hdl, + req.pipe, + ctrl.stage_id, + &mut cap.capture_buf, + cap.num_captures, + field_name, + ) + .map_err(DpdError::from)?; + + fields.push(SnapshotFieldValue { + name: field_name.clone(), + value: val.map(|v| { + let hex: String = + v.iter().map(|b| format!("{b:02x}")).collect(); + format!("0x{hex}") + }), + }); + } + + stages.push(SnapshotStageResult { + stage_id: ctrl.stage_id, + local_stage_trigger: ctrl.local_stage_trigger, + prev_stage_trigger: ctrl.prev_stage_trigger, + timer_trigger: ctrl.timer_trigger, + next_table: ctrl.next_table.clone(), + ingress_dp_error: ctrl.ingress_dp_error, + egress_dp_error: ctrl.egress_dp_error, + tables, + fields, + }); + } + + Ok(SnapshotResult { stages }) + })(); + + // Always clean up the snapshot handle. + let _ = snapshot::snapshot_clear_triggers(hdl, snap_hdl); + let _ = snapshot::snapshot_delete(hdl, snap_hdl); + + result +} + +pub(crate) async fn scope( + switch: &Switch, + req: SnapshotScopeRequest, +) -> Result, DpdError> { + let hdl = &switch.asic_hdl; + + let dir = match req.dir { + SnapshotDirection::Ingress => snapshot::SnapshotDir::Ingress, + SnapshotDirection::Egress => snapshot::SnapshotDir::Egress, + }; + + // Create a temporary snapshot to initialize the internal field + // dictionary in the SDE. + let snap_hdl = + snapshot::snapshot_create(hdl, req.pipe, req.stage, req.stage, dir) + .map_err(DpdError::from)?; + + let result = (|| -> Result, DpdError> { + let mut results = Vec::new(); + for field_name in &req.fields { + let in_scope = if req.trigger { + snapshot::snapshot_trigger_field_in_scope( + hdl, req.pipe, req.stage, dir, field_name, + ) + } else { + snapshot::snapshot_field_in_scope( + hdl, req.pipe, req.stage, dir, field_name, + ) + } + .map_err(DpdError::from)?; + + results.push(SnapshotFieldScope { + field: field_name.clone(), + in_scope, + }); + } + Ok(results) + })(); + + let _ = snapshot::snapshot_delete(hdl, snap_hdl); + result +} + +fn parse_hex_bytes(s: &str) -> Result, String> { + let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); + // Pad to even length so we can decode full bytes. + let padded = if !s.len().is_multiple_of(2) { + format!("0{s}") + } else { + s.to_string() + }; + (0..padded.len()) + .step_by(2) + .map(|i| { + u8::from_str_radix(&padded[i..i + 2], 16).map_err(|e| e.to_string()) + }) + .collect() +} diff --git a/dpd/src/table/arp_ipv4.rs b/dpd/src/table/arp_ipv4.rs index 2603b2f8..963ec673 100644 --- a/dpd/src/table/arp_ipv4.rs +++ b/dpd/src/table/arp_ipv4.rs @@ -65,8 +65,8 @@ pub fn delete_entry(s: &Switch, tgt_ip: Ipv4Addr) -> DpdResult<()> { s.table_entry_del(TableType::ArpIpv4, &match_key) } -pub fn table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::ArpIpv4) +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { + s.table_dump::(TableType::ArpIpv4, from_hardware) } pub fn counter_fetch( diff --git a/dpd/src/table/attached_subnet_v4.rs b/dpd/src/table/attached_subnet_v4.rs index afd3e21c..967bc67a 100644 --- a/dpd/src/table/attached_subnet_v4.rs +++ b/dpd/src/table/attached_subnet_v4.rs @@ -59,9 +59,10 @@ pub fn delete_entry(s: &Switch, subnet: Ipv4Net) -> DpdResult<()> { s.table_entry_del(TableType::AttachedSubnetIpv4, &match_key) } -pub fn table_dump(s: &Switch) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::AttachedSubnetIpv4, + from_hardware, ) } diff --git a/dpd/src/table/attached_subnet_v6.rs b/dpd/src/table/attached_subnet_v6.rs index c700c7c7..d38b352c 100644 --- a/dpd/src/table/attached_subnet_v6.rs +++ b/dpd/src/table/attached_subnet_v6.rs @@ -58,9 +58,10 @@ pub fn delete_entry(s: &Switch, subnet: Ipv6Net) -> DpdResult<()> { s.table_entry_del(TableType::AttachedSubnetIpv6, &match_key) } -pub fn table_dump(s: &Switch) -> DpdResult { +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { s.table_dump::( TableType::AttachedSubnetIpv6, + from_hardware, ) } diff --git a/dpd/src/table/mcast/mcast_egress.rs b/dpd/src/table/mcast/mcast_egress.rs index 08536e38..b1569056 100644 --- a/dpd/src/table/mcast/mcast_egress.rs +++ b/dpd/src/table/mcast/mcast_egress.rs @@ -174,9 +174,13 @@ pub(crate) fn del_bitmap_entry( } /// Dump the multicast decap table. -pub(crate) fn bitmap_table_dump(s: &Switch) -> DpdResult { +pub(crate) fn bitmap_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { s.table_dump::( TableType::McastEgressDecapPorts, + from_hardware, ) } @@ -253,9 +257,13 @@ pub(crate) fn del_port_mapping_entry( } /// Dump the multicast port mapping table. -pub(crate) fn port_mapping_table_dump(s: &Switch) -> DpdResult { +pub(crate) fn port_mapping_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { s.table_dump::( TableType::McastEgressPortMapping, + from_hardware, ) } diff --git a/dpd/src/table/mcast/mcast_nat.rs b/dpd/src/table/mcast/mcast_nat.rs index 62c5cd9e..94ccfaa6 100644 --- a/dpd/src/table/mcast/mcast_nat.rs +++ b/dpd/src/table/mcast/mcast_nat.rs @@ -89,8 +89,14 @@ pub(crate) fn del_ipv4_entry(s: &Switch, ip: Ipv4Addr) -> DpdResult<()> { } /// Dump the IPv4 NAT table's contents. -pub(crate) fn ipv4_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::NatIngressIpv4Mcast) +pub(crate) fn ipv4_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::NatIngressIpv4Mcast, + from_hardware, + ) } /// Fetch the IPv4 NAT table's counters. @@ -159,8 +165,14 @@ pub(crate) fn del_ipv6_entry(s: &Switch, ip: Ipv6Addr) -> DpdResult<()> { } /// Dump the IPv6 NAT table's contents. -pub(crate) fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::NatIngressIpv6Mcast) +pub(crate) fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::NatIngressIpv6Mcast, + from_hardware, + ) } /// Fetch the IPv6 NAT table's counters. diff --git a/dpd/src/table/mcast/mcast_replication.rs b/dpd/src/table/mcast/mcast_replication.rs index e815c92a..2a94c2fa 100644 --- a/dpd/src/table/mcast/mcast_replication.rs +++ b/dpd/src/table/mcast/mcast_replication.rs @@ -120,8 +120,14 @@ pub(crate) fn del_ipv6_entry(s: &Switch, dst_addr: Ipv6Addr) -> DpdResult<()> { } /// Dump the IPv6 multicast table's contents. -pub(crate) fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::McastIpv6) +pub(crate) fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::McastIpv6, + from_hardware, + ) } /// Fetch the IPv6 multicast table's counters. diff --git a/dpd/src/table/mcast/mcast_route.rs b/dpd/src/table/mcast/mcast_route.rs index d86c2e4d..ad2b9932 100644 --- a/dpd/src/table/mcast/mcast_route.rs +++ b/dpd/src/table/mcast/mcast_route.rs @@ -96,8 +96,14 @@ pub(crate) fn del_ipv4_entry(s: &Switch, route: Ipv4Addr) -> DpdResult<()> { } /// Dump the IPv4 multicast routing table's contents. -pub(crate) fn ipv4_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteIpv4Mcast) +pub(crate) fn ipv4_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::RouteIpv4Mcast, + from_hardware, + ) } /// Fetch the IPv4 multicast routing table's counters. @@ -186,8 +192,14 @@ pub(crate) fn del_ipv6_entry(s: &Switch, route: Ipv6Addr) -> DpdResult<()> { } /// Dump the IPv6 multicast routing table's contents. -pub(crate) fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteIpv6Mcast) +pub(crate) fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::RouteIpv6Mcast, + from_hardware, + ) } /// Fetch the IPv6 multicast routing table's counters. diff --git a/dpd/src/table/mcast/mcast_src_filter.rs b/dpd/src/table/mcast/mcast_src_filter.rs index 738d4bf3..e79ac531 100644 --- a/dpd/src/table/mcast/mcast_src_filter.rs +++ b/dpd/src/table/mcast/mcast_src_filter.rs @@ -105,8 +105,14 @@ pub(crate) fn del_ipv4_entry( } /// Dump the IPv4 multicast source filter table's contents. -pub(crate) fn ipv4_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::McastIpv4SrcFilter) +pub(crate) fn ipv4_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::McastIpv4SrcFilter, + from_hardware, + ) } /// Fetch the IPv4 multicast source filter table's counters. @@ -152,8 +158,14 @@ pub(crate) fn del_ipv6_entry( } /// Dump the IPv6 multicast source filter table's contents. -pub(crate) fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::McastIpv6SrcFilter) +pub(crate) fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::McastIpv6SrcFilter, + from_hardware, + ) } /// Fetch the IPv6 multicast source filter table's counters. diff --git a/dpd/src/table/mod.rs b/dpd/src/table/mod.rs index 749679ae..83ca9fed 100644 --- a/dpd/src/table/mod.rs +++ b/dpd/src/table/mod.rs @@ -229,12 +229,15 @@ impl Table { pub fn get_entries( &self, hdl: &asic::Handle, + from_hardware: bool, ) -> DpdResult> where M: MatchParse, A: ActionParse, { - self.asic_data.get_entries::(hdl).map_err(|e| e.into()) + self.asic_data + .get_entries::(hdl, from_hardware) + .map_err(|e| e.into()) } /// Ask the ASIC-level code to fetch the counter data from the ASIC's @@ -257,64 +260,93 @@ pub fn list(switch: &Switch) -> Vec { /// Given the name of a table, call into the table-specific code to get the /// entries stored in the ASIC. -pub fn get_entries(switch: &Switch, name: String) -> DpdResult { +pub fn get_entries( + switch: &Switch, + name: String, + from_hardware: bool, +) -> DpdResult { match TableType::try_from(name.as_str())? { - TableType::RouteIdxIpv4 => route_ipv4::index_dump(switch), - TableType::RouteFwdIpv4 => route_ipv4::forward_dump(switch), - TableType::RouteIdxIpv6 => route_ipv6::index_dump(switch), - TableType::RouteFwdIpv6 => route_ipv6::forward_dump(switch), - TableType::NeighborIpv6 => neighbor_ipv6::table_dump(switch), - TableType::ArpIpv4 => arp_ipv4::table_dump(switch), - TableType::NatIngressIpv4 => nat::ipv4_table_dump(switch), - TableType::NatIngressIpv6 => nat::ipv6_table_dump(switch), - TableType::AttachedSubnetIpv4 => attached_subnet_v4::table_dump(switch), - TableType::AttachedSubnetIpv6 => attached_subnet_v6::table_dump(switch), - TableType::PortIpv4 => port_ip::ipv4_table_dump(switch), - TableType::PortIpv6 => port_ip::ipv6_table_dump(switch), + TableType::RouteIdxIpv4 => { + route_ipv4::index_dump(switch, from_hardware) + } + TableType::RouteFwdIpv4 => { + route_ipv4::forward_dump(switch, from_hardware) + } + TableType::RouteIdxIpv6 => { + route_ipv6::index_dump(switch, from_hardware) + } + TableType::RouteFwdIpv6 => { + route_ipv6::forward_dump(switch, from_hardware) + } + TableType::NeighborIpv6 => { + neighbor_ipv6::table_dump(switch, from_hardware) + } + TableType::ArpIpv4 => arp_ipv4::table_dump(switch, from_hardware), + TableType::NatIngressIpv4 => { + nat::ipv4_table_dump(switch, from_hardware) + } + TableType::NatIngressIpv6 => { + nat::ipv6_table_dump(switch, from_hardware) + } + TableType::AttachedSubnetIpv4 => { + attached_subnet_v4::table_dump(switch, from_hardware) + } + TableType::AttachedSubnetIpv6 => { + attached_subnet_v6::table_dump(switch, from_hardware) + } + TableType::PortIpv4 => port_ip::ipv4_table_dump(switch, from_hardware), + TableType::PortIpv6 => port_ip::ipv6_table_dump(switch, from_hardware), TableType::PortMac => { - MacOps::::table_dump(switch) + MacOps::::table_dump(switch, from_hardware) + } + TableType::UplinkEgress => { + uplink::egress_table_dump(switch, from_hardware) + } + TableType::UplinkIngress => { + uplink::ingress_table_dump(switch, from_hardware) } - TableType::UplinkEgress => uplink::egress_table_dump(switch), - TableType::UplinkIngress => uplink::ingress_table_dump(switch), #[cfg(feature = "multicast")] TableType::McastIpv6 => { - mcast::mcast_replication::ipv6_table_dump(switch) + mcast::mcast_replication::ipv6_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::McastIpv4SrcFilter => { - mcast::mcast_src_filter::ipv4_table_dump(switch) + mcast::mcast_src_filter::ipv4_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::McastIpv6SrcFilter => { - mcast::mcast_src_filter::ipv6_table_dump(switch) + mcast::mcast_src_filter::ipv6_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::NatIngressIpv4Mcast => { - mcast::mcast_nat::ipv4_table_dump(switch) + mcast::mcast_nat::ipv4_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::NatIngressIpv6Mcast => { - mcast::mcast_nat::ipv6_table_dump(switch) + mcast::mcast_nat::ipv6_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::RouteIpv4Mcast => { - mcast::mcast_route::ipv4_table_dump(switch) + mcast::mcast_route::ipv4_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::RouteIpv6Mcast => { - mcast::mcast_route::ipv6_table_dump(switch) + mcast::mcast_route::ipv6_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::PortMacMcast => { - MacOps::::table_dump(switch) + MacOps::::table_dump( + switch, + from_hardware, + ) } #[cfg(feature = "multicast")] TableType::McastEgressDecapPorts => { - mcast::mcast_egress::bitmap_table_dump(switch) + mcast::mcast_egress::bitmap_table_dump(switch, from_hardware) } #[cfg(feature = "multicast")] TableType::McastEgressPortMapping => { - mcast::mcast_egress::port_mapping_table_dump(switch) + mcast::mcast_egress::port_mapping_table_dump(switch, from_hardware) } } } @@ -596,8 +628,11 @@ impl MacOps { } } - pub fn table_dump(s: &Switch) -> DpdResult { - s.table_dump::(T::table_type()) + pub fn table_dump( + s: &Switch, + from_hardware: bool, + ) -> DpdResult { + s.table_dump::(T::table_type(), from_hardware) } pub fn counter_fetch( diff --git a/dpd/src/table/nat.rs b/dpd/src/table/nat.rs index 70041c22..63d6e0ad 100644 --- a/dpd/src/table/nat.rs +++ b/dpd/src/table/nat.rs @@ -148,12 +148,24 @@ pub fn delete_ipv4_entry( s.table_entry_del(TableType::NatIngressIpv4, &match_key) } -pub fn ipv4_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::NatIngressIpv4) +pub fn ipv4_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::NatIngressIpv4, + from_hardware, + ) } -pub fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::NatIngressIpv6) +pub fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::NatIngressIpv6, + from_hardware, + ) } pub fn ipv4_counter_fetch( diff --git a/dpd/src/table/neighbor_ipv6.rs b/dpd/src/table/neighbor_ipv6.rs index e06425c5..1ee72082 100644 --- a/dpd/src/table/neighbor_ipv6.rs +++ b/dpd/src/table/neighbor_ipv6.rs @@ -70,8 +70,8 @@ pub fn delete_entry(s: &Switch, tgt_ip: Ipv6Addr) -> DpdResult<()> { s.table_entry_del(TableType::NeighborIpv6, &match_key) } -pub fn table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::NeighborIpv6) +pub fn table_dump(s: &Switch, from_hardware: bool) -> DpdResult { + s.table_dump::(TableType::NeighborIpv6, from_hardware) } pub fn counter_fetch( diff --git a/dpd/src/table/port_ip.rs b/dpd/src/table/port_ip.rs index f40d6818..656f7b77 100644 --- a/dpd/src/table/port_ip.rs +++ b/dpd/src/table/port_ip.rs @@ -309,12 +309,18 @@ pub fn ipv6_delete(s: &Switch, port: u16, ipv6: Ipv6Addr) -> DpdResult<()> { }) } -pub fn ipv4_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::PortIpv4) +pub fn ipv4_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::(TableType::PortIpv4, from_hardware) } -pub fn ipv6_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::PortIpv6) +pub fn ipv6_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::(TableType::PortIpv6, from_hardware) } pub fn ipv4_table_clear(s: &Switch) -> DpdResult<()> { diff --git a/dpd/src/table/route_ipv4.rs b/dpd/src/table/route_ipv4.rs index 91fbff57..75c3ddc8 100644 --- a/dpd/src/table/route_ipv4.rs +++ b/dpd/src/table/route_ipv4.rs @@ -188,12 +188,21 @@ pub fn delete_route_target(s: &Switch, idx: u16) -> DpdResult<()> { }) } -pub fn forward_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteFwdIpv4) +pub fn forward_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::RouteFwdIpv4, + from_hardware, + ) } -pub fn index_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteIdxIpv4) +pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { + s.table_dump::( + TableType::RouteIdxIpv4, + from_hardware, + ) } pub fn forward_counter_fetch( diff --git a/dpd/src/table/route_ipv6.rs b/dpd/src/table/route_ipv6.rs index 33946d1d..21427ebc 100644 --- a/dpd/src/table/route_ipv6.rs +++ b/dpd/src/table/route_ipv6.rs @@ -146,12 +146,21 @@ pub fn delete_route_target(s: &Switch, idx: u16) -> DpdResult<()> { }) } -pub fn forward_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteFwdIpv6) +pub fn forward_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::RouteFwdIpv6, + from_hardware, + ) } -pub fn index_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::RouteIdxIpv6) +pub fn index_dump(s: &Switch, from_hardware: bool) -> DpdResult { + s.table_dump::( + TableType::RouteIdxIpv6, + from_hardware, + ) } pub fn forward_counter_fetch( diff --git a/dpd/src/table/uplink.rs b/dpd/src/table/uplink.rs index f42a965a..58b38f7f 100644 --- a/dpd/src/table/uplink.rs +++ b/dpd/src/table/uplink.rs @@ -116,12 +116,24 @@ pub fn uplink_clear(s: &Switch, port: u16) -> DpdResult<()> { } } -pub fn egress_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::UplinkEgress) +pub fn egress_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::UplinkEgress, + from_hardware, + ) } -pub fn ingress_table_dump(s: &Switch) -> DpdResult { - s.table_dump::(TableType::UplinkIngress) +pub fn ingress_table_dump( + s: &Switch, + from_hardware: bool, +) -> DpdResult { + s.table_dump::( + TableType::UplinkIngress, + from_hardware, + ) } pub fn egress_counter_fetch( diff --git a/openapi/dpd/dpd-9.0.0-d5e91c.json b/openapi/dpd/dpd-9.0.0-d5e91c.json new file mode 100644 index 00000000..859a0591 --- /dev/null +++ b/openapi/dpd/dpd-9.0.0-d5e91c.json @@ -0,0 +1,10351 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Oxide Switch Dataplane Controller", + "description": "API for managing the Oxide rack switch", + "contact": { + "url": "https://oxide.computer", + "email": "api@oxide.computer" + }, + "version": "9.0.0" + }, + "paths": { + "/all-settings": { + "delete": { + "summary": "Clear all settings.", + "description": "This removes all data entirely.", + "operationId": "reset_all", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/all-settings/{tag}": { + "delete": { + "summary": "Clear all settings associated with a specific tag.", + "description": "This removes:\n\n- All ARP or NDP table entries. - All routes - All links on all switch ports", + "operationId": "reset_all_tagged", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/arp": { + "get": { + "summary": "Fetch the configured IPv4 ARP table entries.", + "operationId": "arp_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv4 ARP table entry, mapping an IPv4 address to a MAC address.", + "operationId": "arp_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all entries in the IPv4 ARP tables.", + "operationId": "arp_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/arp/{ip}": { + "get": { + "summary": "Get a single IPv4 ARP table entry, by its IPv4 address.", + "operationId": "arp_get", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove a single IPv4 ARP entry, by its IPv4 address.", + "operationId": "arp_delete", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/attached_subnet": { + "get": { + "summary": "Get all of the external subnets with internal mappings", + "operationId": "attached_subnet_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttachedSubnetEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all external subnet mappings", + "operationId": "attached_subnet_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/attached_subnet/{subnet}": { + "get": { + "summary": "Get the mapping for the given external subnet.", + "operationId": "attached_subnet_get", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Add a mapping to an internal target for an external subnet address.", + "description": "These identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external subnet.", + "operationId": "attached_subnet_create", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete the mapping for an external subnet", + "operationId": "attached_subnet_delete", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/backplane-map": { + "get": { + "summary": "Return the full backplane map.", + "description": "This returns the entire mapping of all cubbies in a rack, through the cabled backplane, and into the Sidecar main board. It also includes the Tofino \"connector\", which is included in some contexts such as reporting counters.", + "operationId": "backplane_map", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_BackplaneLink", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BackplaneLink" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/backplane-map/{port_id}": { + "get": { + "summary": "Return the backplane mapping for a single switch port.", + "operationId": "port_backplane_link", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackplaneLink" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/build-info": { + "get": { + "summary": "Return detailed build information about the `dpd` server itself.", + "operationId": "build_info", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildInfo" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/channels": { + "get": { + "summary": "Get the set of available channels for all ports.", + "description": "This returns the unused MAC channels for each physical switch port. This can be used to determine how many additional links can be created on a physical switch port.", + "operationId": "channels_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_FreeChannels", + "type": "array", + "items": { + "$ref": "#/components/schemas/FreeChannels" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fec": { + "get": { + "summary": "Get the FEC RS counters for all links.", + "operationId": "fec_rs_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkFecRSCounters", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkFecRSCounters" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fec/{port_id}/{link_id}": { + "get": { + "summary": "Get the FEC RS counters for the given link.", + "operationId": "fec_rs_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkFecRSCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fsm/{port_id}/{link_id}": { + "get": { + "summary": "Get the autonegotiation FSM counters for the given link.", + "operationId": "link_fsm_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkFsmCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/linkup": { + "get": { + "summary": "Get the LinkUp counters for all links.", + "operationId": "link_up_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkUpCounter", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkUpCounter" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/linkup/{port_id}/{link_id}": { + "get": { + "summary": "Get the LinkUp counters for the given link.", + "operationId": "link_up_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkUpCounter" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4": { + "get": { + "summary": "Get a list of all the available p4-defined counters.", + "operationId": "counter_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4/{counter}": { + "get": { + "summary": "Get the values for a given counter.", + "description": "The name of the counter should match one of those returned by the `counter_list()` call.", + "operationId": "counter_get", + "parameters": [ + { + "in": "query", + "name": "force_sync", + "description": "Force a sync of the counters from the ASIC to memory, even if the default refresh timeout hasn't been reached.", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "path", + "name": "counter", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TableCounterEntry", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableCounterEntry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4/{counter}/reset": { + "post": { + "summary": "Reset a single p4-defined counter.", + "description": "The name of the counter should match one of those returned by the `counter_list()` call.", + "operationId": "counter_reset", + "parameters": [ + { + "in": "path", + "name": "counter", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/pcs": { + "get": { + "summary": "Get the physical coding sublayer (PCS) counters for all links.", + "operationId": "pcs_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkPcsCounters", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkPcsCounters" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/pcs/{port_id}/{link_id}": { + "get": { + "summary": "Get the Physical Coding Sublayer (PCS) counters for the given link.", + "operationId": "pcs_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkPcsCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/rmon/{port_id}/{link_id}/all": { + "get": { + "summary": "Get the full set of traffic counters for the given link.", + "operationId": "rmon_counters_get_all", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkRMonCountersAll" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/rmon/{port_id}/{link_id}/subset": { + "get": { + "summary": "Get the most relevant subset of traffic counters for the given link.", + "operationId": "rmon_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkRMonCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/dpd-uptime": { + "get": { + "summary": "Return the server uptime.", + "operationId": "dpd_uptime", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "int64", + "type": "integer", + "format": "int64" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/dpd-version": { + "get": { + "summary": "Return the version of the `dpd` server itself.", + "operationId": "dpd_version", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/leds": { + "get": { + "summary": "Return the state of all attention LEDs on the Sidecar QSFP ports.", + "operationId": "leds_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_Led", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Led" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/links": { + "get": { + "summary": "List all links, on all switch ports.", + "operationId": "link_list_all", + "parameters": [ + { + "in": "query", + "name": "filter", + "description": "Filter links to those whose name contains the provided string.\n\nIf not provided, then all links are returned.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Link", + "type": "array", + "items": { + "$ref": "#/components/schemas/Link" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/links/tfport_data": { + "get": { + "summary": "Collect the link data consumed by `tfportd`. This app-specific convenience", + "description": "routine is meant to reduce the time and traffic expended on this once-per-second operation, by consolidating multiple per-link requests into a single per-switch request.", + "operationId": "tfport_data", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TfportData", + "type": "array", + "items": { + "$ref": "#/components/schemas/TfportData" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv4": { + "get": { + "summary": "Get loopback IPv4 addresses.", + "operationId": "loopback_ipv4_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv4Entry", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Add a loopback IPv4.", + "operationId": "loopback_ipv4_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv4/{ipv4}": { + "delete": { + "summary": "Remove one loopback IPv4 address.", + "operationId": "loopback_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv6": { + "get": { + "summary": "Get loopback IPv6 addresses.", + "operationId": "loopback_ipv6_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv6Entry", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Add a loopback IPv6.", + "operationId": "loopback_ipv6_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv6/{ipv6}": { + "delete": { + "summary": "Remove one loopback IPv6 address.", + "operationId": "loopback_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/external-groups": { + "post": { + "summary": "Create an external-only multicast group configuration.", + "description": "External-only groups are used for IPv4 and non-admin-local IPv6 multicast traffic that doesn't require replication infrastructure. These groups use simple forwarding tables and require a NAT target.", + "operationId": "multicast_group_create_external", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupCreateExternalEntry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupExternalResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/external-groups/{group_ip}": { + "put": { + "summary": "Update an external-only multicast group configuration for a given group IP address.", + "description": "External-only groups are used for IPv4 and non-admin-local IPv6 multicast traffic that doesn't require replication infrastructure.\n\nThe `tag` query parameter must match the group's existing tag.", + "operationId": "multicast_group_update_external", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + }, + { + "in": "query", + "name": "tag", + "description": "Tag that must match the group's existing tag.", + "required": true, + "schema": { + "$ref": "#/components/schemas/MulticastTag" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUpdateExternalEntry" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupExternalResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/groups": { + "get": { + "summary": "List all multicast groups.", + "operationId": "multicast_groups_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponseResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Reset all multicast group configurations.", + "operationId": "multicast_reset", + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/groups/{group_ip}": { + "get": { + "summary": "Get the multicast group configuration for a given group IP address.", + "operationId": "multicast_group_get", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete a multicast group configuration by IP address (API version 4+).", + "description": "All groups have tags (auto-generated if not provided at creation). The tag query parameter must match the group's existing tag.", + "operationId": "multicast_group_delete", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + }, + { + "in": "query", + "name": "tag", + "description": "Tag that must match the group's existing tag.", + "required": true, + "schema": { + "$ref": "#/components/schemas/MulticastTag" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/tags/{tag}": { + "get": { + "summary": "List all multicast groups with a given tag.", + "description": "Returns paginated multicast groups matching the specified tag. Tags are assigned at group creation and are immutable. Use this endpoint to find all groups associated with a specific client or component.", + "operationId": "multicast_groups_list_by_tag", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "$ref": "#/components/schemas/MulticastTag" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponseResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Delete all multicast groups (and associated routes) with a given tag.", + "description": "This is idempotent: if no groups exist with the given tag, the operation returns success (the desired end state of \"no groups with this tag\" is achieved). Use this endpoint for bulk cleanup of all groups associated with a specific client or component.", + "operationId": "multicast_reset_by_tag", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "$ref": "#/components/schemas/MulticastTag" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/underlay-groups": { + "post": { + "summary": "Create an underlay (internal) multicast group configuration.", + "operationId": "multicast_group_create_underlay", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupCreateUnderlayEntry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/underlay-groups/{group_ip}": { + "get": { + "summary": "Get an underlay (internal) multicast group configuration.", + "operationId": "multicast_group_get_underlay", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "$ref": "#/components/schemas/UnderlayMulticastIpv6" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Update an underlay (internal) multicast group configuration.", + "description": "The `tag` query parameter must match the group's existing tag.", + "operationId": "multicast_group_update_underlay", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "$ref": "#/components/schemas/UnderlayMulticastIpv6" + } + }, + { + "in": "query", + "name": "tag", + "description": "Tag that must match the group's existing tag.", + "required": true, + "schema": { + "$ref": "#/components/schemas/MulticastTag" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUpdateUnderlayEntry" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/untagged": { + "delete": { + "summary": "Delete all multicast groups (and associated routes) without a tag.", + "description": "DEPRECATED: All groups have default tags generated at creation time. This endpoint returns HTTP 410 Gone. Use `multicast_reset_by_tag` with the tag returned from group creation instead.", + "operationId": "multicast_reset_untagged", + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4": { + "get": { + "summary": "Get all of the external addresses in use for IPv4 NAT mappings.", + "operationId": "nat_ipv4_addresses_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ipv4ResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all IPv4 NAT mappings.", + "operationId": "nat_ipv4_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4/{ipv4}": { + "get": { + "summary": "Get all of the external->internal NAT mappings for a given IPv4 address.", + "operationId": "nat_ipv4_list", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4NatResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/nat/ipv4/{ipv4}/{low}": { + "get": { + "summary": "Get the external->internal NAT mapping for the given address/port", + "operationId": "nat_ipv4_get", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear the NAT mappings for an IPv4 address and starting L3 port.", + "operationId": "nat_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4/{ipv4}/{low}/{high}": { + "put": { + "summary": "Add an external->internal NAT mapping for the given address/port range", + "description": "This maps an external IPv6 address and L3 port range to: - A gimlet's IPv6 address - A gimlet's MAC address - A Geneve VNI\n\nThese identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external IPv6 and port range when making connections outside of an Oxide rack.", + "operationId": "nat_ipv4_create", + "parameters": [ + { + "in": "path", + "name": "high", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6": { + "get": { + "summary": "Get all of the external addresses in use for NAT mappings.", + "operationId": "nat_ipv6_addresses_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ipv6ResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all IPv6 NAT mappings.", + "operationId": "nat_ipv6_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6/{ipv6}": { + "get": { + "summary": "Get all of the external->internal NAT mappings for a given address.", + "operationId": "nat_ipv6_list", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6NatResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/nat/ipv6/{ipv6}/{low}": { + "get": { + "summary": "Get the external->internal NAT mapping for the given address and starting L3", + "description": "port.", + "operationId": "nat_ipv6_get", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete the NAT mapping for an IPv6 address and starting L3 port.", + "operationId": "nat_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6/{ipv6}/{low}/{high}": { + "put": { + "summary": "Add an external->internal NAT mapping for the given address and L3 port", + "description": "range.\n\nThis maps an external IPv6 address and L3 port range to: - A gimlet's IPv6 address - A gimlet's MAC address - A Geneve VNI\n\nThese identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external IPv6 and port range when making connections outside of an Oxide rack.", + "operationId": "nat_ipv6_create", + "parameters": [ + { + "in": "path", + "name": "high", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ndp": { + "get": { + "summary": "Fetch the IPv6 NDP table entries.", + "description": "This returns a paginated list of all IPv6 neighbors directly connected to the switch.", + "operationId": "ndp_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv6 NDP entry, mapping an IPv6 address to a MAC address.", + "operationId": "ndp_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all entries in the the IPv6 NDP tables.", + "operationId": "ndp_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ndp/{ip}": { + "get": { + "summary": "Get a single IPv6 NDP table entry, by its IPv6 address.", + "operationId": "ndp_get", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove an IPv6 NDP entry, by its IPv6 address.", + "operationId": "ndp_delete", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/port/{port_id}/settings": { + "get": { + "summary": "Get port settings atomically.", + "operationId": "port_settings_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Apply port settings atomically.", + "description": "These settings will be applied holistically, and to the extent possible atomically to a given port. In the event of a failure a rollback is attempted. If the rollback fails there will be inconsistent state. This failure mode returns the error code \"rollback failure\". For more details see the docs on the [`PortSettings`] type.", + "operationId": "port_settings_apply", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear port settings atomically.", + "operationId": "port_settings_clear", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports": { + "get": { + "summary": "List all switch ports on the system.", + "operationId": "port_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_PortId", + "type": "array", + "items": { + "$ref": "#/components/schemas/PortId" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}": { + "get": { + "summary": "Return information about a single switch port.", + "operationId": "port_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPort" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/led": { + "get": { + "summary": "Return the current state of the attention LED on a front-facing QSFP port.", + "operationId": "led_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Led" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Override the current state of the attention LED on a front-facing QSFP port.", + "description": "The attention LED normally follows the state of the port itself. For example, if a transceiver is powered and operating normally, then the LED is solid on. An unexpected power fault would then be reflected by powering off the LED.\n\nThe client may override this behavior, explicitly setting the LED to a specified state. This can be undone, sending the LED back to its default policy, with the endpoint `/ports/{port_id}/led/auto`.", + "operationId": "led_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LedState" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/led/auto": { + "put": { + "summary": "Set the LED policy to automatic.", + "description": "The automatic LED policy ensures that the state of the LED follows the state of the switch port itself.", + "operationId": "led_set_auto", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links": { + "get": { + "summary": "List the links within a single switch port.", + "operationId": "link_list", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Link", + "type": "array", + "items": { + "$ref": "#/components/schemas/Link" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Create a link on a switch port.", + "description": "Create an interface that can be used for sending Ethernet frames on the provided switch port. This will use the first available lanes in the physical port to create an interface of the desired speed, if possible.", + "operationId": "link_create", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkId" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}": { + "get": { + "summary": "Get an existing link by ID.", + "operationId": "link_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete a link from a switch port.", + "operationId": "link_delete", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/autoneg": { + "get": { + "summary": "Return whether the link is configured to use autonegotiation with its peer", + "description": "link.", + "operationId": "link_autoneg_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is configured to use autonegotation with its peer link.", + "operationId": "link_autoneg_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ber": { + "get": { + "summary": "Return the estimated bit-error rate (BER) for a link.", + "operationId": "link_ber_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ber" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/enabled": { + "get": { + "summary": "Return whether the link is enabled.", + "operationId": "link_enabled_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Enable or disable a link.", + "operationId": "link_enabled_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/fault": { + "get": { + "summary": "Return any fault currently set on this link", + "operationId": "link_fault_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FaultCondition" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Inject a fault on this link", + "operationId": "link_fault_inject", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear any fault currently set on this link", + "operationId": "link_fault_clear", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/history": { + "get": { + "summary": "Get the event history for the given link.", + "operationId": "link_history_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkHistory" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv4": { + "get": { + "summary": "List the IPv4 addresses associated with a link.", + "operationId": "link_ipv4_list", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4EntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv4 address to a link.", + "operationId": "link_ipv4_create", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear all IPv4 addresses from a link.", + "operationId": "link_ipv4_reset", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv4/{address}": { + "delete": { + "summary": "Remove an IPv4 address from a link.", + "operationId": "link_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The IPv4 address on which to operate.", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6": { + "get": { + "summary": "List the IPv6 addresses associated with a link.", + "operationId": "link_ipv6_list", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6EntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv6 address to a link.", + "operationId": "link_ipv6_create", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear all IPv6 addresses from a link.", + "operationId": "link_ipv6_reset", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6/{address}": { + "delete": { + "summary": "Remove an IPv6 address from a link.", + "operationId": "link_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The IPv6 address on which to operate.", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6_enabled": { + "get": { + "summary": "Return whether the link is configured to act as an IPv6 endpoint", + "operationId": "link_ipv6_enabled_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is configured to act as an IPv6 endpoint", + "operationId": "link_ipv6_enabled_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/kr": { + "get": { + "summary": "Return whether the link is in KR mode.", + "description": "\"KR\" refers to the Ethernet standard for the link, which are defined in various clauses of the IEEE 802.3 specification. \"K\" is used to denote a link over an electrical cabled backplane, and \"R\" refers to \"scrambled encoding\", a 64B/66B bit-encoding scheme.\n\nThus this should be true iff a link is on the cabled backplane.", + "operationId": "link_kr_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Enable or disable a link.", + "operationId": "link_kr_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/linkup": { + "get": { + "summary": "Return whether a link is up.", + "operationId": "link_linkup_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/mac": { + "get": { + "summary": "Get a link's MAC address.", + "operationId": "link_mac_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MacAddr" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set a link's MAC address.", + "operationId": "link_mac_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MacAddr" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/prbs": { + "get": { + "summary": "Return the link's PRBS speed and mode.", + "description": "During link training, a pseudorandom bit sequence (PRBS) is used to allow each side to synchronize their clocks and set various parameters on the underlying circuitry (such as filter gains).", + "operationId": "link_prbs_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortPrbsMode" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set a link's PRBS speed and mode.", + "operationId": "link_prbs_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortPrbsMode" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/adapt": { + "get": { + "summary": "Get the per-lane adaptation counts for each lane on this link", + "operationId": "link_rx_adapt_count_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_DfeAdaptationState", + "type": "array", + "items": { + "$ref": "#/components/schemas/DfeAdaptationState" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/anlt_status": { + "get": { + "summary": "Get the per-lane AN/LT status for each lane on this link", + "operationId": "link_an_lt_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnLtStatus" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/enc_speed": { + "get": { + "summary": "Get the per-lane speed and encoding for each lane on this link", + "operationId": "link_enc_speed_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_EncSpeed", + "type": "array", + "items": { + "$ref": "#/components/schemas/EncSpeed" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/eye": { + "get": { + "summary": "Get the per-lane eye measurements for each lane on this link", + "operationId": "link_eye_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SerdesEye", + "type": "array", + "items": { + "$ref": "#/components/schemas/SerdesEye" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/lane_map": { + "get": { + "summary": "Get the logical->physical mappings for each lane in this port", + "operationId": "lane_map_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LaneMap" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/rx_sig": { + "get": { + "summary": "Get the per-lane rx signal info for each lane on this link", + "operationId": "link_rx_sig_info_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_RxSigInfo", + "type": "array", + "items": { + "$ref": "#/components/schemas/RxSigInfo" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/tx_eq": { + "get": { + "summary": "Get the per-lane tx eq settings for each lane on this link", + "operationId": "link_tx_eq_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TxEqSwHw", + "type": "array", + "items": { + "$ref": "#/components/schemas/TxEqSwHw" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Update the per-lane tx eq settings for all lanes on this link", + "operationId": "link_tx_eq_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxEq" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/uplink": { + "get": { + "summary": "Return whether a port is intended to carry uplink traffic", + "operationId": "link_uplink_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is intended to carry uplink traffic", + "operationId": "link_uplink_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/management-mode": { + "get": { + "summary": "Return the current management mode of a QSFP switch port.", + "operationId": "management_mode_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagementMode" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set the current management mode of a QSFP switch port.", + "operationId": "management_mode_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagementMode" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver": { + "get": { + "summary": "Return the information about a port's transceiver.", + "description": "This returns the status (presence, power state, etc) of the transceiver along with its identifying information. If the port is an optical switch port, but has no transceiver, then the identifying information is empty.\n\nIf the switch port is not a QSFP port, and thus could never have a transceiver, then \"Not Found\" is returned.", + "operationId": "transceiver_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transceiver" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/datapath": { + "get": { + "summary": "Fetch the state of the datapath for the provided transceiver.", + "operationId": "transceiver_datapath_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Datapath" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/monitors": { + "get": { + "summary": "Fetch the monitored environmental information for the provided transceiver.", + "operationId": "transceiver_monitors_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Monitors" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/power": { + "get": { + "summary": "Return the power state of a transceiver.", + "operationId": "transceiver_power_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PowerState" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Control the power state of a transceiver.", + "operationId": "transceiver_power_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PowerState" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/reset": { + "post": { + "summary": "Effect a module-level reset of a QSFP transceiver.", + "description": "If the QSFP port has no transceiver or is not a QSFP port, then a client error is returned.", + "operationId": "transceiver_reset", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4": { + "get": { + "summary": "Fetch the configured IPv4 routes, mapping IPv4 CIDR blocks to the switch port", + "description": "used for sending out that traffic, and optionally a gateway.", + "operationId": "route_ipv4_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RoutesResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "put": { + "summary": "Route an IPv4 subnet to a link and a nexthop gateway (IPv4 or IPv6).", + "description": "This call can be used to create a new single-path route or to replace any existing routes with a new single-path route.", + "operationId": "route_ipv4_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Route an IPv4 subnet to a link and a nexthop gateway (IPv4 or IPv6).", + "description": "This call can be used to create a new single-path route or to add new targets to a multipath route.", + "operationId": "route_ipv4_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4/{cidr}": { + "get": { + "summary": "Get the configured route for the given IPv4 subnet.", + "operationId": "route_ipv4_get", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv4 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Route", + "type": "array", + "items": { + "$ref": "#/components/schemas/Route" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all targets for the given subnet", + "operationId": "route_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv4 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}": { + "delete": { + "summary": "Remove a single target for the given IPv4 subnet (IPv4 or IPv6 next hop)", + "operationId": "route_ipv4_delete_target", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The subnet being routed", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "path", + "name": "tgt_ip", + "description": "The next hop in the route (IPv4 or IPv6)", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6": { + "get": { + "summary": "Fetch the configured IPv6 routes, mapping IPv6 CIDR blocks to the switch port", + "description": "used for sending out that traffic, and optionally a gateway.", + "operationId": "route_ipv6_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RoutesResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "put": { + "summary": "Route an IPv6 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to replace any existing routes with a new single-path route.", + "operationId": "route_ipv6_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Route an IPv6 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to add new targets to a multipath route.", + "operationId": "route_ipv6_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6/{cidr}": { + "get": { + "summary": "Get a single IPv6 route, by its IPv6 CIDR block.", + "operationId": "route_ipv6_get", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv6 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv6Route", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Route" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove an IPv6 route, by its IPv6 CIDR block.", + "operationId": "route_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv6 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6/{cidr}/{port_id}/{link_id}/{tgt_ip}": { + "delete": { + "summary": "Remove a single target for the given IPv6 subnet", + "operationId": "route_ipv6_delete_target", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The subnet being routed", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "path", + "name": "tgt_ip", + "description": "The next hop in the IPv4 route", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/rpw/nat/gen": { + "get": { + "summary": "Get NAT generation number", + "operationId": "nat_generation", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "int64", + "type": "integer", + "format": "int64" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/rpw/nat/trigger": { + "post": { + "summary": "Trigger NAT Reconciliation", + "operationId": "nat_trigger_update", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Null", + "type": "string", + "enum": [ + null + ] + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/snapshot/capture": { + "post": { + "summary": "Capture a PHV snapshot: create snapshot, set triggers, arm, wait", + "description": "for trigger, read capture, decode fields, and clean up.", + "operationId": "snapshot_capture", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotCreate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotResult" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/snapshot/scope": { + "post": { + "summary": "Check which fields are in scope at a given stage.", + "operationId": "snapshot_scope", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotScopeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SnapshotFieldScope", + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotFieldScope" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/switch/identifiers": { + "get": { + "summary": "Get switch identifiers.", + "description": "This endpoint returns the switch identifiers, which can be used for consistent field definitions across oximeter time series schemas.", + "operationId": "switch_identifiers", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchIdentifiers" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table": { + "get": { + "summary": "Get the list of P4 tables", + "operationId": "table_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table/{table}/counters": { + "get": { + "summary": "Get any counter data from a single P4 match-action table.", + "description": "The name of the table should match one of those returned by the `table_list()` call.", + "operationId": "table_counters", + "parameters": [ + { + "in": "query", + "name": "force_sync", + "description": "Force a sync of the counters from the ASIC to memory, even if the default refresh timeout hasn't been reached.", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "path", + "name": "table", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TableCounterEntry", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableCounterEntry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table/{table}/dump": { + "get": { + "summary": "Get the contents of a single P4 table.", + "description": "The name of the table should match one of those returned by the `table_list()` call.", + "operationId": "table_dump", + "parameters": [ + { + "in": "query", + "name": "from_hardware", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "path", + "name": "table", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/transceivers": { + "get": { + "summary": "Return information about all QSFP transceivers.", + "operationId": "transceivers_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_Transceiver", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Transceiver" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "schemas": { + "AnLtStatus": { + "description": "A collection of the data involved in the autonegiation/link-training process", + "type": "object", + "properties": { + "lanes": { + "description": "The per-lane status", + "type": "array", + "items": { + "$ref": "#/components/schemas/LaneStatus" + } + }, + "lp_pages": { + "description": "The base and extended pages received from the link partner", + "allOf": [ + { + "$ref": "#/components/schemas/LpPages" + } + ] + } + }, + "required": [ + "lanes", + "lp_pages" + ] + }, + "AnStatus": { + "description": "State of a single lane during autonegotiation", + "type": "object", + "properties": { + "an_ability": { + "description": "Are we capable of AN?", + "type": "boolean" + }, + "an_complete": { + "description": "Is autonegotiation complete?", + "type": "boolean" + }, + "ext_np_status": { + "description": "Is extended page format supported?", + "type": "boolean" + }, + "link_status": { + "description": "Allegedly: is the link up? In practice, this always seems to be false? TODO: investigate this", + "type": "boolean" + }, + "lp_an_ability": { + "description": "Can the link partner perform AN?", + "type": "boolean" + }, + "page_rcvd": { + "description": "has a base page been received?", + "type": "boolean" + }, + "parallel_detect_fault": { + "description": "A fault has been detected via the parallel detection function", + "type": "boolean" + }, + "remote_fault": { + "description": "Remote fault detected", + "type": "boolean" + } + }, + "required": [ + "an_ability", + "an_complete", + "ext_np_status", + "link_status", + "lp_an_ability", + "page_rcvd", + "parallel_detect_fault", + "remote_fault" + ] + }, + "ApplicationDescriptor": { + "description": "An Application Descriptor describes the supported datapath configurations.\n\nThis is a CMIS-specific concept. It's used for modules to advertise how it can be used by the host. Each application describes the host-side electrical interface; the media-side interface; the number of lanes required; etc.\n\nHost-side software can select one of these applications to instruct the module to use a specific set of lanes, with the interface on either side of the module.", + "type": "object", + "properties": { + "host_id": { + "description": "The electrical interface with the host side.", + "type": "string" + }, + "host_lane_assignment_options": { + "description": "The lanes on the host-side supporting this application.\n\nThis is a bit mask with a 1 identifying the lowest lane in a consecutive group of lanes to which the application can be assigned. This must be used with the `host_lane_count`. For example a value of `0b0000_0001` with a host lane count of 4 indicates that the first 4 lanes may be used in this application.\n\nAn application may support starting from multiple lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "host_lane_count": { + "description": "The number of host-side lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "media_id": { + "description": "The interface, optical or copper, with the media side.", + "allOf": [ + { + "$ref": "#/components/schemas/MediaInterfaceId" + } + ] + }, + "media_lane_assignment_options": { + "description": "The lanes on the media-side supporting this application.\n\nSee `host_lane_assignment_options` for details.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "media_lane_count": { + "description": "The number of media-side lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "host_id", + "host_lane_assignment_options", + "host_lane_count", + "media_id", + "media_lane_assignment_options", + "media_lane_count" + ] + }, + "ArpEntry": { + "description": "Represents the mapping of an IP address to a MAC address.", + "type": "object", + "properties": { + "ip": { + "description": "The IP address for the entry.", + "type": "string", + "format": "ip" + }, + "mac": { + "description": "The MAC address to which `ip` maps.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "tag": { + "description": "A tag used to associate this entry with a client.", + "type": "string" + }, + "update": { + "description": "The time the entry was updated", + "type": "string" + } + }, + "required": [ + "ip", + "mac", + "tag", + "update" + ] + }, + "ArpEntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/ArpEntry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "AttachedSubnetEntry": { + "description": "represents an external subnet mapping", + "type": "object", + "properties": { + "subnet": { + "$ref": "#/components/schemas/IpNet" + }, + "tgt": { + "$ref": "#/components/schemas/InstanceTarget" + } + }, + "required": [ + "subnet", + "tgt" + ] + }, + "AttachedSubnetEntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/AttachedSubnetEntry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Aux1Monitor": { + "description": "The first auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The monitored property is custom, i.e., part-specific.", + "type": "object", + "properties": { + "custom": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "custom" + ], + "additionalProperties": false + }, + { + "description": "The current of the laser thermoelectric cooler.\n\nFor actively-cooled laser systems, this specifies the percentage of the maximum current the thermoelectric cooler supports. If the percentage is positive, the cooler is heating the laser. If negative, the cooler is cooling the laser.", + "type": "object", + "properties": { + "tec_current": { + "type": "number", + "format": "float" + } + }, + "required": [ + "tec_current" + ], + "additionalProperties": false + } + ] + }, + "Aux2Monitor": { + "description": "The second auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The temperature of the laser itself (degrees C).", + "type": "object", + "properties": { + "laser_temperature": { + "type": "number", + "format": "float" + } + }, + "required": [ + "laser_temperature" + ], + "additionalProperties": false + }, + { + "description": "The current of the laser thermoelectric cooler.\n\nFor actively-cooled laser systems, this specifies the percentage of the maximum current the thermoelectric cooler supports. If the percentage is positive, the cooler is heating the laser. If negative, the cooler is cooling the laser.", + "type": "object", + "properties": { + "tec_current": { + "type": "number", + "format": "float" + } + }, + "required": [ + "tec_current" + ], + "additionalProperties": false + } + ] + }, + "Aux3Monitor": { + "description": "The third auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The temperature of the laser itself (degrees C).", + "type": "object", + "properties": { + "laser_temperature": { + "type": "number", + "format": "float" + } + }, + "required": [ + "laser_temperature" + ], + "additionalProperties": false + }, + { + "description": "Measured voltage of an additional power supply (Volts).", + "type": "object", + "properties": { + "additional_supply_voltage": { + "type": "number", + "format": "float" + } + }, + "required": [ + "additional_supply_voltage" + ], + "additionalProperties": false + } + ] + }, + "AuxMonitors": { + "description": "Auxlliary monitored values for CMIS modules.", + "type": "object", + "properties": { + "aux1": { + "nullable": true, + "description": "Auxlliary monitor 1, either a custom value or TEC current.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux1Monitor" + } + ] + }, + "aux2": { + "nullable": true, + "description": "Auxlliary monitor 1, either laser temperature or TEC current.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux2Monitor" + } + ] + }, + "aux3": { + "nullable": true, + "description": "Auxlliary monitor 1, either laser temperature or additional supply voltage.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux3Monitor" + } + ] + }, + "custom": { + "nullable": true, + "description": "A custom monitor. The value here is entirely vendor- and part-specific, so the part's data sheet must be consulted. The value may be either a signed or unsigned 16-bit integer, and so is included as raw bytes.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "BackplaneCableLeg": { + "description": "The leg of the backplane cable.\n\nThis describes the leg on the actual backplane cable that connects the Sidecar chassis connector to a cubby endpoint.", + "type": "string", + "enum": [ + "A", + "B", + "C", + "D" + ] + }, + "BackplaneLink": { + "description": "A single point-to-point connection on the cabled backplane.\n\nThis describes a single link from the Sidecar switch to a cubby, via the cabled backplane. It ultimately maps the Tofino ASIC pins to the cubby at which that link terminates. This path follows the Sidecar internal cable; the Sidecar chassis connector; and the backplane cable itself. This is used to map the Tofino driver's \"connector\" number (an index in its possible pinouts) through the backplane to our logical cubby numbering.", + "type": "object", + "properties": { + "backplane_leg": { + "$ref": "#/components/schemas/BackplaneCableLeg" + }, + "cubby": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "sidecar_connector": { + "$ref": "#/components/schemas/SidecarConnector" + }, + "sidecar_leg": { + "$ref": "#/components/schemas/SidecarCableLeg" + }, + "tofino_connector": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "backplane_leg", + "cubby", + "sidecar_connector", + "sidecar_leg", + "tofino_connector" + ] + }, + "Ber": { + "description": "Reports the bit-error rate (BER) for a link.", + "type": "object", + "properties": { + "ber": { + "description": "Estimated BER per-lane.", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "symbol_errors": { + "description": "Counters of symbol errors per-lane.", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "total_ber": { + "description": "Aggregate BER on the link.", + "type": "number", + "format": "float" + } + }, + "required": [ + "ber", + "symbol_errors", + "total_ber" + ] + }, + "BuildInfo": { + "description": "Detailed build information about `dpd`.", + "type": "object", + "properties": { + "cargo_triple": { + "type": "string" + }, + "debug": { + "type": "boolean" + }, + "git_branch": { + "type": "string" + }, + "git_commit_timestamp": { + "type": "string" + }, + "git_sha": { + "type": "string" + }, + "opt_level": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "rustc_channel": { + "type": "string" + }, + "rustc_commit_sha": { + "type": "string" + }, + "rustc_host_triple": { + "type": "string" + }, + "rustc_semver": { + "type": "string" + }, + "sde_commit_sha": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "cargo_triple", + "debug", + "git_branch", + "git_commit_timestamp", + "git_sha", + "opt_level", + "rustc_channel", + "rustc_commit_sha", + "rustc_host_triple", + "rustc_semver", + "sde_commit_sha", + "version" + ] + }, + "CmisDatapath": { + "description": "A datapath in a CMIS module.\n\nIn contrast to SFF-8636, CMIS makes first-class the concept of a datapath: a set of lanes and all the associated machinery involved in the transfer of data. This includes:\n\n- The \"application descriptor\" which is the host and media interfaces, and the lanes on each side used to transfer data; - The state of the datapath in a well-defined finite state machine (see CMIS 5.0 section 6.3.3); - The flags indicating how the datapath components are operating, such as receiving an input Rx signal or whether the transmitter is disabled.", + "type": "object", + "properties": { + "application": { + "description": "The application descriptor for this datapath.", + "allOf": [ + { + "$ref": "#/components/schemas/ApplicationDescriptor" + } + ] + }, + "lane_status": { + "description": "The status bits for each lane in the datapath.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CmisLaneStatus" + } + } + }, + "required": [ + "application", + "lane_status" + ] + }, + "CmisLaneStatus": { + "description": "The status of a single CMIS lane.\n\nIf any particular control or status value is unsupported by a module, it is `None`.", + "type": "object", + "properties": { + "rx_auto_squelch_disable": { + "nullable": true, + "description": "Whether the host-side has disabled the Rx auto-squelch.\n\nThe module can implement automatic squelching of the Rx output, if the media-side input signal isn't valid. This indicates whether the host has disabled such a setting.", + "type": "boolean" + }, + "rx_lol": { + "nullable": true, + "description": "Media-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the media-side signal (usually optical).", + "type": "boolean" + }, + "rx_los": { + "nullable": true, + "description": "Media-side loss of signal flag.\n\nThis is true if there is no detected input signal from the media-side (usually optical).", + "type": "boolean" + }, + "rx_output_enabled": { + "nullable": true, + "description": "Whether the Rx output is enabled.\n\nThe host may control this to disable the electrical output from the module to the host.", + "type": "boolean" + }, + "rx_output_polarity": { + "nullable": true, + "description": "The Rx output polarity.\n\nThis indicates a host-side control that flips the polarity of the host-side output signal.", + "allOf": [ + { + "$ref": "#/components/schemas/LanePolarity" + } + ] + }, + "rx_output_status": { + "description": "Status of host-side Rx output.\n\nThis indicates whether the Rx output is sending a valid signal to the host. Note that this is `Invalid` if the output is either muted (such as squelched) or explicitly disabled.", + "allOf": [ + { + "$ref": "#/components/schemas/OutputStatus" + } + ] + }, + "state": { + "description": "The datapath state of this lane.\n\nSee CMIS 5.0 section 8.9.1 for details.", + "type": "string" + }, + "tx_adaptive_eq_fail": { + "nullable": true, + "description": "A failure in the Tx adaptive input equalization.", + "type": "boolean" + }, + "tx_auto_squelch_disable": { + "nullable": true, + "description": "Whether the host-side has disabled the Tx auto-squelch.\n\nThe module can implement automatic squelching of the Tx output, if the host-side input signal isn't valid. This indicates whether the host has disabled such a setting.", + "type": "boolean" + }, + "tx_failure": { + "nullable": true, + "description": "General Tx failure flag.\n\nThis indicates that an internal and unspecified malfunction has occurred on the Tx lane.", + "type": "boolean" + }, + "tx_force_squelch": { + "nullable": true, + "description": "Whether the host-side has force-squelched the Tx output.\n\nThis indicates that the host can _force_ squelching the output if the signal is not valid.", + "type": "boolean" + }, + "tx_input_polarity": { + "nullable": true, + "description": "The Tx input polarity.\n\nThis indicates a host-side control that flips the polarity of the host-side input signal.", + "allOf": [ + { + "$ref": "#/components/schemas/LanePolarity" + } + ] + }, + "tx_lol": { + "nullable": true, + "description": "Host-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the host-side electrical signal.", + "type": "boolean" + }, + "tx_los": { + "nullable": true, + "description": "Host-side loss of signal flag.\n\nThis is true if there is no detected electrical signal from the host-side serdes.", + "type": "boolean" + }, + "tx_output_enabled": { + "nullable": true, + "description": "Whether the Tx output is enabled.", + "type": "boolean" + }, + "tx_output_status": { + "description": "Status of media-side Tx output.\n\nThis indicates whether the Rx output is sending a valid signal to the media itself. Note that this is `Invalid` if the output is either muted (such as squelched) or explicitly disabled.", + "allOf": [ + { + "$ref": "#/components/schemas/OutputStatus" + } + ] + } + }, + "required": [ + "rx_output_status", + "state", + "tx_output_status" + ] + }, + "CounterData": { + "description": "For a counter, this contains the number of bytes, packets, or both that were counted. XXX: Ideally this would be a data-bearing enum, with variants for Pkts, Bytes, and PktsAndBytes. However OpenApi doesn't yet have the necessary support, so we're left with this clumsier representation.", + "type": "object", + "properties": { + "bytes": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pkts": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + } + } + }, + "Datapath": { + "description": "Information about a transceiver's datapath.\n\nThis includes state related to the low-level eletrical and optical path through which bits flow. This includes flags like loss-of-signal / loss-of-lock; transmitter enablement state; and equalization parameters.", + "oneOf": [ + { + "description": "A number of datapaths in a CMIS module.\n\nCMIS modules may have a large number of supported configurations of their various lanes, each called an \"application\". These are described by the `ApplicationDescriptor` type, which mirrors CMIS 5.0 table 8-18. Each descriptor is identified by an \"Application Selector Code\", which is just its index in the section of the memory map describing them.\n\nEach lane can be used in zero or more applications, however, it may exist in at most one application at a time. These active applications, of which there may be more than one, are keyed by their codes in the contained mapping.", + "type": "object", + "properties": { + "cmis": { + "type": "object", + "properties": { + "connector": { + "description": "The type of free-side connector", + "type": "string" + }, + "datapaths": { + "description": "Mapping from \"application selector\" ID to its datapath information.\n\nThe datapath inclues the lanes used; host electrical interface; media interface; and a lot more about the state of the path.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CmisDatapath" + } + }, + "supported_lanes": { + "description": "A bit mask with a 1 in bit `i` if the `i`th lane is supported.", + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "connector", + "datapaths", + "supported_lanes" + ] + } + }, + "required": [ + "cmis" + ], + "additionalProperties": false + }, + { + "description": "Datapath state about each lane in an SFF-8636 module.", + "type": "object", + "properties": { + "sff8636": { + "type": "object", + "properties": { + "connector": { + "description": "The type of a media-side connector.\n\nThese values come from SFF-8024 Rev 4.10 Table 4-3.", + "type": "string" + }, + "lanes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sff8636Datapath" + }, + "minItems": 4, + "maxItems": 4 + }, + "specification": { + "$ref": "#/components/schemas/SffComplianceCode" + } + }, + "required": [ + "connector", + "lanes", + "specification" + ] + } + }, + "required": [ + "sff8636" + ], + "additionalProperties": false + } + ] + }, + "DfeAdaptationState": { + "description": "Rx DFE adaptation information", + "type": "object", + "properties": { + "adapt_cnt": { + "description": "Total DFE attempts", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "adapt_done": { + "description": "DFE complete", + "type": "boolean" + }, + "link_lost_cnt": { + "description": "Times the signal was lost since the last read", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readapt_cnt": { + "description": "DFE attempts since the last read", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "adapt_cnt", + "adapt_done", + "link_lost_cnt", + "readapt_cnt" + ] + }, + "Direction": { + "description": "Direction a multicast group member is reached by.\n\n`External` group members must have any packet encapsulation removed before packet delivery.", + "type": "string", + "enum": [ + "Underlay", + "External" + ] + }, + "ElectricalMode": { + "description": "The electrical mode of a QSFP-capable port.\n\nQSFP ports can be broken out into one of several different electrical configurations or modes. This describes how the transmit/receive lanes are grouped into a single, logical link.\n\nNote that the electrical mode may only be changed if there are no links within the port, _and_ if the inserted QSFP module actually supports this mode.", + "oneOf": [ + { + "description": "All transmit/receive lanes are used for a single link.", + "type": "string", + "enum": [ + "Single" + ] + } + ] + }, + "EncSpeed": { + "description": "Signal speed and encoding for a single lane", + "type": "object", + "properties": { + "encoding": { + "$ref": "#/components/schemas/LaneEncoding" + }, + "gigabits": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "encoding", + "gigabits" + ] + }, + "Error": { + "description": "Error information from a response.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "message", + "request_id" + ] + }, + "ExternalForwarding": { + "description": "Represents the forwarding configuration for external multicast traffic.", + "type": "object", + "properties": { + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + }, + "Fault": { + "description": "A Fault represents a specific kind of failure, and carries some additional context. Currently Faults are only used to describe Link failures, but there is no reason they couldn't be used elsewhere.", + "oneOf": [ + { + "type": "object", + "properties": { + "LinkFlap": { + "type": "string" + } + }, + "required": [ + "LinkFlap" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Autoneg": { + "type": "string" + } + }, + "required": [ + "Autoneg" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Injected": { + "type": "string" + } + }, + "required": [ + "Injected" + ], + "additionalProperties": false + } + ] + }, + "FaultCondition": { + "description": "Represents a potential fault condtion on a link", + "type": "object", + "properties": { + "fault": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Fault" + } + ] + } + } + }, + "FaultReason": { + "description": "The cause of a fault on a transceiver.", + "oneOf": [ + { + "description": "An error occurred accessing the transceiver.", + "type": "string", + "enum": [ + "failed" + ] + }, + { + "description": "Power was enabled, but did not come up in the requisite time.", + "type": "string", + "enum": [ + "power_timeout" + ] + }, + { + "description": "Power was enabled and later lost.", + "type": "string", + "enum": [ + "power_lost" + ] + }, + { + "description": "The service processor disabled the transceiver.\n\nThe SP is responsible for monitoring the thermal data from the transceivers, and controlling the fans to compensate. If a module's thermal data cannot be read, the SP may completely disable the transceiver to ensure it cannot overheat the Sidecar.", + "type": "string", + "enum": [ + "disabled_by_sp" + ] + } + ] + }, + "FecRSCounters": { + "description": "Per-port RS FEC counters", + "type": "object", + "properties": { + "fec_align_status": { + "description": "All lanes synced and aligned", + "type": "boolean" + }, + "fec_corr_cnt": { + "description": "FEC corrected blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_0": { + "description": "FEC symbol errors on lane 0", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_1": { + "description": "FEC symbol errors on lane 1", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_2": { + "description": "FEC symbol errors on lane 2", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_3": { + "description": "FEC symbol errors on lane 3", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_4": { + "description": "FEC symbol errors on lane 4", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_5": { + "description": "FEC symbol errors on lane 5", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_6": { + "description": "FEC symbol errors on lane 6", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_7": { + "description": "FEC symbol errors on lane 7", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_uncorr_cnt": { + "description": "FEC uncorrected blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "hi_ser": { + "description": "symbol errors exceeds threshhold", + "type": "boolean" + }, + "port": { + "description": "Port being tracked", + "type": "string" + } + }, + "required": [ + "fec_align_status", + "fec_corr_cnt", + "fec_ser_lane_0", + "fec_ser_lane_1", + "fec_ser_lane_2", + "fec_ser_lane_3", + "fec_ser_lane_4", + "fec_ser_lane_5", + "fec_ser_lane_6", + "fec_ser_lane_7", + "fec_uncorr_cnt", + "hi_ser", + "port" + ] + }, + "FreeChannels": { + "description": "Represents the free MAC channels on a single physical port.", + "type": "object", + "properties": { + "channels": { + "description": "The set of available channels (lanes) on this connector.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "connector": { + "description": "The Tofino connector for this port.\n\nThis describes the set of electrical connections representing this port object, which are defined by the pinout and board design of the Sidecar.", + "type": "string" + }, + "port_id": { + "description": "The switch port.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "channels", + "connector", + "port_id" + ] + }, + "InstanceTarget": { + "description": "represents an internal target for either NAT or an external subnet mapping", + "type": "object", + "properties": { + "inner_mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "internal_ip": { + "type": "string", + "format": "ipv6" + }, + "vni": { + "$ref": "#/components/schemas/Vni" + } + }, + "required": [ + "inner_mac", + "internal_ip", + "vni" + ] + }, + "InternalForwarding": { + "description": "Represents the NAT target for multicast traffic for internal/underlay forwarding.", + "type": "object", + "properties": { + "nat_target": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/NatTarget" + } + ] + } + } + }, + "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, + "oneOf": [ + { + "title": "v4", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + { + "title": "v6", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + } + ] + }, + "IpSrc": { + "description": "Source filter match key for multicast traffic.\n\nFor SSM groups, use `Exact` with specific source addresses. For ASM groups with any-source filtering, use `Any`.", + "oneOf": [ + { + "description": "Exact match for the source IP address.", + "type": "object", + "properties": { + "Exact": { + "type": "string", + "format": "ip" + } + }, + "required": [ + "Exact" + ], + "additionalProperties": false + }, + { + "description": "Match any source address (0.0.0.0/0 or ::/0 depending on group IP version).", + "type": "string", + "enum": [ + "Any" + ] + } + ] + }, + "Ipv4Entry": { + "description": "An IPv4 address assigned to a link.", + "type": "object", + "properties": { + "addr": { + "description": "The IP address.", + "type": "string", + "format": "ipv4" + }, + "tag": { + "description": "Client-side tag for this object.", + "type": "string" + } + }, + "required": [ + "addr", + "tag" + ] + }, + "Ipv4EntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Entry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv4Nat": { + "description": "represents an IPv4 NAT reservation", + "type": "object", + "properties": { + "external": { + "type": "string", + "format": "ipv4" + }, + "high": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "low": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "target": { + "$ref": "#/components/schemas/NatTarget" + } + }, + "required": [ + "external", + "high", + "low", + "target" + ] + }, + "Ipv4NatResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Nat" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv4Net": { + "example": "192.168.1.0/24", + "title": "An IPv4 subnet", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, + "type": "string", + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + }, + "Ipv4Route": { + "description": "A route for an IPv4 subnet.", + "type": "object", + "properties": { + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + }, + "tag": { + "type": "string" + }, + "tgt_ip": { + "type": "string", + "format": "ipv4" + }, + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "link_id", + "port_id", + "tag", + "tgt_ip" + ] + }, + "Ipv4RouteUpdate": { + "description": "Represents a new or replacement mapping of an IPv4 subnet to a single RouteTarget nexthop target, which may be either IPv4 or IPv6.", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + "replace": { + "description": "Should this route replace any existing route? If a route exists and this parameter is false, then the call will fail.", + "type": "boolean" + }, + "target": { + "description": "A single Route associated with this CIDR", + "allOf": [ + { + "$ref": "#/components/schemas/RouteTarget" + } + ] + } + }, + "required": [ + "cidr", + "replace", + "target" + ] + }, + "Ipv4Routes": { + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + "targets": { + "description": "All RouteTargets associated with this CIDR", + "type": "array", + "items": { + "$ref": "#/components/schemas/Route" + } + } + }, + "required": [ + "cidr", + "targets" + ] + }, + "Ipv4RoutesResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Routes" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Entry": { + "description": "An IPv6 address assigned to a link.", + "type": "object", + "properties": { + "addr": { + "description": "The IP address.", + "type": "string", + "format": "ipv6" + }, + "tag": { + "description": "Client-side tag for this object.", + "type": "string" + } + }, + "required": [ + "addr", + "tag" + ] + }, + "Ipv6EntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Entry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Nat": { + "description": "represents an IPv6 NAT reservation", + "type": "object", + "properties": { + "external": { + "type": "string", + "format": "ipv6" + }, + "high": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "low": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "target": { + "$ref": "#/components/schemas/NatTarget" + } + }, + "required": [ + "external", + "high", + "low", + "target" + ] + }, + "Ipv6NatResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Nat" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Net": { + "example": "fd12:3456::/64", + "title": "An IPv6 subnet", + "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + }, + "Ipv6Route": { + "description": "A route for an IPv6 subnet.", + "type": "object", + "properties": { + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + }, + "tag": { + "type": "string" + }, + "tgt_ip": { + "type": "string", + "format": "ipv6" + }, + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "link_id", + "port_id", + "tag", + "tgt_ip" + ] + }, + "Ipv6RouteUpdate": { + "description": "Represents a new or replacement mapping of a subnet to a single IPv6 RouteTarget nexthop target.", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + }, + "replace": { + "description": "Should this route replace any existing route? If a route exists and this parameter is false, then the call will fail.", + "type": "boolean" + }, + "target": { + "description": "A single RouteTarget associated with this CIDR", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Route" + } + ] + } + }, + "required": [ + "cidr", + "replace", + "target" + ] + }, + "Ipv6Routes": { + "description": "Represents all mappings of an IPv6 subnet to a its nexthop target(s).", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + }, + "targets": { + "description": "All RouteTargets associated with this CIDR", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Route" + } + } + }, + "required": [ + "cidr", + "targets" + ] + }, + "Ipv6RoutesResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Routes" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "LaneEncoding": { + "description": "Signal encoding", + "oneOf": [ + { + "description": "Pulse Amplitude Modulation 4-level", + "type": "string", + "enum": [ + "Pam4" + ] + }, + { + "description": "Non-Return-to-Zero encoding", + "type": "string", + "enum": [ + "Nrz" + ] + }, + { + "description": "No encoding selected", + "type": "string", + "enum": [ + "None" + ] + } + ] + }, + "LaneMap": { + "description": "Mapping of the logical lanes in a link to their physical instantiation in the MAC/serdes interface.", + "type": "object", + "properties": { + "logical_lane": { + "description": "logical lane within the mac block for each lane", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "mac_block": { + "description": "MAC block in the tofino ASIC", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "rx_phys": { + "description": "Rx logical->physical mapping", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "rx_polarity": { + "description": "Rx polarity", + "type": "array", + "items": { + "$ref": "#/components/schemas/Polarity" + } + }, + "tx_phys": { + "description": "Tx logical->physical mapping", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "tx_polarity": { + "description": "Tx polarity", + "type": "array", + "items": { + "$ref": "#/components/schemas/Polarity" + } + } + }, + "required": [ + "logical_lane", + "mac_block", + "rx_phys", + "rx_polarity", + "tx_phys", + "tx_polarity" + ] + }, + "LanePolarity": { + "description": "The polarity of a transceiver lane.", + "type": "string", + "enum": [ + "normal", + "flipped" + ] + }, + "LaneStatus": { + "description": "The combined status of a lane, with respect to the autonegotiation / link-training process.", + "type": "object", + "properties": { + "lane_an_status": { + "description": "Detailed autonegotiation status", + "allOf": [ + { + "$ref": "#/components/schemas/AnStatus" + } + ] + }, + "lane_done": { + "description": "Has a lane successfully completed autoneg and link training?", + "type": "boolean" + }, + "lane_lt_status": { + "description": "Detailed link-training status", + "allOf": [ + { + "$ref": "#/components/schemas/LtStatus" + } + ] + } + }, + "required": [ + "lane_an_status", + "lane_done", + "lane_lt_status" + ] + }, + "Led": { + "description": "Information about a QSFP port's LED.", + "type": "object", + "properties": { + "policy": { + "description": "The policy by which the LED is controlled.", + "allOf": [ + { + "$ref": "#/components/schemas/LedPolicy" + } + ] + }, + "state": { + "description": "The state of the LED.", + "allOf": [ + { + "$ref": "#/components/schemas/LedState" + } + ] + } + }, + "required": [ + "policy", + "state" + ] + }, + "LedPolicy": { + "description": "The policy by which a port's LED is controlled.", + "oneOf": [ + { + "description": "The default policy is for the LED to reflect the port's state itself.\n\nIf the port is operating normally, the LED will be solid on. Without a transceiver, the LED will be solid off. A blinking LED is used to indicate an unsupported module or other failure on that port.", + "type": "string", + "enum": [ + "automatic" + ] + }, + { + "description": "The LED is explicitly overridden by client requests.", + "type": "string", + "enum": [ + "override" + ] + } + ] + }, + "LedState": { + "description": "The state of a module's attention LED, on the Sidecar front IO panel.", + "oneOf": [ + { + "description": "The LED is off.\n\nThis indicates that the port is disabled or not working at all.", + "type": "string", + "enum": [ + "off" + ] + }, + { + "description": "The LED is solid on.\n\nThis indicates that the port is working as expected and enabled.", + "type": "string", + "enum": [ + "on" + ] + }, + { + "description": "The LED is blinking.\n\nThis is used to draw attention to the port, such as to indicate a fault or to locate a port for servicing.", + "type": "string", + "enum": [ + "blink" + ] + } + ] + }, + "Link": { + "description": "An Ethernet-capable link within a switch port.", + "type": "object", + "properties": { + "address": { + "description": "The MAC address for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "asic_id": { + "description": "The lower-level ASIC ID used to refer to this object in the switch driver software.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "autoneg": { + "description": "True if this link is configured to autonegotiate with its peer.", + "type": "boolean" + }, + "enabled": { + "description": "True if this link is enabled.", + "type": "boolean" + }, + "fec": { + "nullable": true, + "description": "The error-correction scheme for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "fsm_state": { + "description": "Current state in the autonegotiation/link-training finite state machine", + "type": "string" + }, + "ipv6_enabled": { + "description": "The link is configured for IPv6 use", + "type": "boolean" + }, + "kr": { + "description": "True if this link is in KR mode, i.e., is on a cabled backplane.", + "type": "boolean" + }, + "link_id": { + "description": "The `LinkId` within the switch port for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "link_state": { + "description": "The state of the Ethernet link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkState" + } + ] + }, + "media": { + "description": "The physical media underlying this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortMedia" + } + ] + }, + "port_id": { + "description": "The switch port on which this link exists.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + }, + "prbs": { + "description": "The PRBS mode.", + "allOf": [ + { + "$ref": "#/components/schemas/PortPrbsMode" + } + ] + }, + "presence": { + "description": "True if the transceiver module has detected a media presence.", + "type": "boolean" + }, + "speed": { + "description": "The speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + }, + "tofino_connector": { + "description": "The Tofino connector number associated with this link.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "address", + "asic_id", + "autoneg", + "enabled", + "fsm_state", + "ipv6_enabled", + "kr", + "link_id", + "link_state", + "media", + "port_id", + "prbs", + "presence", + "speed", + "tofino_connector" + ] + }, + "LinkCreate": { + "description": "Parameters used to create a link on a switch port.", + "type": "object", + "properties": { + "autoneg": { + "description": "Whether the link is configured to autonegotiate with its peer during link training.\n\nThis is generally only true for backplane links, and defaults to `false`.", + "default": false, + "type": "boolean" + }, + "fec": { + "nullable": true, + "description": "The requested forward-error correction method. If this is None, the standard FEC for the underlying media will be applied if it can be determined.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "kr": { + "description": "Whether the link is configured in KR mode, an electrical specification generally only true for backplane link.\n\nThis defaults to `false`.", + "default": false, + "type": "boolean" + }, + "lane": { + "nullable": true, + "description": "The first lane of the port to use for the new link", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "speed": { + "description": "The requested speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + }, + "tx_eq": { + "nullable": true, + "description": "Transceiver equalization adjustment parameters. This defaults to `None`.", + "default": null, + "allOf": [ + { + "$ref": "#/components/schemas/TxEq" + } + ] + } + }, + "required": [ + "speed" + ] + }, + "LinkEvent": { + "type": "object", + "properties": { + "channel": { + "nullable": true, + "description": "Channel ID for sub-link-level events", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "class": { + "description": "Event class", + "type": "string" + }, + "details": { + "nullable": true, + "description": "Optionally, additional details about the event", + "type": "string" + }, + "subclass": { + "description": "Event subclass", + "type": "string" + }, + "timestamp": { + "description": "Time the event occurred. The time is represented in milliseconds, starting at an undefined time in the past. This means that timestamps can be used to measure the time between events, but not to determine the wall-clock time at which the event occurred.", + "type": "integer", + "format": "int64" + } + }, + "required": [ + "class", + "subclass", + "timestamp" + ] + }, + "LinkFecRSCounters": { + "description": "The FEC counters for a specific link, including its link ID.", + "type": "object", + "properties": { + "counters": { + "description": "The FEC counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/FecRSCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkFsmCounter": { + "description": "Reports how many times a given autoneg/link-training state has been entered", + "type": "object", + "properties": { + "current": { + "description": "Times entered since the link was last enabled", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "state_name": { + "description": "FSM state being counted", + "type": "string" + }, + "total": { + "description": "Times entered since the link was created", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "current", + "state_name", + "total" + ] + }, + "LinkFsmCounters": { + "description": "Reports all the autoneg/link-training states a link has transitioned into.", + "type": "object", + "properties": { + "counters": { + "description": "All the states this link has entered, along with counts of how many times each state was entered.", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkFsmCounter" + } + }, + "link_path": { + "description": "Link being reported", + "type": "string" + } + }, + "required": [ + "counters", + "link_path" + ] + }, + "LinkHistory": { + "type": "object", + "properties": { + "events": { + "description": "The set of historical events recorded", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkEvent" + } + }, + "timestamp": { + "description": "The timestamp in milliseconds at which this history was collected.", + "type": "integer", + "format": "int64" + } + }, + "required": [ + "events", + "timestamp" + ] + }, + "LinkId": { + "description": "An identifier for a link within a switch port.\n\nA switch port identified by a [`PortId`] may have multiple links within it, each identified by a `LinkId`. These are unique within a switch port only.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "LinkPcsCounters": { + "description": "The Physical Coding Sublayer (PCS) counters for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The PCS counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/PcsCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkRMonCounters": { + "description": "The RMON counters (traffic counters) for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The RMON counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/RMonCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkRMonCountersAll": { + "description": "The complete RMON counters (traffic counters) for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The RMON counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/RMonCountersAll" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkSettings": { + "description": "An object with link settings used in concert with [`PortSettings`].", + "type": "object", + "properties": { + "addrs": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + }, + "uniqueItems": true + }, + "params": { + "$ref": "#/components/schemas/LinkCreate" + } + }, + "required": [ + "addrs", + "params" + ] + }, + "LinkState": { + "description": "The state of a data link with a peer.", + "oneOf": [ + { + "description": "An error was encountered while trying to configure the link in the switch hardware.", + "type": "object", + "properties": { + "config_error": { + "type": "string" + } + }, + "required": [ + "config_error" + ], + "additionalProperties": false + }, + { + "description": "The link is up.", + "type": "string", + "enum": [ + "up" + ] + }, + { + "description": "The link is down.", + "type": "string", + "enum": [ + "down" + ] + }, + { + "description": "The Link is offline due to a fault", + "type": "object", + "properties": { + "faulted": { + "$ref": "#/components/schemas/Fault" + } + }, + "required": [ + "faulted" + ], + "additionalProperties": false + }, + { + "description": "The link's state is not known.", + "type": "string", + "enum": [ + "unknown" + ] + } + ] + }, + "LinkUpCounter": { + "description": "Reports how many times a link has transitioned from Down to Up.", + "type": "object", + "properties": { + "current": { + "description": "LinkUp transitions since the link was last enabled", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "link_path": { + "description": "Link being reported", + "type": "string" + }, + "total": { + "description": "LinkUp transitions since the link was created", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "current", + "link_path", + "total" + ] + }, + "LpPages": { + "description": "Set of AN pages sent by our link partner", + "type": "object", + "properties": { + "base_page": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "next_page1": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "next_page2": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "base_page", + "next_page1", + "next_page2" + ] + }, + "LtStatus": { + "description": "Link-training status for a single lane", + "type": "object", + "properties": { + "frame_lock": { + "description": "Frame lock state", + "type": "boolean" + }, + "readout_state": { + "description": "Readout for frame lock state", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readout_training_state": { + "description": "Training state readout", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readout_txstate": { + "description": "State machine readout for training arbiter", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "rx_trained": { + "description": "Local training finished", + "type": "boolean" + }, + "sig_det": { + "description": "Signal detect for PCS", + "type": "boolean" + }, + "training_failure": { + "description": "Link training failed", + "type": "boolean" + }, + "tx_training_data_en": { + "description": "TX control to send training pattern", + "type": "boolean" + } + }, + "required": [ + "frame_lock", + "readout_state", + "readout_training_state", + "readout_txstate", + "rx_trained", + "sig_det", + "training_failure", + "tx_training_data_en" + ] + }, + "MacAddr": { + "description": "An EUI-48 MAC address, used for layer-2 addressing.", + "type": "object", + "properties": { + "a": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 6, + "maxItems": 6 + } + }, + "required": [ + "a" + ] + }, + "ManagementMode": { + "description": "How a switch port is managed.\n\nThe free-side devices in QSFP ports are complex devices, whose operation usually involves coordinated steps through one or more state machines. For example, when bringing up an optical link, a signal from the peer link must be detected; then a signal recovered; equalizer gains set; etc. In `Automatic` mode, all these kinds of steps are managed autonomously by switch driver software. In `Manual` mode, none of these will occur -- a switch port will only change in response to explicit requests from the operator or Oxide control plane.", + "oneOf": [ + { + "description": "A port is managed manually, by either the Oxide control plane or an operator.", + "type": "string", + "enum": [ + "manual" + ] + }, + { + "description": "A port is managed automatically by the switch software.", + "type": "string", + "enum": [ + "automatic" + ] + } + ] + }, + "MediaInterfaceId": { + "oneOf": [ + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for multi-mode fiber media.\n\nSee SFF-8024 Table 4-6.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mmf" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for single-mode fiber.\n\nSee SFF-8024 Table 4-7.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "smf" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for passive copper cables.\n\nSee SFF-8024 Table 4-8.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "passive_copper" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for active cable assemblies.\n\nSee SFF-8024 Table 4-9.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "active_cable" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for BASE-T.\n\nSee SFF-8024 Table 4-10.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "base_t" + ] + } + }, + "required": [ + "id", + "type" + ] + } + ] + }, + "Monitors": { + "description": "Free-side device monitoring information.\n\nNote that all values are optional, as some specifications do not require that modules implement monitoring of those values.", + "type": "object", + "properties": { + "aux_monitors": { + "nullable": true, + "description": "Auxiliary monitoring values.\n\nThese are only available on CMIS-compatible transceivers, e.g., QSFP-DD.", + "allOf": [ + { + "$ref": "#/components/schemas/AuxMonitors" + } + ] + }, + "receiver_power": { + "nullable": true, + "description": "The measured input optical power (milliwatts);\n\nNote that due to a limitation in the SFF-8636 specification, it's possible for receiver power to be zero. See [`ReceiverPower`] for details.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiverPower" + } + }, + "supply_voltage": { + "nullable": true, + "description": "The measured input supply voltage (Volts).", + "type": "number", + "format": "float" + }, + "temperature": { + "nullable": true, + "description": "The measured cage temperature (degrees C);", + "type": "number", + "format": "float" + }, + "transmitter_bias_current": { + "nullable": true, + "description": "The output laser bias current (milliamps).", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "transmitter_power": { + "nullable": true, + "description": "The measured output optical power (milliwatts).", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + } + } + }, + "MulticastGroupCreateExternalEntry": { + "description": "A multicast group configuration for POST requests for external (to the rack) groups.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "nullable": true, + "description": "Tag for validating update/delete requests. If a tag is not provided, one is auto-generated as `{uuid}:{group_ip}`.", + "type": "string" + } + }, + "required": [ + "external_forwarding", + "group_ip", + "internal_forwarding" + ] + }, + "MulticastGroupCreateUnderlayEntry": { + "description": "A multicast group configuration for POST requests for internal (to the rack) groups.", + "type": "object", + "properties": { + "group_ip": { + "$ref": "#/components/schemas/UnderlayMulticastIpv6" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "nullable": true, + "description": "Tag for validating update/delete requests. If a tag is not provided, one is auto-generated as `{uuid}:{group_ip}`.", + "type": "string" + } + }, + "required": [ + "group_ip", + "members" + ] + }, + "MulticastGroupExternalResponse": { + "description": "Response structure for external multicast group operations. These groups handle IPv4 and non-admin-local IPv6 multicast via NAT targets.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "description": "Tag for validating update/delete requests. Always present and generated as `{uuid}:{group_ip}` if not provided at creation time.", + "type": "string" + } + }, + "required": [ + "external_forwarding", + "external_group_id", + "group_ip", + "internal_forwarding", + "tag" + ] + }, + "MulticastGroupMember": { + "description": "Represents a member of a multicast group.", + "type": "object", + "properties": { + "direction": { + "$ref": "#/components/schemas/Direction" + }, + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + } + }, + "required": [ + "direction", + "link_id", + "port_id" + ] + }, + "MulticastGroupResponse": { + "description": "Unified response type for operations that return mixed group types.", + "oneOf": [ + { + "description": "Response structure for underlay/internal multicast group operations. These groups handle admin-local IPv6 multicast with full replication.", + "type": "object", + "properties": { + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "$ref": "#/components/schemas/UnderlayMulticastIpv6" + }, + "kind": { + "type": "string", + "enum": [ + "underlay" + ] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "description": "Tag for validating update/delete requests. Always present and generated as `{uuid}:{group_ip}` if not provided at creation time.", + "type": "string" + }, + "underlay_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "external_group_id", + "group_ip", + "kind", + "members", + "tag", + "underlay_group_id" + ] + }, + { + "description": "Response structure for external multicast group operations. These groups handle IPv4 and non-admin-local IPv6 multicast via NAT targets.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "kind": { + "type": "string", + "enum": [ + "external" + ] + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "description": "Tag for validating update/delete requests. Always present and generated as `{uuid}:{group_ip}` if not provided at creation time.", + "type": "string" + } + }, + "required": [ + "external_forwarding", + "external_group_id", + "group_ip", + "internal_forwarding", + "kind", + "tag" + ] + } + ] + }, + "MulticastGroupResponseResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupResponse" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "MulticastGroupUnderlayResponse": { + "description": "Response structure for underlay/internal multicast group operations. These groups handle admin-local IPv6 multicast with full replication.", + "type": "object", + "properties": { + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "$ref": "#/components/schemas/UnderlayMulticastIpv6" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "description": "Tag for validating update/delete requests. Always present and generated as `{uuid}:{group_ip}` if not provided at creation time.", + "type": "string" + }, + "underlay_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "external_group_id", + "group_ip", + "members", + "tag", + "underlay_group_id" + ] + }, + "MulticastGroupUpdateExternalEntry": { + "description": "A multicast group update entry for PUT requests for external (to the rack) groups.\n\nTag validation is performed via the `tag` query parameter.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + } + }, + "required": [ + "external_forwarding", + "internal_forwarding" + ] + }, + "MulticastGroupUpdateUnderlayEntry": { + "description": "Represents a multicast replication entry for PUT requests for internal (to the rack) groups.\n\nTag validation is performed via the `tag` query parameter.", + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + } + }, + "required": [ + "members" + ] + }, + "NatTarget": { + "description": "represents an internal NAT target", + "type": "object", + "properties": { + "inner_mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "internal_ip": { + "type": "string", + "format": "ipv6" + }, + "vni": { + "$ref": "#/components/schemas/Vni" + } + }, + "required": [ + "inner_mac", + "internal_ip", + "vni" + ] + }, + "Oui": { + "description": "An Organization Unique Identifier.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 3, + "maxItems": 3 + }, + "OutputStatus": { + "type": "string", + "enum": [ + "valid", + "invalid" + ] + }, + "PcsCounters": { + "description": "Per-port PCS counters", + "type": "object", + "properties": { + "bad_sync_headers": { + "description": "Count of bad sync headers", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "bip_errors_per_pcs_lane": { + "description": "Bit Inteleaved Parity errors (per lane)", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "block_lock_loss": { + "description": "Count of block-lock loss detections", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "errored_blocks": { + "description": "Count of errored blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "hi_ber": { + "description": "Count of high bit error rate events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "invalid_errors": { + "description": "Count of invalid error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "port": { + "description": "Port being tracked", + "type": "string" + }, + "sync_loss": { + "description": "Count of sync loss detections", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "unknown_errors": { + "description": "Count of unknown error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "valid_errors": { + "description": "Count of valid error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "bad_sync_headers", + "bip_errors_per_pcs_lane", + "block_lock_loss", + "errored_blocks", + "hi_ber", + "invalid_errors", + "port", + "sync_loss", + "unknown_errors", + "valid_errors" + ] + }, + "Polarity": { + "type": "string", + "enum": [ + "Normal", + "Inverted" + ] + }, + "PortFec": { + "type": "string", + "enum": [ + "None", + "Firecode", + "RS" + ] + }, + "PortId": { + "example": "qsfp0", + "title": "PortId", + "description": "Physical switch port identifier", + "oneOf": [ + { + "title": "internal", + "type": "string", + "pattern": "(^[iI][nN][tT]0$)" + }, + { + "title": "rear", + "type": "string", + "pattern": "(^[rR][eE][aA][rR](([0-9])|([1-2][0-9])|(3[0-1]))$)" + }, + { + "title": "qsfp", + "type": "string", + "pattern": "(^[qQ][sS][fF][pP](([0-9])|([1-2][0-9])|(3[0-1]))$)" + } + ] + }, + "PortMedia": { + "type": "string", + "enum": [ + "Copper", + "Optical", + "CPU", + "None", + "Unknown" + ] + }, + "PortPrbsMode": { + "description": "Legal PRBS modes", + "type": "string", + "enum": [ + "Mode31", + "Mode23", + "Mode15", + "Mode13", + "Mode11", + "Mode9", + "Mode7", + "Mission" + ] + }, + "PortSettings": { + "description": "A port settings transaction object. When posted to the `/port-settings/{port_id}` API endpoint, these settings will be applied holistically, and to the extent possible atomically to a given port.", + "type": "object", + "properties": { + "links": { + "description": "The link settings to apply to the port on a per-link basis. Any links not in this map that are resident on the switch port will be removed. Any links that are in this map that are not resident on the switch port will be added. Any links that are resident on the switch port and in this map, and are different, will be modified. Links are indexed by spatial index within the port.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/LinkSettings" + } + } + }, + "required": [ + "links" + ] + }, + "PortSpeed": { + "description": "Speeds with which a single port may be configured", + "type": "string", + "enum": [ + "Speed0G", + "Speed1G", + "Speed10G", + "Speed25G", + "Speed40G", + "Speed50G", + "Speed100G", + "Speed200G", + "Speed400G" + ] + }, + "PowerMode": { + "description": "The power mode of a module.", + "type": "object", + "properties": { + "software_override": { + "nullable": true, + "description": "Whether the module is configured for software override of power control.\n\nIf the module is in `PowerState::Off`, this can't be determined, and `None` is returned.", + "type": "boolean" + }, + "state": { + "description": "The actual power state.", + "allOf": [ + { + "$ref": "#/components/schemas/PowerState" + } + ] + } + }, + "required": [ + "state" + ] + }, + "PowerState": { + "description": "An allowed power state for the module.", + "oneOf": [ + { + "description": "A module is entirely powered off, using the EFuse.", + "type": "string", + "enum": [ + "off" + ] + }, + { + "description": "Power is enabled to the module, but module remains in low-power mode.\n\nIn this state, modules will not establish a link or transmit traffic, but they may be managed and queried for information through their memory maps.", + "type": "string", + "enum": [ + "low" + ] + }, + { + "description": "The module is in high-power mode.\n\nNote that additional configuration may be required to correctly configure the module, such as described in SFF-8636 rev 2.10a table 6-10, and that the _host side_ is responsible for ensuring that the relevant configuration is applied.", + "type": "string", + "enum": [ + "high" + ] + } + ] + }, + "RMonCounters": { + "description": "High level subset of the RMon counters maintained by the Tofino ASIC", + "type": "object", + "properties": { + "crc_error_stomped": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "fragments_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frame_too_long": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_dropped_buffer_full": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_with_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_with_any_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx_in_good_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_total": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_without_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "port": { + "type": "string" + } + }, + "required": [ + "crc_error_stomped", + "fragments_rx", + "frame_too_long", + "frames_dropped_buffer_full", + "frames_rx_all", + "frames_rx_ok", + "frames_tx_all", + "frames_tx_ok", + "frames_tx_with_error", + "frames_with_any_error", + "octets_rx", + "octets_rx_in_good_frames", + "octets_tx_total", + "octets_tx_without_error", + "port" + ] + }, + "RMonCountersAll": { + "description": "All of the RMon counters maintained by the Tofino ASIC", + "type": "object", + "properties": { + "crc_error_stomped": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "fragments_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frame_too_long": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_dropped_buffer_full": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_indersized": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_1024_1518": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_128_255": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_1519_2047": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_2048_4095": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_256_511": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_4096_8191": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_512_1023": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_65_127": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_8192_9215": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_9216": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_eq_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_lt_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_oftype_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_oversized": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_any_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_broadcast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_fcs_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_length_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_multicast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_unicast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_truncated": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_broadcast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_1024_1518": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_128_255": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_1519_2047": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_2048_4095": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_256_511": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_4096_8191": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_512_1023": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_65_127": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_8192_9215": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_9216": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_eq_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_lt_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_multicast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_pri_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_unicast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_vlan": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_with_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "jabber_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx_in_good_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_total": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_without_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "port": { + "type": "string" + }, + "pri0_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri0_framex_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri1_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri1_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri2_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri2_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri3_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri3_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri4_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri4_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri5_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri5_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri6_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri6_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri7_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri7_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "priority_pause_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri0_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri1_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri2_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri3_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri4_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri5_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri6_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri7_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_standard_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_vlan_frames_good": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri0_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri1_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri2_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri3_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri4_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri5_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri6_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri7_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "crc_error_stomped", + "fragments_rx", + "frame_too_long", + "frames_dropped_buffer_full", + "frames_rx_all", + "frames_rx_indersized", + "frames_rx_length_1024_1518", + "frames_rx_length_128_255", + "frames_rx_length_1519_2047", + "frames_rx_length_2048_4095", + "frames_rx_length_256_511", + "frames_rx_length_4096_8191", + "frames_rx_length_512_1023", + "frames_rx_length_65_127", + "frames_rx_length_8192_9215", + "frames_rx_length_9216", + "frames_rx_length_eq_64", + "frames_rx_length_lt_64", + "frames_rx_oftype_pause", + "frames_rx_ok", + "frames_rx_oversized", + "frames_rx_with_any_error", + "frames_rx_with_broadcast_addresses", + "frames_rx_with_fcs_error", + "frames_rx_with_length_error", + "frames_rx_with_multicast_addresses", + "frames_rx_with_unicast_addresses", + "frames_truncated", + "frames_tx_all", + "frames_tx_broadcast", + "frames_tx_length_1024_1518", + "frames_tx_length_128_255", + "frames_tx_length_1519_2047", + "frames_tx_length_2048_4095", + "frames_tx_length_256_511", + "frames_tx_length_4096_8191", + "frames_tx_length_512_1023", + "frames_tx_length_65_127", + "frames_tx_length_8192_9215", + "frames_tx_length_9216", + "frames_tx_length_eq_64", + "frames_tx_length_lt_64", + "frames_tx_multicast", + "frames_tx_ok", + "frames_tx_pause", + "frames_tx_pri_pause", + "frames_tx_unicast", + "frames_tx_vlan", + "frames_tx_with_error", + "jabber_rx", + "octets_rx", + "octets_rx_in_good_frames", + "octets_tx_total", + "octets_tx_without_error", + "port", + "pri0_frames_rx", + "pri0_framex_tx", + "pri1_frames_rx", + "pri1_frames_tx", + "pri2_frames_rx", + "pri2_frames_tx", + "pri3_frames_rx", + "pri3_frames_tx", + "pri4_frames_rx", + "pri4_frames_tx", + "pri5_frames_rx", + "pri5_frames_tx", + "pri6_frames_rx", + "pri6_frames_tx", + "pri7_frames_rx", + "pri7_frames_tx", + "priority_pause_frames", + "rx_pri0_pause_1us_count", + "rx_pri1_pause_1us_count", + "rx_pri2_pause_1us_count", + "rx_pri3_pause_1us_count", + "rx_pri4_pause_1us_count", + "rx_pri5_pause_1us_count", + "rx_pri6_pause_1us_count", + "rx_pri7_pause_1us_count", + "rx_standard_pause_1us_count", + "rx_vlan_frames_good", + "tx_pri0_pause_1us_count", + "tx_pri1_pause_1us_count", + "tx_pri2_pause_1us_count", + "tx_pri3_pause_1us_count", + "tx_pri4_pause_1us_count", + "tx_pri5_pause_1us_count", + "tx_pri6_pause_1us_count", + "tx_pri7_pause_1us_count" + ] + }, + "ReceiverPower": { + "description": "Measured receiver optical power.\n\nThe SFF specifications allow for devices to monitor input optical power in several ways. It may either be an average power, over some unspecified time, or a peak-to-peak power. The latter is often abbreviated OMA, for Optical Modulation Amplitude. Again the time interval for peak-to-peak measurments are not specified.\n\nDetails -------\n\nThe SFF-8636 specification has an unfortunate limitation. There is no separate advertisement for whether a module supports measurements of receiver power. Instead, the _kind_ of measurement is advertised. The _same bit value_ could mean that either a peak-to-peak measurement is supported, or the measurements are not supported at all. Thus values of `PeakToPeak(0.0)` may mean that power measurements are not supported.", + "oneOf": [ + { + "description": "The measurement is represents average optical power, in mW.", + "type": "object", + "properties": { + "average": { + "type": "number", + "format": "float" + } + }, + "required": [ + "average" + ], + "additionalProperties": false + }, + { + "description": "The measurement represents a peak-to-peak, in mW.", + "type": "object", + "properties": { + "peak_to_peak": { + "type": "number", + "format": "float" + } + }, + "required": [ + "peak_to_peak" + ], + "additionalProperties": false + } + ] + }, + "Route": { + "oneOf": [ + { + "type": "object", + "properties": { + "V4": { + "$ref": "#/components/schemas/Ipv4Route" + } + }, + "required": [ + "V4" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "V6": { + "$ref": "#/components/schemas/Ipv6Route" + } + }, + "required": [ + "V6" + ], + "additionalProperties": false + } + ] + }, + "RouteTarget": { + "description": "Represents a specific egress port and nexthop target.", + "oneOf": [ + { + "type": "object", + "properties": { + "V4": { + "$ref": "#/components/schemas/Ipv4Route" + } + }, + "required": [ + "V4" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "V6": { + "$ref": "#/components/schemas/Ipv6Route" + } + }, + "required": [ + "V6" + ], + "additionalProperties": false + } + ] + }, + "RxSigInfo": { + "description": "Per-lane Rx signal information", + "type": "object", + "properties": { + "phy_ready": { + "description": "CDR lock achieved", + "type": "boolean" + }, + "ppm": { + "description": "Apparent PPM difference between local and remote", + "type": "integer", + "format": "int32" + }, + "sig_detect": { + "description": "Rx signal detected", + "type": "boolean" + } + }, + "required": [ + "phy_ready", + "ppm", + "sig_detect" + ] + }, + "SerdesEye": { + "description": "Eye height(s) for a single lane in mv", + "oneOf": [ + { + "type": "object", + "properties": { + "Nrz": { + "type": "number", + "format": "float" + } + }, + "required": [ + "Nrz" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Pam4": { + "type": "object", + "properties": { + "eye1": { + "type": "number", + "format": "float" + }, + "eye2": { + "type": "number", + "format": "float" + }, + "eye3": { + "type": "number", + "format": "float" + } + }, + "required": [ + "eye1", + "eye2", + "eye3" + ] + } + }, + "required": [ + "Pam4" + ], + "additionalProperties": false + } + ] + }, + "Sff8636Datapath": { + "description": "The datapath of an SFF-8636 module.\n\nThis describes the state of a single lane in an SFF module. It includes information about input and output signals, faults, and controls.", + "type": "object", + "properties": { + "rx_cdr_enabled": { + "description": "Media-side transmit Clock and Data Recovery (CDR) enable status.\n\nCDR is the process by which the module enages an internal retimer function, through which the module attempts to recovery a clock signal directly from the input bitstream.", + "type": "boolean" + }, + "rx_lol": { + "description": "Media-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the media-side signal (usually optical).", + "type": "boolean" + }, + "rx_los": { + "description": "Media-side loss of signal flag.\n\nThis is true if there is no detected input signal from the media-side (usually optical).", + "type": "boolean" + }, + "tx_adaptive_eq_fault": { + "description": "Flag indicating a fault in adaptive transmit equalization.", + "type": "boolean" + }, + "tx_cdr_enabled": { + "description": "Host-side transmit Clock and Data Recovery (CDR) enable status.\n\nCDR is the process by which the module enages an internal retimer function, through which the module attempts to recovery a clock signal directly from the input bitstream.", + "type": "boolean" + }, + "tx_enabled": { + "description": "Software control of output transmitter.", + "type": "boolean" + }, + "tx_fault": { + "description": "Flag indicating a fault in the transmitter and/or laser.", + "type": "boolean" + }, + "tx_lol": { + "description": "Host-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the host-side electrical signal.", + "type": "boolean" + }, + "tx_los": { + "description": "Host-side loss of signal flag.\n\nThis is true if there is no detected electrical signal from the host-side serdes.", + "type": "boolean" + } + }, + "required": [ + "rx_cdr_enabled", + "rx_lol", + "rx_los", + "tx_adaptive_eq_fault", + "tx_cdr_enabled", + "tx_enabled", + "tx_fault", + "tx_lol", + "tx_los" + ] + }, + "SffComplianceCode": { + "description": "The compliance code for an SFF-8636 module.\n\nThese values record a specification compliance code, from SFF-8636 Table 6-17, or an extended specification compliance code, from SFF-8024 Table 4-4.", + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "description": "Extended electrical or optical interface codes", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "extended" + ] + } + }, + "required": [ + "code", + "type" + ] + }, + { + "type": "object", + "properties": { + "code": { + "description": "The Ethernet specification implemented by a module.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ethernet" + ] + } + }, + "required": [ + "code", + "type" + ] + } + ] + }, + "SidecarCableLeg": { + "description": "The leg of the Sidecar-internal cable.\n\nThis describes the leg on the cabling that connects the pins on the Tofino ASIC to the Sidecar chassis connector.", + "type": "string", + "enum": [ + "A", + "C" + ] + }, + "SidecarConnector": { + "description": "The Sidecar chassis connector mating the backplane and internal cabling.\n\nThis describes the \"group\" of backplane links that all terminate in one connector on the Sidecar itself. This is the connection point between a cable on the backplane itself and the Sidecar chassis.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "SnapshotCreate": { + "description": "Request body for creating and capturing a PHV snapshot.", + "type": "object", + "properties": { + "dir": { + "description": "Whether to capture on the ingress or egress pipeline.", + "allOf": [ + { + "$ref": "#/components/schemas/SnapshotDirection" + } + ] + }, + "end_stage": { + "description": "Tofino hardware stage to stop capturing at.\n\nSee /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of stage layout.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "fields": { + "description": "Field names to decode from the capture.", + "type": "array", + "items": { + "type": "string" + } + }, + "pipe": { + "description": "Index of the pipeline to capture. Typically this will be 0 through 3. Different ports map to different pipelines.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "start_stage": { + "description": "Tofino hardware stage to start capturing at.\n\nSee /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa to get a sense of stage layout.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "timeout_secs": { + "description": "Timeout in seconds to wait for trigger.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "triggers": { + "description": "Fields and masks to use as snapshot trigger. Triggers are combined as a logical `and`.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotTrigger" + } + } + }, + "required": [ + "dir", + "end_stage", + "fields", + "pipe", + "start_stage", + "timeout_secs", + "triggers" + ] + }, + "SnapshotDirection": { + "description": "Direction of a PHV snapshot.", + "oneOf": [ + { + "description": "Take snapshot of ingress pipeline", + "type": "string", + "enum": [ + "Ingress" + ] + }, + { + "description": "Take snapshot of egress pipeline", + "type": "string", + "enum": [ + "Egress" + ] + } + ] + }, + "SnapshotFieldScope": { + "description": "Whether a field is in scope at a given stage.", + "type": "object", + "properties": { + "field": { + "description": "Field name", + "type": "string" + }, + "in_scope": { + "description": "Whether or not the field is in scope.", + "type": "boolean" + } + }, + "required": [ + "field", + "in_scope" + ] + }, + "SnapshotFieldValue": { + "description": "A decoded field value from a snapshot capture.", + "type": "object", + "properties": { + "name": { + "description": "Name of the field.", + "type": "string" + }, + "value": { + "nullable": true, + "description": "None if the field is not valid at this stage.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "SnapshotResult": { + "description": "Result of a snapshot capture operation.", + "type": "object", + "properties": { + "stages": { + "description": "Stages captured in the result.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotStageResult" + } + } + }, + "required": [ + "stages" + ] + }, + "SnapshotScopeRequest": { + "description": "Request body for checking field scope at a given stage.", + "type": "object", + "properties": { + "dir": { + "description": "Whether to check the ingress or egress pipeline.", + "allOf": [ + { + "$ref": "#/components/schemas/SnapshotDirection" + } + ] + }, + "fields": { + "description": "Fields to check.", + "type": "array", + "items": { + "type": "string" + } + }, + "pipe": { + "description": "Pipeline index to check.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "stage": { + "description": "Stage index.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "trigger": { + "description": "If true, check trigger scope; otherwise check capture scope.", + "type": "boolean" + } + }, + "required": [ + "dir", + "fields", + "pipe", + "stage", + "trigger" + ] + }, + "SnapshotStageResult": { + "description": "Per-stage result from a snapshot capture.", + "type": "object", + "properties": { + "egress_dp_error": { + "description": "Datapath error detected in the egress pipeline at capture time. Only reported on Tofino 2+; always false on Tofino 1.", + "type": "boolean" + }, + "fields": { + "description": "Fields captured in the result.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotFieldValue" + } + }, + "ingress_dp_error": { + "description": "Datapath error detected in the ingress pipeline at capture time. Only reported on Tofino 2+; always false on Tofino 1.", + "type": "boolean" + }, + "local_stage_trigger": { + "description": "Whether this stage's own PHV match criteria fired the snapshot. This is the primary trigger: the PHV contents at this stage matched the key/mask programmed via the snapshot trigger configuration.", + "type": "boolean" + }, + "next_table": { + "description": "The P4 table name that the MAU pipeline selected for execution in the following stage after processing this one.", + "type": "string" + }, + "prev_stage_trigger": { + "description": "Whether the snapshot was triggered because the previous stage was already triggered and propagated its trigger signal forward. A `prev_stage_trigger` with no `local_stage_trigger` means this stage did not match the trigger criteria itself -- it was captured solely because an adjacent stage matched.", + "type": "boolean" + }, + "stage_id": { + "description": "The index of the stage this result came from.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "tables": { + "description": "Tables captured in the result.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotTableResult" + } + }, + "timer_trigger": { + "description": "Whether the snapshot was triggered by the timer mechanism rather than a PHV field match. Useful for capturing pipeline state at a specific time regardless of packet contents.", + "type": "boolean" + } + }, + "required": [ + "egress_dp_error", + "fields", + "ingress_dp_error", + "local_stage_trigger", + "next_table", + "prev_stage_trigger", + "stage_id", + "tables", + "timer_trigger" + ] + }, + "SnapshotTableResult": { + "description": "Table hit/miss result from a snapshot capture.", + "type": "object", + "properties": { + "executed": { + "description": "Whether the table was active in this stage. This is the primary gate: if a table was not executed, `hit`, `inhibited`, and `match_hit_address` are all meaningless (zeroed by the SDE).", + "type": "boolean" + }, + "hit": { + "description": "Whether the match lookup found a matching entry.\n\nOnly meaningful when `executed` is true and `inhibited` is false. The absence of `hit` does not necessarily mean that a lookup was attempted. It simply means there was no hit, which could mean no lookup was attempted or that the table's gateway inhibited it.", + "type": "boolean" + }, + "inhibited": { + "description": "Whether the table's gateway inhibited the match lookup from proceeding. Gateways are conditional guards attached to tables that can skip the lookup entirely. When inhibited, `hit` and `match_hit_address` will be 0. Only applicable to tables that have an attached gateway.", + "type": "boolean" + }, + "match_hit_address": { + "description": "The physical address of the entry that matched, sourced from the exact-match or TCAM hit-address register depending on table type. Zero when the table was not executed or was inhibited.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "name": { + "description": "Name of the table", + "type": "string" + } + }, + "required": [ + "executed", + "hit", + "inhibited", + "match_hit_address", + "name" + ] + }, + "SnapshotTrigger": { + "description": "A trigger field for a snapshot, with hex-encoded value and mask.", + "type": "object", + "properties": { + "field": { + "description": "Name of the field to capture.\n\nMust match what's in the phv ingress or phv egress section of /opt/oxide/dendrite/sidecar/pipe/sidecar.bfa.", + "type": "string" + }, + "mask": { + "description": "Hex-encoded mask (e.g. \"0xffffffffffff\")", + "type": "string" + }, + "value": { + "description": "Hex-encoded value (e.g. \"0x112233445566\")", + "type": "string" + } + }, + "required": [ + "field", + "mask", + "value" + ] + }, + "SwitchIdentifiers": { + "description": "Identifiers for a switch.", + "type": "object", + "properties": { + "asic_backend": { + "description": "Asic backend (compiler target) responsible for these identifiers.", + "type": "string" + }, + "fab": { + "nullable": true, + "description": "Fabrication plant identifier.", + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "lot": { + "nullable": true, + "description": "Lot identifier.", + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "model": { + "description": "The model number of the switch being managed.", + "type": "string" + }, + "revision": { + "description": "The revision number of the switch being managed.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial": { + "description": "The serial number of the switch being managed.", + "type": "string" + }, + "sidecar_id": { + "description": "Unique identifier for the chip.", + "type": "string", + "format": "uuid" + }, + "slot": { + "description": "The slot number of the switch being managed.\n\nMGS uses u16 for this internally.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "wafer": { + "nullable": true, + "description": "Wafer number within the lot.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "wafer_loc": { + "nullable": true, + "description": "The wafer location as (x, y) coordinates on the wafer, represented as an array due to the lack of tuple support in OpenAPI.", + "type": "array", + "items": { + "type": "integer", + "format": "int16" + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "asic_backend", + "model", + "revision", + "serial", + "sidecar_id", + "slot" + ] + }, + "SwitchPort": { + "description": "A physical port on the Sidecar switch.", + "type": "object", + "properties": { + "management_mode": { + "description": "How the QSFP device is managed.\n\nSee `ManagementMode` for details.", + "allOf": [ + { + "$ref": "#/components/schemas/ManagementMode" + } + ] + }, + "port_id": { + "description": "The identifier for the switch port.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + }, + "transceiver": { + "nullable": true, + "description": "Details about a transceiver module inserted into the switch port.\n\nIf there is no transceiver at all, this will be `None`.", + "allOf": [ + { + "$ref": "#/components/schemas/Transceiver" + } + ] + } + }, + "required": [ + "port_id" + ] + }, + "Table": { + "description": "Represents the contents of a P4 table", + "type": "object", + "properties": { + "entries": { + "description": "There will be an entry for each populated slot in the table", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableEntry" + } + }, + "name": { + "description": "A user-friendly name for the table", + "type": "string" + }, + "size": { + "description": "The maximum number of entries the table can hold", + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "entries", + "name", + "size" + ] + }, + "TableCounterEntry": { + "type": "object", + "properties": { + "data": { + "description": "Counter values", + "allOf": [ + { + "$ref": "#/components/schemas/CounterData" + } + ] + }, + "keys": { + "description": "Names and values of each of the key fields.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "data", + "keys" + ] + }, + "TableEntry": { + "description": "Each entry in a P4 table is addressed by matching against a set of key values. If an entry is found, an action is taken with an action-specific set of arguments.\n\nNote: each entry will have the same key fields and each instance of any given action will have the same argument names, so a vector of TableEntry structs will contain a signficant amount of redundant data. We could consider tightening this up by including a schema of sorts in the \"struct Table\".", + "type": "object", + "properties": { + "action": { + "description": "Name of the action to take on a match", + "type": "string" + }, + "action_args": { + "description": "Names and values for the arguments to the action implementation.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "keys": { + "description": "Names and values of each of the key fields.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "action", + "action_args", + "keys" + ] + }, + "TfportData": { + "description": "The per-link data consumed by tfportd", + "type": "object", + "properties": { + "asic_id": { + "description": "The lower-level ASIC ID used to refer to this object in the switch driver software.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "ipv6_enabled": { + "description": "Is ipv6 enabled for this link", + "type": "boolean" + }, + "link_id": { + "description": "The link ID for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "link_local": { + "nullable": true, + "description": "The IPv6 link-local address of the link, if it exists.", + "type": "string", + "format": "ipv6" + }, + "mac": { + "description": "The MAC address for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "port_id": { + "description": "The switch port ID for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "asic_id", + "ipv6_enabled", + "link_id", + "mac", + "port_id" + ] + }, + "Transceiver": { + "description": "The state of a transceiver in a QSFP switch port.", + "oneOf": [ + { + "description": "The transceiver could not be managed due to a power fault.", + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/FaultReason" + }, + "state": { + "type": "string", + "enum": [ + "faulted" + ] + } + }, + "required": [ + "info", + "state" + ] + }, + { + "description": "A transceiver was present, but unsupported and automatically disabled.", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "unsupported" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "A transceiver is present and supported.", + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/TransceiverInfo" + }, + "state": { + "type": "string", + "enum": [ + "supported" + ] + } + }, + "required": [ + "info", + "state" + ] + } + ] + }, + "TransceiverInfo": { + "description": "Information about a QSFP transceiver.\n\nThis stores the most relevant information about a transceiver module, such as vendor info or power. Each field may be missing, indicating it could not be determined.", + "type": "object", + "properties": { + "electrical_mode": { + "description": "The electrical mode of the transceiver.\n\nSee [`ElectricalMode`] for details.", + "allOf": [ + { + "$ref": "#/components/schemas/ElectricalMode" + } + ] + }, + "in_reset": { + "nullable": true, + "description": "True if the module is currently in reset.", + "type": "boolean" + }, + "interrupt_pending": { + "nullable": true, + "description": "True if there is a pending interrupt on the module.", + "type": "boolean" + }, + "power_mode": { + "nullable": true, + "description": "The power mode of the transceiver.", + "allOf": [ + { + "$ref": "#/components/schemas/PowerMode" + } + ] + }, + "vendor_info": { + "nullable": true, + "description": "Vendor and part identifying information.\n\nThe information will not be populated if it could not be read.", + "allOf": [ + { + "$ref": "#/components/schemas/VendorInfo" + } + ] + } + }, + "required": [ + "electrical_mode" + ] + }, + "TxEq": { + "description": "Parameters to adjust the transceiver equalization settings for a link on a switch. These parameters match those available on a tofino-based sidecar, and may need to be adapted when we move to a new switch ASIC.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "type": "integer", + "format": "int32" + } + } + }, + "TxEqSwHw": { + "description": "This represents the software-determined equalization value initially assigned to the transceiver and the value actually being used by the hardware. The values may differ on transceivers that are capable of tuning their own settings at run time.", + "type": "object", + "properties": { + "hw": { + "$ref": "#/components/schemas/TxEq" + }, + "sw": { + "$ref": "#/components/schemas/TxEq" + } + }, + "required": [ + "hw", + "sw" + ] + }, + "UnderlayMulticastIpv6": { + "description": "A validated underlay multicast IPv6 address.\n\nUnderlay multicast addresses must be within the subnet allocated by Omicron for rack-internal multicast traffic (ff04::/64). This is a subset of the admin-local scope (ff04::/16) defined in RFC 4291.", + "type": "string", + "format": "ipv6" + }, + "Vendor": { + "description": "Vendor-specific information about a transceiver module.", + "type": "object", + "properties": { + "date": { + "nullable": true, + "type": "string" + }, + "name": { + "type": "string" + }, + "oui": { + "$ref": "#/components/schemas/Oui" + }, + "part": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "serial": { + "type": "string" + } + }, + "required": [ + "name", + "oui", + "part", + "revision", + "serial" + ] + }, + "VendorInfo": { + "description": "The vendor information for a transceiver module.", + "type": "object", + "properties": { + "identifier": { + "description": "The SFF-8024 identifier.", + "type": "string" + }, + "vendor": { + "description": "The vendor information.", + "allOf": [ + { + "$ref": "#/components/schemas/Vendor" + } + ] + } + }, + "required": [ + "identifier", + "vendor" + ] + }, + "Vni": { + "description": "A Geneve Virtual Network Identifier.\n\nA Geneve VNI is a 24-bit value used to identify virtual networks encapsulated using the Generic Network Virtualization Encapsulation (Geneve) protocol (RFC 8926).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "ipv4ResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "type": "string", + "format": "ipv4" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ipv6ResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "type": "string", + "format": "ipv6" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "MulticastTag": { + "description": "Tag for identifying and authorizing multicast group operations.\n\nTag format: 1 to 80 ASCII bytes containing alphanumeric characters, hyphens, underscores, colons, or periods. Default format is `{uuid}:{group_ip}`.", + "type": "string", + "pattern": "^[a-zA-Z0-9_.:-]+$", + "minLength": 1, + "maxLength": 80 + } + }, + "responses": { + "Error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } +} diff --git a/openapi/dpd/dpd-latest.json b/openapi/dpd/dpd-latest.json index 37420b52..7c81c141 120000 --- a/openapi/dpd/dpd-latest.json +++ b/openapi/dpd/dpd-latest.json @@ -1 +1 @@ -dpd-8.0.0-8c22d4.json \ No newline at end of file +dpd-9.0.0-d5e91c.json \ No newline at end of file diff --git a/swadm/Cargo.toml b/swadm/Cargo.toml index cab5bfde..9c88e020 100644 --- a/swadm/Cargo.toml +++ b/swadm/Cargo.toml @@ -16,6 +16,7 @@ colored.workspace = true common.workspace = true dpd-client.workspace = true futures.workspace = true +reqwest.workspace = true oxide-tokio-rt.workspace = true oxnet.workspace = true slog.workspace = true diff --git a/swadm/src/main.rs b/swadm/src/main.rs index 2a20f05e..2c850d35 100644 --- a/swadm/src/main.rs +++ b/swadm/src/main.rs @@ -25,6 +25,7 @@ mod counters; mod link; mod nat; mod route; +mod snapshot; mod switchport; mod table; @@ -91,6 +92,10 @@ enum Commands { #[command(subcommand)] cmd: compliance::Compliance, }, + Snapshot { + #[command(subcommand)] + cmd: snapshot::Snapshot, + }, } // A LinkPath or "loopback", used when either is appropriate. @@ -209,7 +214,16 @@ async fn main_impl() -> anyhow::Result<()> { let host = opts.host.unwrap_or_else(|| "localhost".to_string()); let log = slog::Logger::root(slog::Discard, slog::o!()); let client_state = ClientState { tag: String::from("cli"), log }; - let client = Client::new(&format!("http://{host}:{port}"), client_state); + let reqwest_client = reqwest::ClientBuilder::new() + .connect_timeout(std::time::Duration::from_secs(5)) + .timeout(std::time::Duration::from_secs(300)) + .build() + .expect("failed to build HTTP client"); + let client = Client::new_with_client( + &format!("http://{host}:{port}"), + reqwest_client, + client_state, + ); match opts.cmd { Commands::DpdBuildInfo => build_info(&client).await, @@ -231,5 +245,8 @@ async fn main_impl() -> anyhow::Result<()> { Commands::Compliance { cmd: compliance } => { compliance::compliance_cmd(&client, compliance).await } + Commands::Snapshot { cmd } => { + snapshot::snapshot_cmd(&client, cmd).await + } } } diff --git a/swadm/src/snapshot.rs b/swadm/src/snapshot.rs new file mode 100644 index 00000000..0c031d3f --- /dev/null +++ b/swadm/src/snapshot.rs @@ -0,0 +1,224 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/ +// +// Copyright 2026 Oxide Computer Company + +use anyhow::{Context, Result}; +use clap::{Subcommand, ValueEnum}; + +use dpd_client::Client; +use dpd_client::types; + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum Direction { + Ingress, + Egress, +} + +impl From for types::SnapshotDirection { + fn from(d: Direction) -> Self { + match d { + Direction::Ingress => types::SnapshotDirection::Ingress, + Direction::Egress => types::SnapshotDirection::Egress, + } + } +} + +/// Tofino snapshot commands +#[derive(Debug, Subcommand)] +pub enum Snapshot { + /// Capture a PHV snapshot + Capture { + /// MAU start stage + #[arg(long)] + start_stage: u8, + + /// MAU end stage + #[arg(long)] + end_stage: u8, + + /// Pipeline direction + #[arg(long, value_enum, default_value_t = Direction::Ingress)] + dir: Direction, + + /// Pipe to capture from + #[arg(long, default_value_t = 0)] + pipe: u32, + + /// Trigger fields as "field=value/mask" (hex values). + /// Example: "hdr.ethernet.dst_addr=0x112233445566/0xffffffffffff" + /// + /// Field names must match what's in the phv ingress or phv egress + /// /section of opt/oxide/dendrite/sidecar/pipe/sidecar.bfa. + #[arg(long = "trigger", short = 't')] + triggers: Vec, + + /// P4 field names to decode from the capture. + /// + /// Field names must match what's in the phv ingress or phv egress + /// /section of opt/oxide/dendrite/sidecar/pipe/sidecar.bfa. + #[arg(long = "field", short = 'f')] + fields: Vec, + + /// Timeout in seconds to wait for the snapshot to trigger. + #[arg(long, default_value_t = 10)] + timeout: u64, + }, + + /// Check field scope at a given stage + Scope { + /// MAU stage to query + #[arg(long)] + stage: u8, + + /// Pipeline direction + #[arg(long, value_enum, default_value_t = Direction::Ingress)] + dir: Direction, + + /// Pipe + #[arg(long, default_value_t = 0)] + pipe: u32, + + /// Field names to check + #[arg(long = "field", short = 'f')] + fields: Vec, + + /// Check trigger scope (vs capture scope) + #[arg(long)] + trigger: bool, + }, +} + +/// Parse a trigger spec: "field=value/mask" where value and mask are hex. +fn parse_trigger(s: &str) -> Result { + let (field, rest) = + s.split_once('=').context("expected field=value/mask")?; + let (val_str, mask_str) = + rest.split_once('/').context("expected value/mask after =")?; + Ok(types::SnapshotTrigger { + field: field.trim().to_string(), + value: val_str.trim().to_string(), + mask: mask_str.trim().to_string(), + }) +} + +pub async fn snapshot_cmd(client: &Client, cmd: Snapshot) -> Result<()> { + match cmd { + Snapshot::Capture { + start_stage, + end_stage, + dir, + pipe, + triggers, + fields, + timeout, + } => { + let api_triggers: Vec = triggers + .iter() + .map(|t| parse_trigger(t)) + .collect::>>() + .context("bad trigger spec")?; + + println!( + "creating snapshot: pipe={pipe} \ + stages={start_stage}..{end_stage} dir={dir:?}" + ); + + for trig in &api_triggers { + println!( + " trigger: {} = {} / {}", + trig.field, trig.value, trig.mask + ); + } + + println!("arming and waiting for trigger (timeout {timeout}s)..."); + + let result = client + .snapshot_capture(&types::SnapshotCreate { + pipe, + start_stage, + end_stage, + dir: dir.into(), + triggers: api_triggers, + fields: fields.clone(), + timeout_secs: timeout, + }) + .await + .context("snapshot capture failed")? + .into_inner(); + + println!("\ncapture: {} stage(s)", result.stages.len()); + + for stage in &result.stages { + println!("\n--- stage {} ---", stage.stage_id); + println!(" local_trigger: {}", stage.local_stage_trigger); + println!(" prev_trigger: {}", stage.prev_stage_trigger); + println!(" timer_trigger: {}", stage.timer_trigger); + println!(" next_table: {}", stage.next_table); + if stage.ingress_dp_error { + println!(" INGRESS DATAPATH ERROR"); + } + if stage.egress_dp_error { + println!(" EGRESS DATAPATH ERROR"); + } + + for tbl in &stage.tables { + if !tbl.executed { + println!( + " table: {} -> not active in this stage", + tbl.name + ); + } else if tbl.inhibited { + println!( + " table: {} -> inhibited by gateway logic", + tbl.name + ); + } else if tbl.hit { + println!( + " table: {} -> HIT (addr={:#x})", + tbl.name, tbl.match_hit_address, + ); + } else { + println!(" table: {} -> miss", tbl.name); + } + } + + for field in &stage.fields { + match &field.value { + Some(v) => println!(" {} = {v}", field.name), + None => { + println!(" {} = (not valid)", field.name) + } + } + } + } + + println!("\nsnapshot complete."); + } + + Snapshot::Scope { stage, dir, pipe, fields, trigger } => { + let kind = if trigger { "trigger" } else { "capture" }; + println!("field {kind} scope at stage {stage} ({dir:?}):\n"); + + let results = client + .snapshot_scope(&types::SnapshotScopeRequest { + pipe, + stage, + dir: dir.into(), + fields, + trigger, + }) + .await + .context("snapshot scope failed")? + .into_inner(); + + for r in &results { + let mark = if r.in_scope { "YES" } else { " no" }; + println!(" [{mark}] {}", r.field); + } + } + } + + Ok(()) +} diff --git a/swadm/src/table.rs b/swadm/src/table.rs index c352a78b..ed50330a 100644 --- a/swadm/src/table.rs +++ b/swadm/src/table.rs @@ -40,6 +40,10 @@ pub enum Table { parseable: bool, /// The name of the table to display. name: String, + + #[clap(long)] + /// Dump table directly from hardware instead of software cache + from_hardware: bool, }, /// Fetch any counter data associated with the specified table. #[clap(visible_alias = "ctrs")] @@ -89,8 +93,9 @@ async fn table_dump( dump_schema: bool, parseable: bool, action_filter: Option, + from_hardware: bool, ) -> anyhow::Result<()> { - let t = client.table_dump(&table).await?.into_inner(); + let t = client.table_dump(&table, from_hardware).await?.into_inner(); if t.entries.is_empty() { return Ok(()); } @@ -235,8 +240,9 @@ pub async fn table_cmd( } Ok(()) } - Table::Dump { schema, parseable, action, name } => { - table_dump(client, name, schema, parseable, action).await + Table::Dump { schema, parseable, action, name, from_hardware } => { + table_dump(client, name, schema, parseable, action, from_hardware) + .await } Table::Counters { force_sync, parseable, name } => { table_counters(client, name, force_sync, parseable).await diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 797fb42b..a89078a7 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -159,7 +159,6 @@ pub fn build( "tofino2".to_string(), "--arch".to_string(), "default".to_string(), - "--enable-bf-asm".to_string(), "--create-graphs".to_string(), "-I".to_string(), src_dir.clone(), @@ -176,8 +175,11 @@ pub fn build( args.push(app_path); println!("op: {args:?}"); - if !Command::new(&p4c_path).args(&args).status()?.success() { - return Err(anyhow!("p4 build failed")); + let out = Command::new(&p4c_path).args(&args).output()?; + let stdout = String::from_utf8_lossy(&out.stdout); + let stderr = String::from_utf8_lossy(&out.stderr); + if !out.status.success() { + return Err(anyhow!("p4 build failed: {stdout} {stderr}")); } let config = P4Config::new(&app_name, "tofino2");