diff --git a/switchapi/es2k/switch_lag.c b/switchapi/es2k/switch_lag.c index c37df33..2f81a45 100644 --- a/switchapi/es2k/switch_lag.c +++ b/switchapi/es2k/switch_lag.c @@ -104,9 +104,8 @@ switch_status_t switch_api_lag_create(switch_device_t device, if (!SWITCH_RMAC_HANDLE(api_lag_info->rmac_handle)) { status = SWITCH_STATUS_INVALID_HANDLE; krnlmon_log_error( - "LAG create: Invalid rmac handle on device %d: " - "error: %s\n", - device, switch_error_to_string(status)); + "LAG create: Invalid rmac handle on device:%d error: %s\n", device, + switch_error_to_string(status)); return status; } @@ -115,10 +114,8 @@ switch_status_t switch_api_lag_create(switch_device_t device, status = switch_lag_get(device, *lag_h, &lag_info); if (status != SWITCH_STATUS_SUCCESS) { - krnlmon_log_error( - "LAG create: Failed to get LAG on device %d: " - "error: %s\n", - device, switch_error_to_string(status)); + krnlmon_log_error("LAG create: Failed to get LAG on device:%d error: %s\n", + device, switch_error_to_string(status)); status = switch_lag_handle_delete(device, *lag_h); CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); return status; @@ -126,7 +123,7 @@ switch_status_t switch_api_lag_create(switch_device_t device, lag_info->lag_handle = *lag_h; - status = SWITCH_LIST_INIT(&(lag_info->lag_members)); + status = SWITCH_LIST_INIT(&lag_info->lag_members); SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); SWITCH_MEMCPY(&lag_info->api_lag_info, api_lag_info, @@ -159,9 +156,8 @@ switch_status_t switch_api_lag_delete(switch_device_t device, status = switch_pd_tx_lag_table_entry(device, lag_info, false); if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( - "Failed to delete tx_lag_table entry on device %d: " - ",error: %s\n", - device, switch_error_to_string(status)); + "Failed to delete tx_lag_table entry on device:%d error: %s\n", device, + switch_error_to_string(status)); return status; } @@ -169,9 +165,8 @@ switch_status_t switch_api_lag_delete(switch_device_t device, status = switch_pd_rx_lag_table_entry(device, lag_info, false); if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( - "Failed to delete rx_lag_table entry on device %d: " - ",error: %s\n", - device, switch_error_to_string(status)); + "Failed to delete rx_lag_table entry on device:%d error: %s\n", device, + switch_error_to_string(status)); return status; } @@ -208,8 +203,7 @@ switch_status_t switch_api_lag_member_create( CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( - "LAG member create: Failed to get LAG member on device %d: " - "error: %s\n", + "LAG member create: Failed to get LAG member on device:%d error: %s\n", device, switch_error_to_string(status)); status = switch_lag_member_handle_delete(device, *lag_member_h); CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); @@ -284,6 +278,78 @@ switch_status_t switch_api_lag_update(const switch_device_t device, status = switch_lag_get(device, lag_h, &lag_info); CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); + if (lag_info->api_lag_info.bond_mode == SWITCHAPI_BOND_MODE_ACTIVE_BACKUP) { + if (!SWITCH_LAG_MEMBER_HANDLE(lag_member_h)) { + status = SWITCH_STATUS_INVALID_PARAMETER; + krnlmon_log_error( + "LAG get: Invalid LAG member handle on device %d, " + "LAG member handle 0x%lx: " + "error: %s\n", + device, lag_member_h, switch_error_to_string(status)); + return status; + } + status = switch_lag_member_get(device, lag_member_h, &lag_member_info); + CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); + + if (lag_info->active_lag_member != 0) { + // delete rx path + status = switch_pd_rx_lag_table_entry(device, lag_info, false); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to delete rx_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + // insert lag member + status = SWITCH_LIST_INSERT(&lag_info->lag_members, + &lag_member_info->node, lag_member_info); + SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); + + // create rx path + status = switch_pd_rx_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create rx_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + } else { + // update lag members list with the lag_member_h + status = SWITCH_LIST_INSERT(&lag_info->lag_members, + &lag_member_info->node, lag_member_info); + SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); + } + } + + return status; +} + +/** + * Routine Description: + * @details On change of oper_state of a LAG member, + * update the number of active ports of a LAG and + * re-populate the Tx and Rx LAG table entries with + * active port. + * + * Arguments: + * @param[in] device - device + * @param[in] lag_member_h - LAG member handle + * @param[in] oper_state - oper state of LAG member + * + * Return Values: + * @return SWITCH_STATUS_SUCCESS on success + * Failure status code on error + */ +switch_status_t switch_api_lag_member_update(const switch_device_t device, + const switch_handle_t lag_member_h, + bool oper_state) { + switch_lag_info_t* lag_info = NULL; + switch_lag_member_info_t* lag_member_info = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + if (!SWITCH_LAG_MEMBER_HANDLE(lag_member_h)) { status = SWITCH_STATUS_INVALID_PARAMETER; krnlmon_log_error( @@ -296,36 +362,163 @@ switch_status_t switch_api_lag_update(const switch_device_t device, status = switch_lag_member_get(device, lag_member_h, &lag_member_info); CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); - if (lag_info->active_lag_member != 0) { - // delete rx path - status = switch_pd_rx_lag_table_entry(device, lag_info, false); + lag_member_info->api_lag_member_info.oper_state = oper_state; + + switch_handle_t lag_h = lag_member_info->api_lag_member_info.lag_h; + + if (!SWITCH_LAG_HANDLE(lag_h)) { + status = SWITCH_STATUS_INVALID_PARAMETER; + krnlmon_log_error( + "LAG get: Invalid LAG handle on device %d, " + "LAG handle 0x%lx: " + "error: %s\n", + device, lag_h, switch_error_to_string(status)); + return status; + } + status = switch_lag_get(device, lag_h, &lag_info); + CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); + + // update the active num ports of LAG + if (oper_state) + lag_info->num_active_ports++; + else + lag_info->num_active_ports--; + + if (lag_info->num_active_ports == 0 && + (SWITCH_LIST_COUNT(&lag_info->lag_members) != 0)) { + // delete the existing tx and rx entries + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, false); if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( - "Failed to delete rx_lag_table entry on device %d: " - ",error: %s\n", + "Failed to delete tx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, false); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to delete rx_lacp_lag_table entry on device %d: " + "error: %s\n", device, switch_error_to_string(status)); return status; } - // insert lag member - status = SWITCH_LIST_INSERT(&(lag_info->lag_members), - &(lag_member_info->node), lag_member_info); + // delete the lag_member from the LAG + status = SWITCH_LIST_DELETE(&lag_info->lag_members, &lag_member_info->node); SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); - // create rx path - status = switch_pd_rx_lag_table_entry(device, lag_info, true); + } else if (lag_info->num_active_ports == 1) { + /* oper_state false means num_active_ports is decremented from 2 to 1 + and oper_state true means it is incremented from 0 to 1 */ + if (oper_state) { + // populate the tx and rx entries with single active member + status = SWITCH_LIST_INSERT(&lag_info->lag_members, + &lag_member_info->node, lag_member_info); + SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create tx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create rx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + } else { + // delete the existing entries and populate the tx and rx entries with + // other active member + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, false); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to delete tx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, false); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to delete rx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = + SWITCH_LIST_DELETE(&lag_info->lag_members, &lag_member_info->node); + SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); + + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create tx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create rx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + } + } else if (lag_info->num_active_ports == 2) { + // delete the existing entries and re-distribute + // table entries between both active ports + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, false); if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( - "Failed to create rx_lag_table entry on device %d: " - ",error: %s\n", + "Failed to delete tx_lacp_lag_table entry on device %d: " + "error: %s\n", device, switch_error_to_string(status)); return status; } - } else { - // update lag members list with the lag_member_h - status = SWITCH_LIST_INSERT(&(lag_info->lag_members), - &(lag_member_info->node), lag_member_info); + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, false); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to delete rx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = SWITCH_LIST_INSERT(&lag_info->lag_members, &lag_member_info->node, + lag_member_info); SWITCH_ASSERT(status == SWITCH_STATUS_SUCCESS); + + status = switch_pd_tx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create tx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } + + status = switch_pd_rx_lacp_lag_table_entry(device, lag_info, true); + if (status != SWITCH_STATUS_SUCCESS) { + krnlmon_log_error( + "Failed to create rx_lacp_lag_table entry on device %d: " + "error: %s\n", + device, switch_error_to_string(status)); + return status; + } } return status; @@ -409,7 +602,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to create tx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -419,7 +612,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to create rx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -430,7 +623,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to delete tx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -439,7 +632,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to delete rx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -451,7 +644,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to create tx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -460,7 +653,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to create rx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -470,7 +663,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to delete tx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } @@ -479,7 +672,7 @@ switch_status_t switch_api_program_lag_hw(const switch_device_t device, if (status != SWITCH_STATUS_SUCCESS) { krnlmon_log_error( "Failed to delete rx_lag_table entry on device %d: " - ",error: %s\n", + "error: %s\n", device, switch_error_to_string(status)); return status; } diff --git a/switchapi/es2k/switch_pd_lag.c b/switchapi/es2k/switch_pd_lag.c index ba04fc0..9088178 100644 --- a/switchapi/es2k/switch_pd_lag.c +++ b/switchapi/es2k/switch_pd_lag.c @@ -388,7 +388,6 @@ switch_status_t switch_pd_rx_lag_table_entry(switch_device_t device, goto dealloc_resources; } - uint32_t port_id = 0; switch_lag_member_info_t* lag_member_info = NULL; status = switch_lag_member_get(device, lag_member_h, &lag_member_info); CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); @@ -446,3 +445,424 @@ switch_status_t switch_pd_rx_lag_table_entry(switch_device_t device, data_hdl, session, entry_add); return switch_pd_tdi_status_to_status(status); } + +/** + * Routine Description: + * @brief Program tx_lag_table in MEV-TS for LACP mode + * + * Arguments: + * @param[in] device - device + * @param[in] lag_info - LAG info + * @param[in] entry_add - true for entry add, false + * for entry delete + * + * Return Values: + * @return TDI_SUCCESS on success + * Failure status code on error + */ +switch_status_t switch_pd_tx_lacp_lag_table_entry( + switch_device_t device, const switch_lag_info_t* lag_info, bool entry_add) { + tdi_status_t status; + tdi_id_t field_id_lag_id = 0; + tdi_id_t field_id_hash = 0; + tdi_id_t action_id = 0; + tdi_id_t data_field_id = 0; + + tdi_dev_id_t dev_id = device; + + tdi_flags_hdl* flags_hdl = NULL; + tdi_target_hdl* target_hdl = NULL; + const tdi_device_hdl* dev_hdl = NULL; + tdi_session_hdl* session = NULL; + const tdi_info_hdl* info_hdl = NULL; + tdi_table_key_hdl* key_hdl = NULL; + tdi_table_data_hdl* data_hdl = NULL; + const tdi_table_hdl* table_hdl = NULL; + const tdi_table_info_hdl* table_info_hdl = NULL; + + uint16_t lag_id = -1; + uint8_t egress_port = -1; + static uint32_t total_lag_list = 0; + uint32_t lag_list = 0; + uint8_t port_count = 0; + + switch_list_t lag_members; + switch_node_t* node = NULL; + switch_lag_member_info_t* lag_member = NULL; + switch_handle_t lag_h = SWITCH_API_INVALID_HANDLE; + switch_handle_t lag_member_h = SWITCH_API_INVALID_HANDLE; + + krnlmon_log_debug("%s", __func__); + + status = tdi_flags_create(0, &flags_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create flags handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_device_get(dev_id, &dev_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to get device handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_target_create(dev_hdl, &target_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create target handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_session_create(dev_hdl, &session); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create tdi session, error: %d", status); + goto dealloc_resources; + } + status = tdi_info_get(dev_id, PROGRAM_NAME, &info_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to get tdi info handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_table_from_name_get(info_hdl, LNW_TX_LAG_TABLE, &table_hdl); + if (status != TDI_SUCCESS || !table_hdl) { + krnlmon_log_error("Unable to get table handle for: %s, error: %d", + LNW_TX_LAG_TABLE, status); + goto dealloc_resources; + } + + status = tdi_table_key_allocate(table_hdl, &key_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to allocate key handle for: %s, error: %d", + LNW_TX_LAG_TABLE, status); + goto dealloc_resources; + } + + status = tdi_table_info_get(table_hdl, &table_info_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get table info handle for table, error: %d", + status); + goto dealloc_resources; + } + + status = tdi_key_field_id_get(table_info_hdl, LNW_TX_LAG_TABLE_KEY_LAG_ID, + &field_id_lag_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get field ID for key: %s, error: %d", + LNW_TX_LAG_TABLE_KEY_LAG_ID, status); + goto dealloc_resources; + } + + status = tdi_key_field_id_get( + table_info_hdl, LNW_TX_LAG_TABLE_KEY_VMETA_COMMON_HASH, &field_id_hash); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get field ID for key: %s, error: %d", + LNW_TX_LAG_TABLE_KEY_VMETA_COMMON_HASH, status); + goto dealloc_resources; + } + + status = tdi_action_name_to_id( + table_info_hdl, LNW_TX_LAG_TABLE_ACTION_SET_EGRESS_PORT, &action_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get action allocator ID for: %s, error: %d", + LNW_TX_LAG_TABLE_ACTION_SET_EGRESS_PORT, status); + goto dealloc_resources; + } + + status = tdi_table_action_data_allocate(table_hdl, action_id, &data_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to get action allocator for ID: %d, " + "error: %d", + action_id, status); + goto dealloc_resources; + } + + status = tdi_data_field_id_with_action_get( + table_info_hdl, ACTION_SET_EGRESS_PORT_PARAM_EGRESS_PORT, action_id, + &data_field_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get data field id param for: %s, error: %d", + ACTION_SET_EGRESS_PORT_PARAM_EGRESS_PORT, status); + goto dealloc_resources; + } + + lag_h = lag_info->lag_handle; + lag_members = (switch_list_t)lag_info->lag_members; + lag_id = lag_h & ~(SWITCH_HANDLE_TYPE_LAG << SWITCH_HANDLE_TYPE_SHIFT); + + while ((total_lag_list < LNW_LAG_HASH_SIZE) && + (lag_list < LNW_LAG_PER_GROUP_HASH_SIZE)) { + port_count = 0; + FOR_EACH_IN_LIST(lag_members, node) { + lag_member = (switch_lag_member_info_t*)node->data; + lag_member_h = lag_member->lag_member_handle; + switch_lag_member_info_t* lag_member_info = NULL; + status = switch_lag_member_get(device, lag_member_h, &lag_member_info); + CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); + status = switch_pd_get_physical_port_id( + device, lag_member_info->api_lag_member_info.port_id, &egress_port); + status = tdi_key_field_set_value(key_hdl, field_id_lag_id, lag_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to set value for key ID: %d for tx_lag_table" + ", error: %d", + field_id_lag_id, status); + goto dealloc_resources; + } + + status = tdi_key_field_set_value(key_hdl, field_id_hash, + lag_list + port_count); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to set value for key ID: %d for tx_lag_table" + ", error: %d", + field_id_hash, status); + goto dealloc_resources; + } + + if (entry_add) { + status = tdi_data_field_set_value(data_hdl, data_field_id, egress_port); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to set action value for ID: %d, error: %d", + data_field_id, status); + goto dealloc_resources; + } + + status = tdi_table_entry_add(table_hdl, session, target_hdl, flags_hdl, + key_hdl, data_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to add %s entry, error: %d", + LNW_TX_LAG_TABLE, status); + goto dealloc_resources; + } + total_lag_list++; + } else { + /* Delete an entry from target */ + krnlmon_log_info("Delete tx_lag_table entry"); + status = tdi_table_entry_del(table_hdl, session, target_hdl, flags_hdl, + key_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to delete %s entry, error: %d", + LNW_TX_LAG_TABLE, status); + goto dealloc_resources; + } + total_lag_list--; + } + port_count++; + } + FOR_EACH_IN_LIST_END(); + lag_list += port_count; + } + + krnlmon_log_debug("Total LAG hash entries created are: %d", total_lag_list); + +dealloc_resources: + status = tdi_switch_pd_deallocate_resources(flags_hdl, target_hdl, key_hdl, + data_hdl, session, entry_add); + return switch_pd_tdi_status_to_status(status); +} + +/** + * Routine Description: + * @brief Program rx_lag_table in MEV-TS for LACP mode + * + * Arguments: + * @param[in] device - device + * @param[in] lag_info - LAG info + * @param[in] entry_add - true for entry add, false + * for entry delete + * + * Return Values: + * @return TDI_SUCCESS on success + * Failure status code on error + */ +switch_status_t switch_pd_rx_lacp_lag_table_entry( + switch_device_t device, const switch_lag_info_t* lag_info, bool entry_add) { + tdi_status_t status; + + tdi_id_t field_id_port_id = 0; + tdi_id_t field_id_lag_id = 0; + tdi_id_t action_id = 0; + tdi_id_t data_field_id = 0; + + tdi_dev_id_t dev_id = device; + + tdi_flags_hdl* flags_hdl = NULL; + tdi_target_hdl* target_hdl = NULL; + const tdi_device_hdl* dev_hdl = NULL; + tdi_session_hdl* session = NULL; + const tdi_info_hdl* info_hdl = NULL; + tdi_table_key_hdl* key_hdl = NULL; + tdi_table_data_hdl* data_hdl = NULL; + const tdi_table_hdl* table_hdl = NULL; + const tdi_table_info_hdl* table_info_hdl = NULL; + + switch_list_t lag_members; + switch_node_t* node = NULL; + switch_lag_member_info_t* lag_member = NULL; + switch_handle_t lag_member_h = SWITCH_API_INVALID_HANDLE; + uint8_t egress_port = -1; + + krnlmon_log_debug("%s", __func__); + + status = tdi_info_get(dev_id, PROGRAM_NAME, &info_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to get tdi info handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_flags_create(0, &flags_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create flags handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_device_get(dev_id, &dev_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to get device handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_target_create(dev_hdl, &target_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create target handle, error: %d", status); + goto dealloc_resources; + } + + status = tdi_session_create(dev_hdl, &session); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Failed to create tdi session, error: %d", status); + goto dealloc_resources; + } + + status = tdi_table_from_name_get(info_hdl, LNW_RX_LAG_TABLE, &table_hdl); + if (status != TDI_SUCCESS || !table_hdl) { + krnlmon_log_error("Unable to get table handle for: %s, error: %d", + LNW_RX_LAG_TABLE, status); + goto dealloc_resources; + } + + status = tdi_table_key_allocate(table_hdl, &key_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to allocate key handle for: %s, error: %d", + LNW_RX_LAG_TABLE, status); + goto dealloc_resources; + } + + status = tdi_table_info_get(table_hdl, &table_info_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get table info handle for table, error: %d", + status); + goto dealloc_resources; + } + + status = tdi_key_field_id_get(table_info_hdl, LNW_RX_LAG_TABLE_KEY_PORT_ID, + &field_id_port_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get field ID for key: %s, error: %d", + LNW_RX_LAG_TABLE_KEY_PORT_ID, status); + goto dealloc_resources; + } + + status = tdi_key_field_id_get(table_info_hdl, LNW_RX_LAG_TABLE_KEY_LAG_ID, + &field_id_lag_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get field ID for key: %s, error: %d", + LNW_RX_LAG_TABLE_KEY_LAG_ID, status); + goto dealloc_resources; + } + + status = tdi_action_name_to_id( + table_info_hdl, LNW_RX_LAG_TABLE_ACTION_FWD_TO_VSI, &action_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get action allocator ID for: %s, error: %d", + LNW_RX_LAG_TABLE_ACTION_FWD_TO_VSI, status); + goto dealloc_resources; + } + + status = tdi_table_action_data_allocate(table_hdl, action_id, &data_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to get action allocator for ID: %d, " + "error: %d", + action_id, status); + goto dealloc_resources; + } + + status = tdi_data_field_id_with_action_get(table_info_hdl, + LNW_ACTION_FWD_TO_VSI_PARAM_PORT, + action_id, &data_field_id); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to get data field id param for: %s, error: %d", + LNW_ACTION_FWD_TO_VSI_PARAM_PORT, status); + goto dealloc_resources; + } + + lag_members = (switch_list_t)lag_info->lag_members; + FOR_EACH_IN_LIST(lag_members, node) { + lag_member = (switch_lag_member_info_t*)node->data; + lag_member_h = lag_member->lag_member_handle; + + status = tdi_key_field_set_value( + key_hdl, field_id_lag_id, + (lag_info->lag_handle & + ~(SWITCH_HANDLE_TYPE_LAG << SWITCH_HANDLE_TYPE_SHIFT))); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to set value for key ID: %d for rx_lag_table" + ", error: %d", + field_id_lag_id, status); + goto dealloc_resources; + } + + switch_lag_member_info_t* lag_member_info = NULL; + status = switch_lag_member_get(device, lag_member_h, &lag_member_info); + CHECK_RET(status != SWITCH_STATUS_SUCCESS, status); + + status = switch_pd_get_physical_port_id( + device, lag_member_info->api_lag_member_info.port_id, &egress_port); + + status = tdi_key_field_set_value(key_hdl, field_id_port_id, egress_port); + if (status != TDI_SUCCESS) { + krnlmon_log_error( + "Unable to set value for key ID: %d for rx_lag_table" + ", error: %d", + field_id_port_id, status); + goto dealloc_resources; + } + + if (entry_add) { + status = + tdi_data_field_set_value(data_hdl, data_field_id, egress_port + 16); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to set action value for ID: %d, error: %d", + data_field_id, status); + goto dealloc_resources; + } + + status = tdi_table_entry_add(table_hdl, session, target_hdl, flags_hdl, + key_hdl, data_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to add %s entry, error: %d", LNW_RX_LAG_TABLE, + status); + goto dealloc_resources; + } + } else { + /* Delete an entry from target */ + krnlmon_log_info("Delete rx_lag_table entry"); + status = tdi_table_entry_del(table_hdl, session, target_hdl, flags_hdl, + key_hdl); + if (status != TDI_SUCCESS) { + krnlmon_log_error("Unable to delete %s entry, error: %d", + LNW_RX_LAG_TABLE, status); + goto dealloc_resources; + } + } + } + FOR_EACH_IN_LIST_END(); + +dealloc_resources: + status = tdi_switch_pd_deallocate_resources(flags_hdl, target_hdl, key_hdl, + data_hdl, session, entry_add); + return switch_pd_tdi_status_to_status(status); +} diff --git a/switchapi/es2k/switch_pd_lag.h b/switchapi/es2k/switch_pd_lag.h index 30564ed..1daffb3 100644 --- a/switchapi/es2k/switch_pd_lag.h +++ b/switchapi/es2k/switch_pd_lag.h @@ -35,6 +35,14 @@ switch_status_t switch_pd_rx_lag_table_entry(switch_device_t device, const switch_lag_info_t* lag_info, bool entry_add); +// Method to program the tx_lag_table on Tx side for LACP mode +switch_status_t switch_pd_tx_lacp_lag_table_entry( + switch_device_t device, const switch_lag_info_t* lag_info, bool oper_state); + +// Method to program the rx_lag_table on Rx side for LACP mode +switch_status_t switch_pd_rx_lacp_lag_table_entry( + switch_device_t device, const switch_lag_info_t* lag_info, bool oper_state); + #ifdef __cplusplus } #endif diff --git a/switchapi/switch_lag.h b/switchapi/switch_lag.h index 7edadc2..d7ea56c 100644 --- a/switchapi/switch_lag.h +++ b/switchapi/switch_lag.h @@ -60,7 +60,7 @@ extern "C" { /*** LAG information ***/ typedef struct switch_api_lag_info_s { uint32_t lag_ifindex; - uint32_t lag_mode; + uint8_t bond_mode; // see switchapi_bond_mode_t for values uint32_t active_slave; switch_handle_t rmac_handle; } switch_api_lag_info_t; @@ -70,6 +70,7 @@ typedef struct switch_lag_info_s { switch_list_t lag_members; switch_handle_t lag_handle; switch_handle_t active_lag_member; + uint8_t num_active_ports; } switch_lag_info_t; /*** LAG member information ***/ @@ -77,6 +78,8 @@ typedef struct switch_api_lag_member_info_s { uint32_t lag_member_ifindex; uint8_t slave_state; uint32_t port_id; + bool oper_state; + switch_handle_t lag_h; } switch_api_lag_member_info_t; typedef struct switch_lag_member_info_s { @@ -85,6 +88,17 @@ typedef struct switch_lag_member_info_s { switch_node_t node; } switch_lag_member_info_t; +// Different modes of LAG +typedef enum { + SWITCHAPI_BOND_MODE_BALANCE_RR, + SWITCHAPI_BOND_MODE_ACTIVE_BACKUP, + SWITCHAPI_BOND_MODE_BALANCE_XOR, + SWITCHAPI_BOND_MODE_BROADCAST, + SWITCHAPI_BOND_MODE_LACP, + SWITCHAPI_BOND_MODE_BALANCE_TLB, + SWITCHAPI_BOND_MODE_BALANCE_ALB, +} switchapi_bond_mode_t; + // Switchapi method to allocate LAG handles switch_status_t switch_lag_init(switch_device_t device); @@ -114,6 +128,11 @@ switch_status_t switch_api_lag_update(const switch_device_t device, const switch_handle_t lag_handle, const switch_handle_t lag_member_handle); +// Switchapi method to update LAG member structure +switch_status_t switch_api_lag_member_update( + const switch_device_t device, const switch_handle_t lag_member_handle, + bool oper_state); + // Switchapi method to get LAG attributes switch_status_t switch_api_lag_attribute_get( const switch_device_t device, const switch_handle_t lag_handle, diff --git a/switchlink/sai/switchlink_handle_lag.c b/switchlink/sai/switchlink_handle_lag.c index 4067f27..afe6580 100644 --- a/switchlink/sai/switchlink_handle_lag.c +++ b/switchlink/sai/switchlink_handle_lag.c @@ -77,6 +77,11 @@ static int create_lag(const switchlink_db_interface_info_t* lag_intf, attr_list[ac].value.oid = *lag_h; } ac++; + + attr_list[ac].id = SAI_LAG_ATTR_PORT_VLAN_ID; + attr_list[ac].value.u8 = lag_intf->bond_mode; + ac++; + status = sai_lag_api->create_lag(lag_h, 0, ac, attr_list); } return ((status == SAI_STATUS_SUCCESS) ? 0 : -1); @@ -125,6 +130,30 @@ static int set_lag_attribute(const switchlink_db_interface_info_t* lag_info, return sai_lag_api->set_lag_attribute(lag_info->lag_h, &attr); } +/** + * Routine Description: + * Calls SAI to set lag member attribute + * + * Arguments: + * [in] lag_member_info - lag member info + * [out] oper_state - oper state + * + * Return Values: + * 0 on success + * -1 in case of error + */ +static int set_lag_member_attribute( + const switchlink_db_lag_member_info_t* lag_member_info, + uint8_t oper_state) { + sai_attribute_t attr; + memset(&attr, 0, sizeof(attr)); + + attr.id = SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE; + attr.value.booldata = (oper_state == IF_OPER_DOWN) ? false : true; + return sai_lag_api->set_lag_member_attribute(lag_member_info->lag_member_h, + &attr); +} + /** * Routine Description: * SAI call to create lag member @@ -172,6 +201,14 @@ static int create_lag_member( attr_list[ac].value.u32 = port_id; ac++; + // this SAI attribute is used to propagate oper state to switchapi module. + attr_list[ac].id = SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE; + if (lag_member_intf->oper_state == IF_OPER_DOWN) + attr_list[ac].value.booldata = false; + else + attr_list[ac].value.booldata = true; + ac++; + return sai_lag_api->create_lag_member(lag_member_h, 0, ac, attr_list); } @@ -227,19 +264,30 @@ void switchlink_create_lag(switchlink_db_interface_info_t* lag_intf) { } else { krnlmon_log_debug("Switchlink DB already has LAG config: %s", lag_intf->ifname); - // check if active_slave attribute is updated. - if ((lag_intf->active_slave != lag_info.active_slave) || - (lag_intf->oper_state != lag_info.oper_state)) { - // need to program MEV-TS with new active_slave info - // get the lag member handle with ifindex = active_slave - if ((lag_intf->active_slave != 0) && - (lag_intf->oper_state == IF_OPER_UP)) { - switchlink_db_lag_member_info_t lag_member_info; - memset(&lag_member_info, 0, sizeof(switchlink_db_lag_member_info_t)); - lag_member_info.ifindex = lag_intf->active_slave; - status = switchlink_db_get_lag_member_info(&lag_member_info); - if (status == SWITCHLINK_DB_STATUS_SUCCESS) { - status = set_lag_attribute(&lag_info, lag_member_info.lag_member_h); + if (lag_intf->bond_mode == SWITCHLINK_BOND_MODE_ACTIVE_BACKUP) { + // check if active_slave attribute is updated. + if ((lag_intf->active_slave != lag_info.active_slave) || + (lag_intf->oper_state != lag_info.oper_state)) { + // need to program MEV-TS with new active_slave info + // get the lag member handle with ifindex = active_slave + if ((lag_intf->active_slave != 0) && + (lag_intf->oper_state == IF_OPER_UP)) { + switchlink_db_lag_member_info_t lag_member_info; + memset(&lag_member_info, 0, sizeof(switchlink_db_lag_member_info_t)); + lag_member_info.ifindex = lag_intf->active_slave; + status = switchlink_db_get_lag_member_info(&lag_member_info); + if (status == SWITCHLINK_DB_STATUS_SUCCESS) { + status = set_lag_attribute(&lag_info, lag_member_info.lag_member_h); + if (status) { + krnlmon_log_error( + "newlink: Failed to update switchlink lag: %s, error: %d\n", + lag_intf->ifname, status); + return; + } + } + } else if ((lag_intf->active_slave == 0) || + (lag_intf->oper_state == IF_OPER_DOWN)) { + status = set_lag_attribute(&lag_info, 0); if (status) { krnlmon_log_error( "newlink: Failed to update switchlink lag: %s, error: %d\n", @@ -247,24 +295,15 @@ void switchlink_create_lag(switchlink_db_interface_info_t* lag_intf) { return; } } - } else if ((lag_intf->active_slave == 0) || - (lag_intf->oper_state == IF_OPER_DOWN)) { - status = set_lag_attribute(&lag_info, 0); - if (status) { - krnlmon_log_error( - "newlink: Failed to update switchlink lag: %s, error: %d\n", - lag_intf->ifname, status); - return; - } - } - lag_info.oper_state = lag_intf->oper_state; - lag_info.active_slave = lag_intf->active_slave; + lag_info.oper_state = lag_intf->oper_state; + lag_info.active_slave = lag_intf->active_slave; + } } - if (memcmp(&(lag_info.mac_addr), &(lag_intf->mac_addr), + if (memcmp(&lag_info.mac_addr, &lag_intf->mac_addr, sizeof(switchlink_mac_addr_t))) { - memcpy(&(lag_info.mac_addr), &(lag_intf->mac_addr), + memcpy(&lag_info.mac_addr, &lag_intf->mac_addr, sizeof(switchlink_mac_addr_t)); // Delete if RMAC is configured previously, and create this new RMAC. @@ -357,6 +396,32 @@ void switchlink_create_lag_member( // lag member has already been created krnlmon_log_debug("Switchlink DB already has LAG Member config: %s", lag_member_intf->ifname); + + if (lag_member_intf->is_lacp_member) { + // check if lag member oper_state has changed + if (lag_member_info.oper_state != lag_member_intf->oper_state) { + status = set_lag_member_attribute(&lag_member_info, + lag_member_intf->oper_state); + if (status) { + krnlmon_log_error( + "newlink: Failed to update switchlink lag member: %s, error: %d\n", + lag_member_intf->ifname, status); + return; + } + // update the oper_state in the switchlink db + lag_member_info.oper_state = lag_member_intf->oper_state; + status = switchlink_db_update_lag_member_oper_state(&lag_member_info); + if (status) { + krnlmon_log_error( + "newlink: Failed to update oper state of lag member: %s, error: " + "%d\n", + lag_member_intf->ifname, status); + return; + } + lag_member_intf->lag_member_h = lag_member_info.lag_member_h; + } + } + return; } diff --git a/switchlink/switchlink_db.c b/switchlink/switchlink_db.c index c12b101..e4dbfc1 100644 --- a/switchlink/switchlink_db.c +++ b/switchlink/switchlink_db.c @@ -1133,6 +1133,34 @@ switchlink_db_status_t switchlink_db_delete_lag_member( return SWITCHLINK_DB_STATUS_ITEM_NOT_FOUND; } +/** + * Routine Description: + * Updates oper_state of lag member in switchlink database + * + * Arguments: + * [in] lag_member_info - switchlink database lag member info + * + * Return Values: + * SWITCHLINK_DB_STATUS_SUCCESS on success + * SWITCHLINK_DB_STATUS_ITEM_NOT_FOUND otherwise + */ +switchlink_db_status_t switchlink_db_update_lag_member_oper_state( + switchlink_db_lag_member_info_t* lag_member_info) { + krnlmon_assert(lag_member_info != NULL); + tommy_node* node = tommy_list_head(&switchlink_db_lag_member_obj_list); + while (node) { + switchlink_db_lag_member_obj_t* obj = node->data; + krnlmon_assert(obj != NULL); + node = node->next; + if ((lag_member_info->ifindex == obj->lag_member_info.ifindex)) { + obj->lag_member_info.oper_state = lag_member_info->oper_state; + return SWITCHLINK_DB_STATUS_SUCCESS; + } + } + + return SWITCHLINK_DB_STATUS_ITEM_NOT_FOUND; +} + /** * Routine Description: * Get lag member info from switchlink database diff --git a/switchlink/switchlink_db.h b/switchlink/switchlink_db.h index e26d7d8..daff307 100644 --- a/switchlink/switchlink_db.h +++ b/switchlink/switchlink_db.h @@ -137,6 +137,7 @@ typedef struct switchlink_db_lag_member_info_ { switchlink_handle_t lag_h; switchlink_mac_addr_t mac_addr; switchlink_mac_addr_t perm_hwaddr; + bool is_lacp_member; } switchlink_db_lag_member_info_t; /*** interface ***/ @@ -258,6 +259,9 @@ extern switchlink_db_status_t switchlink_db_add_lag_member( extern switchlink_db_status_t switchlink_db_delete_lag_member( switchlink_db_lag_member_info_t* lag_member_info); +extern switchlink_db_status_t switchlink_db_update_lag_member_oper_state( + switchlink_db_lag_member_info_t* lag_member_info); + extern switchlink_db_status_t switchlink_db_get_lag_member_info( switchlink_db_lag_member_info_t* lag_member_info); diff --git a/switchlink/switchlink_link.c b/switchlink/switchlink_link.c index d8b9858..f6ffc7f 100644 --- a/switchlink/switchlink_link.c +++ b/switchlink/switchlink_link.c @@ -61,6 +61,7 @@ struct link_attrs { switchlink_mac_addr_t perm_hwaddr; // lag member attributes uint8_t slave_state; + uint16_t aggr_id; }; static const switchlink_mac_addr_t null_mac = {0, 0, 0, 0, 0, 0}; @@ -236,6 +237,11 @@ static void process_info_lag_member_data_attr( nla_len(infoslavedata)); } break; + case IFLA_BOND_SLAVE_AD_AGGREGATOR_ID: + attrs->aggr_id = nla_get_u16(infoslavedata); + krnlmon_log_debug("IFLA Bond Slave AD Aggregator ID: %d\n", + attrs->aggr_id); + break; default: break; } @@ -362,7 +368,8 @@ void switchlink_process_link_msg(const struct nlmsghdr* nlmsg, int msgtype) { intf_info.active_slave = attrs.active_slave; intf_info.link_type = SWITCHLINK_LINK_TYPE_BOND; intf_info.intf_type = SWITCHLINK_INTF_TYPE_L3; - if (intf_info.bond_mode == SWITCHLINK_BOND_MODE_ACTIVE_BACKUP) { + if (intf_info.bond_mode == SWITCHLINK_BOND_MODE_ACTIVE_BACKUP || + intf_info.bond_mode == SWITCHLINK_BOND_MODE_LACP) { switchlink_create_lag(&intf_info); } else { krnlmon_log_debug("bond mode:%d isn't supported\n", @@ -426,6 +433,7 @@ void switchlink_process_link_msg(const struct nlmsghdr* nlmsg, int msgtype) { lag_member_info.ifindex = ifmsg->ifi_index; lag_member_info.oper_state = attrs.oper_state; lag_member_info.slave_state = attrs.slave_state; + if (attrs.aggr_id != 0) lag_member_info.is_lacp_member = true; memcpy(&(lag_member_info.mac_addr), &(attrs.mac_addr), sizeof(switchlink_mac_addr_t)); memcpy(&(lag_member_info.perm_hwaddr), &(attrs.perm_hwaddr), diff --git a/switchsai/sailag.c b/switchsai/sailag.c index e9a501f..bf56186 100644 --- a/switchsai/sailag.c +++ b/switchsai/sailag.c @@ -149,6 +149,16 @@ static sai_status_t sai_create_lag(_Out_ sai_object_id_t* lag_id, lag_h = attribute->value.oid; + attribute = + get_attr_from_list(SAI_LAG_ATTR_PORT_VLAN_ID, attr_list, attr_count); + if (attribute == NULL) { + status = SAI_STATUS_INVALID_PARAMETER; + krnlmon_log_error("missing attribute %s", sai_status_to_string(status)); + return status; + } + + api_lag_info.bond_mode = attribute->value.u8; + // This means lag interface already created, adding RMAC now if (lag_h) { switch_api_lag_attribute_get(switch_id, lag_h, &api_lag_info); @@ -260,6 +270,7 @@ static sai_status_t sai_create_lag_member( } lag_h = attribute->value.oid; + api_lag_member_info.lag_h = lag_h; attribute = get_attr_from_list(SAI_LAG_MEMBER_ATTR_PORT_ID, attr_list, attr_count); @@ -271,6 +282,16 @@ static sai_status_t sai_create_lag_member( api_lag_member_info.port_id = attribute->value.u32; + attribute = get_attr_from_list(SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE, attr_list, + attr_count); + if (attribute == NULL) { + status = SAI_STATUS_INVALID_PARAMETER; + krnlmon_log_error("missing attribute %s", sai_status_to_string(status)); + return status; + } + + api_lag_member_info.oper_state = attribute->value.booldata; + switch_status = switch_api_lag_member_create(switch_id, &api_lag_member_info, &lag_member_h); status = sai_switch_status_to_sai_status(switch_status); @@ -348,6 +369,35 @@ static sai_status_t sai_set_lag_attribute(_In_ sai_object_id_t lag_id, return (sai_status_t)status; } +/** + * Routine Description: + * Set LAG member attribute. + * + * Arguments: + * [in] lag_member_id - LAG member ID + * [in] attr - SAI attribute of LAG object + * + * Return Values: + * SAI_STATUS_SUCCESS on success + * Failure status code on error + */ +static sai_status_t sai_set_lag_member_attribute( + _In_ sai_object_id_t lag_member_id, _In_ const sai_attribute_t* attr) { + sai_status_t status = SAI_STATUS_SUCCESS; + switch_status_t switch_status = SWITCH_STATUS_SUCCESS; + switch_handle_t lag_member_h = lag_member_id; + bool oper_state = attr->value.booldata; + + switch_status = switch_api_lag_member_update(0, lag_member_h, oper_state); + status = sai_switch_status_to_sai_status(switch_status); + if (status != SAI_STATUS_SUCCESS) { + krnlmon_log_error("Failed to update lag member, error: %s", + sai_status_to_string(status)); + } + + return (sai_status_t)status; +} + /** * LAG methods table retrieved with sai_api_query() */ @@ -357,6 +407,7 @@ sai_lag_api_t lag_api = { .create_lag_member = sai_create_lag_member, .remove_lag_member = sai_remove_lag_member, .set_lag_attribute = sai_set_lag_attribute, + .set_lag_member_attribute = sai_set_lag_member_attribute, }; sai_status_t sai_lag_initialize(sai_api_service_t* sai_api_service) {