-
Notifications
You must be signed in to change notification settings - Fork 8
Initial implementation for switch port settings #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@internet-diglett and I spent a few hours today updating the implementation to fix some issues with serialization and typing as well as chatting about a path forward. There's currently an issue with the nested EDIT: See #418 (comment) for more details but
Blocks: map[string]schema.Block{
"link": schema.ListNestedBlock{
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
},
},
Blocks: map[string]schema.Block{
"address": schema.ListNestedBlock{
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"addr": schema.StringAttribute{
Required: true,
},
},
Blocks: map[string]schema.Block{
"address_lot": schema.SingleNestedBlock{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"name": schema.StringAttribute{
Required: true,
},
},
},
},
},
},
},
},
},
},
resource "oxide_switch_port_configuration" "example" {
name = "foo"
description = "Managed by Terraform."
link {
name = "foo"
address {
addr = "foo"
address_lot {
name = "bar"
}
}
}
}
> terraform plan
oxide_switch_port_configuration.example: Refreshing state... [id=973beb3d-e658-40c3-8dba-5b3d10ef42c0]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. |
I did some more testing and if you change the schema for both
EDIT: See #418 (comment) for more details but |
This PR aims to improve the usability of the switch port settings API by returning data of similar shape and content as what users provide when the create the switch port settings. Issues resolved: * Before this PR, you only need one call to create a complete switch port settings object, but you have to perform several lookups to get the complete switch port configuration. We were already returning the object ids of the nested objects, so it makes a lot of sense to just return the actual object (especially since the user supplied the entire object when they configured it) * The `HashMap<String_of_link_name, T>` pattern also did not give the expected behavior in our go client generation. Changing this to a `Vec<T_with_link_name_inside>` matches what the user provides during creation and also resolves this behavior. * Some of our `Vec<T>` fields can be empty but must be present. In the Go client, an empty array is omitted from the json object. We needed to add a `default` annotation to these fields so Serde handles this situation correctly. ## Related - oxidecomputer/oxide.go#278 - oxidecomputer/terraform-provider-oxide#426 Closes #5780
This PR aims to improve the usability of the switch port settings API by returning data of similar shape and content as what users provide when the create the switch port settings. Issues resolved: * Before this PR, you only need one call to create a complete switch port settings object, but you have to perform several lookups to get the complete switch port configuration. We were already returning the object ids of the nested objects, so it makes a lot of sense to just return the actual object (especially since the user supplied the entire object when they configured it) * The `HashMap<String_of_link_name, T>` pattern also did not give the expected behavior in our go client generation. Changing this to a `Vec<T_with_link_name_inside>` matches what the user provides during creation and also resolves this behavior. * Some of our `Vec<T>` fields can be empty but must be present. In the Go client, an empty array is omitted from the json object. We needed to add a `default` annotation to these fields so Serde handles this situation correctly. ## Related - oxidecomputer/oxide.go#278 - oxidecomputer/terraform-provider-oxide#426 Closes #5780
fbde127
to
bf25088
Compare
Quick update here. I haven't forgotten about this. A few things came up this week that I was handling first. Will review this next week. |
This patch made the following changes. * Renamed the resource from `oxide_switch_port_configuration` to `oxide_switch_port_settings` to be consistent with the upstream API. * Changed the schema to reflect the schema from the `networking_switch_port_settings_create` API. Further work is required to account for the schema returned by the `networking_switch_port_settings_view` API. * Created new functions to convert to and from Terraform models to Oxide models.
bf25088
to
416afe0
Compare
I made the following changes in 416afe0.
I following Running Omicron (Simulated) and used it to test these changes locally with the following configuration. resource "oxide_switch_port_settings" "example" {
name = "example"
description = "example"
addresses = [
{
link_name = "phy0"
addresses = [
{
address = "10.85.0.30/24"
address_lot = "example"
},
]
},
]
links = [
{
autoneg = false
fec = "none"
lldp = {
enabled = false
}
mtu = 1500
link_name = "phy0"
speed = "speed1_g"
},
]
port_config = {
geometry = "qsfp28x1"
}
routes = [
{
link_name = "phy0"
routes = [
{
dst = "0.0.0.0/0"
gw = "10.85.0.1"
},
{
dst = "1.1.1.1/32"
gw = "10.85.0.1"
},
]
},
]
} However, I received the following error.
It's likely that a simulated Omicron isn't sufficient to test these changes since that environment does not run the Rack Setup Service (RSS) to initialize the networking settings. I'll pair with @internet-diglett tomorrow since they have environments to test against. The sheer size of the |
Oof, looks like more fun with enums and Go 🙃 Looking at that error, I think the API isn't accepting your POST request, it's a 400 so I don't think this would work anywhere, sadly.
I think the API isn't recognising the way you are passing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked at the entire PR but took a quick look at where the error you were encountering might be occurring. I hope this helps!
Thanks Karen! I thought I covered all those With the changes in 3d7d79a I'm now getting the following error.
Which the logs tell me is an issue with the serialization for
|
I was thinking the In an effort to eliminate uncertainty from the Go SDK and Terraform, I tested against the API directly with the following request body. Note {
"addresses": [
{
"addresses": [
{
"address": "10.85.0.30/24",
"address_lot": "example",
"vlan_id": 0
}
],
"link_name": "phy0"
}
],
"bgp_peers": [],
"description": "example",
"groups": [],
"links": [
{
"autoneg": false,
"fec": "none",
"link_name": "phy0",
"lldp": {
"enabled": false
},
"mtu": 1500,
"speed": "speed1_g",
"tx_eq": {}
}
],
"name": "example",
"port_config": {
"geometry": "qsfp28x1"
},
"routes": [
{
"link_name": "phy0",
"routes": [
{
"dst": "0.0.0.0/0",
"gw": "10.85.0.1",
"rib_priority": 0
}
]
}
]
} I still received an internal server error.
With the same error as before.
I then removed diff --git a/tmp/settings.json b/tmp/settings.json
index de538ff54..ce48cdd43 100644
--- a/tmp/settings.json
+++ b/tmp/settings.json
@@ -38,7 +38,6 @@
{
"dst": "0.0.0.0/0",
"gw": "10.85.0.1",
- "rib_priority": 0
}
]
} Now the error is
I then pushed aaa884d to allow setting
If I'm interpreting all this correctly then somewhere in Omicron |
I renamed the `local_pref` column to `rib_priority` to complete the rename in #6693. I also changed the type of the renamed column from `INT8` to `INT2`, clamping existing values to `0` or `255`. This was missed in #6693 and led to the following error when reading the value from the database. ``` {"msg":"request completed","v":0,"name":"omicron-dev","level":30,"time":"2025-07-11T13:57:48.670343242Z","hostname":"ms-ox01","pid":3113,"uri":"/v1/system/networking/switch-port-settings","method":"POST","req_id":"12b35f5a-2255-4244-a5aa-f9c84032fb81","remote_addr":"127.0.0.1:41766","local_addr":"127.0.0.1:12220","component":"dropshot_external","name":"e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c","error_message_external":"Internal Server Error","error_message_internal":"Unknown diesel error creating SwitchPortSettings called \"example\": Error deserializing field 'local_pref': Received more than 2 bytes while decoding an i16. Was an Integer expression accidentally marked as SmallInt?","latency_us":133766,"response_code":500} ``` The error occurred because the Rust type was `SqlU8` and the database type was `INT8`. Reads would fail because `INT8` columns could not be read into `SqlU8` types without loss of precision. This was caught in oxidecomputer/terraform-provider-oxide#426 when implementing a Terraform provider resource for switch port settings. It's likely that this has been broken since #6693 and any user that attempted to set `rib_priority` in their Rack Setup Service (RSS) would have encountered this error. However, `rib_priority` is an uncommon configuration option and none of our customer's RSS configurations show this as being set. Given this information, it seems safe to assume that no customer has the `rib_priority` column set so the clamping logic implemented here should work well for customer installations.
I submitted oxidecomputer/omicron#8587 to address the issue with |
I renamed the `local_pref` column to `rib_priority` to complete the rename in #6693. I also changed the type of the renamed column from `INT8` to `INT2`, clamping existing values to `0` or `255`. This was missed in #6693 and led to the following error when reading the value from the database. ``` {"msg":"request completed","v":0,"name":"omicron-dev","level":30,"time":"2025-07-11T13:57:48.670343242Z","hostname":"ms-ox01","pid":3113,"uri":"/v1/system/networking/switch-port-settings","method":"POST","req_id":"12b35f5a-2255-4244-a5aa-f9c84032fb81","remote_addr":"127.0.0.1:41766","local_addr":"127.0.0.1:12220","component":"dropshot_external","name":"e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c","error_message_external":"Internal Server Error","error_message_internal":"Unknown diesel error creating SwitchPortSettings called \"example\": Error deserializing field 'local_pref': Received more than 2 bytes while decoding an i16. Was an Integer expression accidentally marked as SmallInt?","latency_us":133766,"response_code":500} ``` The error occurred because the Rust type was `SqlU8` and the database type was `INT8`. Reads would fail because `INT8` columns could not be read into `SqlU8` types without loss of precision. This was caught in oxidecomputer/terraform-provider-oxide#426 when implementing a Terraform provider resource for switch port settings. It's likely that this has been broken since #6693 and any user that attempted to set `rib_priority` in their Rack Setup Service (RSS) would have encountered this error. However, `rib_priority` is an uncommon configuration option and none of our customer's RSS configurations show this as being set. Given this information, it seems safe to assume that no customer has the `rib_priority` column set so the clamping logic implemented here should work well for customer installations.
Haha. Yeah definitely not in this pull request. We'll need the following.
|
Much better! The initial resource is created.
With a no-op plan immediately afterwards.
|
I just have the following work left before this is ready for merge.
|
This is ready for review! I'm going to defer on the I ran the acceptance tests against a simulated Omicron deployment and they pass. The output is below. I also added a lengthy comment to the
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great! Thanks for working on this. It looks like a mammoth undertaking 😅 . Let's add some documentation for the resource and an entry to the changelog as well :)
This one is worth having an example in the examples
directory too I think 🤔
Will get to the rest of the open discussion items tomorrow. Thank you for the review! |
I renamed the `local_pref` column to `rib_priority` to complete the rename in #6693. I also changed the type of the renamed column from `INT8` to `INT2`, clamping existing values to `0` or `255`. This was missed in #6693 and led to the following error when reading the value from the database. ``` {"msg":"request completed","v":0,"name":"omicron-dev","level":30,"time":"2025-07-11T13:57:48.670343242Z","hostname":"ms-ox01","pid":3113,"uri":"/v1/system/networking/switch-port-settings","method":"POST","req_id":"12b35f5a-2255-4244-a5aa-f9c84032fb81","remote_addr":"127.0.0.1:41766","local_addr":"127.0.0.1:12220","component":"dropshot_external","name":"e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c","error_message_external":"Internal Server Error","error_message_internal":"Unknown diesel error creating SwitchPortSettings called \"example\": Error deserializing field 'local_pref': Received more than 2 bytes while decoding an i16. Was an Integer expression accidentally marked as SmallInt?","latency_us":133766,"response_code":500} ``` The error occurred because the Rust type was `SqlU8` and the database type was `INT8`. Reads would fail because `INT8` columns could not be read into `SqlU8` types without loss of precision. This was caught in oxidecomputer/terraform-provider-oxide#426 when implementing a Terraform provider resource for switch port settings. It's likely that this has been broken since #6693 and any user that attempted to set `rib_priority` in their Rack Setup Service (RSS) would have encountered this error. However, `rib_priority` is an uncommon configuration option and none of our customer's RSS configurations show this as being set. Given this information, it seems safe to assume that no customer has the `rib_priority` column set so the clamping logic implemented here should work well for customer installations.
I renamed the `local_pref` column to `rib_priority` to complete the rename in #6693. I also changed the type of the renamed column from `INT8` to `INT2`, clamping existing values to `0` or `255`. This was missed in #6693 and led to the following error when reading the value from the database. ``` {"msg":"request completed","v":0,"name":"omicron-dev","level":30,"time":"2025-07-11T13:57:48.670343242Z","hostname":"ms-ox01","pid":3113,"uri":"/v1/system/networking/switch-port-settings","method":"POST","req_id":"12b35f5a-2255-4244-a5aa-f9c84032fb81","remote_addr":"127.0.0.1:41766","local_addr":"127.0.0.1:12220","component":"dropshot_external","name":"e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c","error_message_external":"Internal Server Error","error_message_internal":"Unknown diesel error creating SwitchPortSettings called \"example\": Error deserializing field 'local_pref': Received more than 2 bytes while decoding an i16. Was an Integer expression accidentally marked as SmallInt?","latency_us":133766,"response_code":500} ``` The error occurred because the Rust type was `SqlU8` and the database type was `INT8`. Reads would fail because `INT8` columns could not be read into `SqlU8` types without loss of precision. This was caught in oxidecomputer/terraform-provider-oxide#426 when implementing a Terraform provider resource for switch port settings. It's likely that this has been broken since #6693 and any user that attempted to set `rib_priority` in their Rack Setup Service (RSS) would have encountered this error. However, `rib_priority` is an uncommon configuration option and none of our customer's RSS configurations show this as being set. Given this information, it seems safe to assume that no customer has the `rib_priority` column set so the clamping logic implemented here should work well for customer installations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing my comments so quickly! Just a few bits to go, and should be ready soon
// 2. This function assumes null values by default to prevent Terraform from | ||
// having a non-empty refresh plan right after a successful apply. For example, | ||
// assume the Terraform configuration omits the `bgp_peers` attribute. After an | ||
// apply, the Oxide `switch_port_settings_create` API returns `"bgp_peers": []` | ||
// and Terraform plans must see this as a null value to match the configuration. | ||
// Otherwise, the Terraform refresh plan will read the value `[]` and attempt to | ||
// set it back to null since the configuration had `bgp_peers` omitted. This is | ||
// why you'll see `if len(settings.Addresses) > 0` conditions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. bgp_peers
is optional, so I can see why we'd ignore an empty field in the response. What about the required ones (Addresses
and Links
)? We could safely assume that they would never be empty in the response no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bgp_peers
is optional in the request but required in the response. For asymmetric cases like this we want to treat []
as null
in Terraform to avoid the non-empty refresh plan. Since the nested structure within bgp_peers
is required a user cannot set bgp_peers = []
in their Terraform configuration so to remove BGP peers they would just omit it from the configuration (i.e., null
). That's what the if len() > 0
condition covers.
For the attributes that are symmetrical (i.e., addresses
) where they are required in the request and required in the response we could remove the if len() > 0
check` if we desired and the behavior would remain the same. I don't have a preference here since the positive case of length being non-zero is what will be executed all the time.
initialConfigTmpl := ` | ||
resource "oxide_switch_port_settings" "{{.BlockName}}" { | ||
name = "{{.SwitchPortSettingsName}}" | ||
description = "Terraform acceptance testing." | ||
|
||
port_config = { | ||
geometry = "qsfp28x1" | ||
} | ||
|
||
addresses = [ | ||
{ | ||
link_name = "phy0" | ||
addresses = [ | ||
{ | ||
address = "0.0.0.0/0" | ||
address_lot_id = "{{.AddressLotID}}" | ||
}, | ||
] | ||
}, | ||
] | ||
|
||
links = [ | ||
{ | ||
link_name = "phy0" | ||
autoneg = false | ||
mtu = 1500 | ||
speed = "speed1_g" | ||
lldp = { | ||
enabled = false | ||
} | ||
}, | ||
] | ||
|
||
routes = [ | ||
{ | ||
link_name = "phy0" | ||
routes = [ | ||
{ | ||
dst = "0.0.0.0/0" | ||
gw = "0.0.0.0" | ||
}, | ||
] | ||
}, | ||
] | ||
} | ||
` | ||
updateConfigTmpl := ` | ||
resource "oxide_switch_port_settings" "{{.BlockName}}" { | ||
name = "{{.SwitchPortSettingsName}}" | ||
description = "Terraform acceptance testing (updated)." | ||
|
||
port_config = { | ||
geometry = "qsfp28x1" | ||
} | ||
|
||
addresses = [ | ||
{ | ||
link_name = "phy0" | ||
addresses = [ | ||
{ | ||
address = "0.0.0.0/0" | ||
address_lot_id = "{{.AddressLotID}}" | ||
}, | ||
] | ||
}, | ||
] | ||
|
||
links = [ | ||
{ | ||
link_name = "phy0" | ||
autoneg = false | ||
mtu = 1500 | ||
speed = "speed1_g" | ||
lldp = { | ||
enabled = true | ||
} | ||
}, | ||
] | ||
|
||
routes = [ | ||
{ | ||
link_name = "phy0" | ||
routes = [ | ||
{ | ||
dst = "0.0.0.0/0" | ||
gw = "0.0.0.0" | ||
}, | ||
] | ||
}, | ||
] | ||
} | ||
` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry! Just noticed this test doesn't include any of the optional attributes. Let's add in one of the two configurations some values for bgp_peers
, groups
, interfaces
, and all of the optional fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added interfaces
for now in ccb557d.
Attempted to add bgp_peers
but that required manually creating a BGP announce set and BGP configuration. Also, when I went to apply this I ran into an API type error that I noticed the other day but didn't dig into. I created oxidecomputer/oxide.go#303 to track this.
I'm not sure what groups
is meant to represent here. @internet-diglett any context you can provide here?
// toNetworkingSwitchPortSettingsCreateParams converts [switchPortSettingsModel` | ||
// to [oxide.NetworkingSwitchPortSettingsCreateParams]. This is far simpler than | ||
// [toSwitchPortSettingsModel] since the Oxide `switch_port_settings_create` API | ||
// request body matches the Terraform schema. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// toNetworkingSwitchPortSettingsCreateParams converts [switchPortSettingsModel` | |
// to [oxide.NetworkingSwitchPortSettingsCreateParams]. This is far simpler than | |
// [toSwitchPortSettingsModel] since the Oxide `switch_port_settings_create` API | |
// request body matches the Terraform schema. | |
// toNetworkingSwitchPortSettingsCreateParams converts `switchPortSettingsModel` | |
// to `oxide.NetworkingSwitchPortSettingsCreateParams`. This is far simpler than | |
// `toSwitchPortSettingsModel` since the Oxide `switch_port_settings_create` API | |
// request body matches the Terraform schema. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used [
and ]
since that's Go doc link syntax and it supports go to definition in LSPs. Happy to change it. Just giving context.
// 1. This function handles the asymmetrical nature of the | ||
// `switch_port_settings_create` API. For example, the request body | ||
// accepts attributes such as `addresses[].addresses[].address` but returns | ||
// `addresses[].address`. This function handles that conversion by creating a | ||
// map of all the respective configurations for a given link name and using it | ||
// to populate the nested model. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious about this one. I'm sure you both talked about this already. But just want to know, @internet-diglett, why can't the API return the same addresses data structure that the switch port settings were created with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't talk about the asymmetry of the API in much detail. As I understand it this Terraform resource is urgently needed to quickly test switch port settings for ongoing networking work. Some of that work is for validating different Oxide deployments and some of that work is going to be used to update the API design for these networking APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's probably no reason it can't, just that we haven't shaped it that way today. The primary focus with this segment of work was to get Terraform configuration working and fixing anything in the API that fundamentally prevented it from working. This implementation will not be the final form. Going forward we will be extracting certain parts of the config into separate resources because we have determined they do not need to be tied to a switch port (like routes), adding things like etags, ipv6 support, etc. so we kinda had to draw a line somewhere for the scope of work we wanted to focus on for the time being.
@internet-diglett when I create multiple "links": [
{
"port_settings_id": "698f4600-0f9d-48d0-ad8b-caf00e7b0e92",
"link_name": "phy0",
"mtu": 1500,
"fec": null,
"speed": "speed1_g",
"autoneg": false,
"lldp_link_config": null,
"tx_eq_config": null
},
{
"port_settings_id": "698f4600-0f9d-48d0-ad8b-caf00e7b0e92",
"link_name": "phy1",
"mtu": 1500,
"fec": null,
"speed": "speed10_g",
"autoneg": false,
"lldp_link_config": {
"id": "42f34fbd-6556-4dff-ab4b-e387ed7a761a",
"enabled": true,
"link_name": "phy0",
"link_description": null,
"chassis_id": null,
"system_name": null,
"system_description": null,
"management_ip": null
},
"tx_eq_config": null
}
], This results in a non-empty refresh plan.
Wondering if this is a bug in the Omicron API. Given the goals of this resource discussed in #426 (comment) this isn't a blocker. I just wanted to note it. |
I've created an issue here: Can you attach your terraform config to that for reproduction and testing? |
Done! |
No description provided.