Presently the client serializes all attributes it knows about when sending Create and Update requests to the controller. By contrast, the official web UI only sends a subset of attributes that appear relevant. When retrieving resources with the Get operation, some attributes that the controller doesn't think we should care about get omitted, and others that we've set previously (that the controller never expected to see) get returned. Because we set far more attributes than the web UI sets, this results in two classes of bugs:
- There appears to be perpetual differences between local state and what's stored on the controller, because we are trying to set an attribute that the controller omits in its response. This results in Terraform diffs that never converge.
- When attributes that the controller isn't expecting are later made available to the web UI client, this causes the web UI to behave incorrectly. For instance, a network configured as WAN will show "LAN" on the network list. There may be more serious bugs lurking.
Acknowledging that this is a poorly-behaved API service (it's not exactly documented or supported), it's still an issue that needs a solution. Has anyone given this any thought?
It seems like there needs to be a few decisions:
- Do we try to fix this?
- How should we go about suppressing fields? How do we figure out what to suppress?
- Knowing the best plan for (2), do we still think this is a tractable problem?
I suggest:
- Yes
- Making requests, cataloging behavior, and hand-crafting a set of conditions that need to be true for a list of fields to exist in the request (and contribute to a diff), along with whether fields are required, and what their defaults are.
- I don't know.
For reference, here are some JSON blobs (sorted fields):
Network
Creating a WAN in the web UI:
{
"name": "LTE",
"purpose": "wan",
"report_wan_event": true,
"wan_dhcp_options": [],
"wan_egress_qos": "",
"wan_ip_aliases": [],
"wan_load_balance_type": "failover-only",
"wan_load_balance_weight": 50,
"wan_networkgroup": "WAN2",
"wan_type": "dhcp",
"wan_type_v6": "disabled"
}
Retrieving this object back from the controller adds an _id and site attributes.
Creating a LAN in the web UI:
{
"dhcp_relay_enabled": false,
"dhcpd_dns_enabled": false,
"dhcpd_enabled": true,
"dhcpd_gateway_enabled": false,
"dhcpd_leasetime": 86400,
"dhcpd_start": "10.0.0.6",
"dhcpd_stop": "10.0.0.254",
"dhcpd_time_offset_enabled": false,
"enabled": true,
"gateway_type": "default",
"ip_subnet": "10.0.0.1/24",
"ipv6_interface_type": "none",
"ipv6_pd_start": "::2",
"ipv6_pd_stop": "::7d1",
"is_nat": true,
"name": "Test",
"nat_outbound_ip_addresses": [],
"networkgroup": "LAN",
"purpose": "corporate",
"vlan": "22",
"vlan_enabled": true
}
Note the entirely different field sets, such as networkgroup.
Switch Port Profile
Separately, here's a GET of an existing switch port profile:
{
"_id": "...omitted...",
"site_id": "...omitted...",
"name": "IoT",
"forward": "native",
"isolation": false,
"native_networkconf_id": "...omitted...",
"attr_no_edit": true
}
Here's the Terraform config:
resource "unifi_port_profile" "iot" {
name = "IoT"
native_networkconf_id = unifi_network.iot.id
}
But it sees this diff (with my commentary):
~ resource "unifi_port_profile" "iot" {
~ autoneg = false -> true // the web UI says "link speed" = "Autonegotiate"
+ dot1x_ctrl = "force_authorized" // the web UI says "802.1X Control" = "Force authorized"
~ dot1x_idle_timeout = 0 -> 300
id = "...omitted..."
~ lldpmed_enabled = false -> true // Enabled in the web UI
name = "IoT"
+ op_mode = "switch"
~ stp_port_mode = false -> true // Enabled in the web UI
# (25 unchanged attributes hidden)
}
And here's what it sends to the controller:
{
"_id": "5d6f172bf25e119aa7e3005e",
"site_id": "default",
"autoneg": true,
"dot1x_ctrl": "force_authorized",
"dot1x_idle_timeout": 300,
"egress_rate_limit_kbps_enabled": false,
"forward": "native",
"full_duplex": false,
"isolation": false,
"lldpmed_enabled": true,
"lldpmed_notify_enabled": false,
"native_networkconf_id": "5d6f172bf25e119aa7e3005d",
"name": "IoT",
"op_mode": "switch",
"port_security_enabled": false,
"stormctrl_bcast_enabled": false,
"stormctrl_mcast_enabled": false,
"stormctrl_ucast_enabled": false,
"stp_port_mode": true,
"voice_networkconf_id": ""
}
(This particular update gets a 400 api.err.NoEdit but that's another issue.)
Presently the client serializes all attributes it knows about when sending Create and Update requests to the controller. By contrast, the official web UI only sends a subset of attributes that appear relevant. When retrieving resources with the Get operation, some attributes that the controller doesn't think we should care about get omitted, and others that we've set previously (that the controller never expected to see) get returned. Because we set far more attributes than the web UI sets, this results in two classes of bugs:
Acknowledging that this is a poorly-behaved API service (it's not exactly documented or supported), it's still an issue that needs a solution. Has anyone given this any thought?
It seems like there needs to be a few decisions:
I suggest:
For reference, here are some JSON blobs (sorted fields):
Network
Creating a WAN in the web UI:
{ "name": "LTE", "purpose": "wan", "report_wan_event": true, "wan_dhcp_options": [], "wan_egress_qos": "", "wan_ip_aliases": [], "wan_load_balance_type": "failover-only", "wan_load_balance_weight": 50, "wan_networkgroup": "WAN2", "wan_type": "dhcp", "wan_type_v6": "disabled" }Retrieving this object back from the controller adds an
_idandsiteattributes.Creating a LAN in the web UI:
{ "dhcp_relay_enabled": false, "dhcpd_dns_enabled": false, "dhcpd_enabled": true, "dhcpd_gateway_enabled": false, "dhcpd_leasetime": 86400, "dhcpd_start": "10.0.0.6", "dhcpd_stop": "10.0.0.254", "dhcpd_time_offset_enabled": false, "enabled": true, "gateway_type": "default", "ip_subnet": "10.0.0.1/24", "ipv6_interface_type": "none", "ipv6_pd_start": "::2", "ipv6_pd_stop": "::7d1", "is_nat": true, "name": "Test", "nat_outbound_ip_addresses": [], "networkgroup": "LAN", "purpose": "corporate", "vlan": "22", "vlan_enabled": true }Note the entirely different field sets, such as
networkgroup.Switch Port Profile
Separately, here's a GET of an existing switch port profile:
{ "_id": "...omitted...", "site_id": "...omitted...", "name": "IoT", "forward": "native", "isolation": false, "native_networkconf_id": "...omitted...", "attr_no_edit": true }Here's the Terraform config:
But it sees this diff (with my commentary):
And here's what it sends to the controller:
{ "_id": "5d6f172bf25e119aa7e3005e", "site_id": "default", "autoneg": true, "dot1x_ctrl": "force_authorized", "dot1x_idle_timeout": 300, "egress_rate_limit_kbps_enabled": false, "forward": "native", "full_duplex": false, "isolation": false, "lldpmed_enabled": true, "lldpmed_notify_enabled": false, "native_networkconf_id": "5d6f172bf25e119aa7e3005d", "name": "IoT", "op_mode": "switch", "port_security_enabled": false, "stormctrl_bcast_enabled": false, "stormctrl_mcast_enabled": false, "stormctrl_ucast_enabled": false, "stp_port_mode": true, "voice_networkconf_id": "" }(This particular update gets a 400
api.err.NoEditbut that's another issue.)