paultyng/terraform-provider-unifi

unifi_network invalid payload

Opened this issue · 10 comments

Recently I have noticed that we are getting the following error quite a lot:

╷
│ Error: api.err.InvalidPayload (400 ) for POST https://xxxxxxxxxxxxxxxxxxxxx/api/s/1ugonv7v/rest/networkconf
│ 
│   with unifi_network.management["f2da017a-cf58-4ff6-943a-ae71eba1981b"],
│   on unifi_customer_networks.tf line 74, in resource "unifi_network" "management":
│   74: resource "unifi_network" "management" {
│ 
╵

I turned on trace to see if unifi gives me a better error:

---[ REQUEST ]---------------------------------------
POST /api/s/1ugonv7v/rest/networkconf HTTP/1.1
Host: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
User-Agent: terraform-provider-unifi/0.1
Content-Length: 1606
Content-Type: application/json; charset=utf-8
Cookie: unifises=gdaBAdkkmjwmNgGI24scY5AbZjzAnFKM; csrf_token=gdSuAP01vQHV7S2Fl36TOd0p8GlOpfKu
Accept-Encoding: gzip

{
 "auto_scale_enabled": false,
 "dhcpd_boot_enabled": false,
 "dhcpd_boot_server": "",
 "dhcpd_dns_1": "",
 "dhcpd_dns_2": "",
 "dhcpd_dns_3": "",
 "dhcpd_dns_4": "",
 "dhcpd_dns_enabled": false,
 "dhcpd_enabled": false,
 "dhcpd_gateway": "",
 "dhcpd_gateway_enabled": false,
 "dhcpd_ip_1": "",
 "dhcpd_ip_2": "",
 "dhcpd_ip_3": "",
 "dhcpd_leasetime": 86400,
 "dhcpd_mac_1": "",
 "dhcpd_mac_2": "",
 "dhcpd_mac_3": "",
 "dhcpd_ntp_1": "",
 "dhcpd_ntp_2": "",
 "dhcpd_ntp_enabled": false,
 "dhcpd_start": "",
 "dhcpd_stop": "",
 "dhcpd_time_offset_enabled": false,
 "dhcpd_unifi_controller": "",
 "dhcpdv6_dns_auto": false,
 "dhcpdv6_enabled": false,
 "dhcpd_wins_1": "",
 "dhcpd_wins_2": "",
 "dhcpd_wins_enabled": false,
 "dhcp_relay_enabled": false,
 "dhcpguard_enabled": false,
 "dpi_enabled": false,
 "dpigroup_id": "",
 "domain_name": "",
 "enabled": true,
 "exposed_to_site_vpn": false,
 "gateway_device": "",
 "igmp_fastleave": false,
 "igmp_querier": "",
 "igmp_snooping": false,
 "igmp_supression": false,
 "ipsec_dynamic_routing": false,
 "ipsec_pfs": false,
 "ipv6_interface_type": "none",
 "ipv6_pd_prefixid": "",
 "ipv6_ra_enabled": false,
 "is_nat": false,
 "lte_lan_enabled": false,
 "mac_override": "",
 "mac_override_enabled": false,
 "name": "management",
 "networkgroup": "LAN",
 "pptpc_require_mppe": false,
 "purpose": "vlan-only",
 "radiusprofile_id": "",
 "remote_site_id": "",
 "report_wan_event": false,
 "require_mschapv2": false,
 "upnp_lan_enabled": false,
 "usergroup_id": "",
 "vlan": 101,
 "vlan_enabled": true,
 "vpn_client_default_route": false,
 "vpn_client_pull_dns": false,
 "wan_dns1": "",
 "wan_dns2": "",
 "wan_dns3": "",
 "wan_dns4": "",
 "wan_gateway": "",
 "wan_gateway_v6": "",
 "wan_ipv6": "",
 "wan_provider_capabilities": {},
 "wan_smartq_enabled": false,
 "wan_vlan_enabled": false
}
-----------------------------------------------------: timestamp=2021-10-11T16:13:09.257+1300
2021-10-11T16:13:09.324+1300 [INFO]  provider.terraform-provider-unifi_v0.30.0: 2021/10/11 16:13:09 [DEBUG] Unifi API Response Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 400
Content-Length: 225
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
Connection: keep-alive
Content-Type: application/json;charset=UTF-8
Date: Mon, 11 Oct 2021 03:13:09 GMT
Server: nginx/1.15.12
Vary: Origin
X-Frame-Options: DENY

{
 "meta": {
  "rc": "error",
  "validationError": {
   "field": "wan_gateway",
   "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
  },
  "msg": "api.err.InvalidPayload"
 },
 "data": []
}
-----------------------------------------------------: timestamp=2021-10-11T16:13:09.323+1300

Should we make wan_gateway a string pointer so that it's not marshalled to an empty string when this request is made?

Adding some more context. I am running this against version 6.4.54

And here is a terraform snippet that can reproduce the error:

resource "unifi_site" "customer" {
  description = "to_delete"
}

resource "unifi_network" "management" {
  site        = unifi_site.customer.name
  name        = "management"
  purpose     = "vlan-only"
  vlan_id     = 101
}

Adding in a dummy value for wan_gateway is the workaround I am currently using. My workaround in production looks like this:

resource "unifi_network" "management" {
  site        = unifi_site.customer.name
  name        = "management"
  purpose     = "vlan-only"
  vlan_id     = 101
  wan_gateway = "0.0.0.0" 

  lifecycle {
    ignore_changes = [
      wan_gateway,
    ]
  }
}

Can confirm getting same POST Message (400) on apply.....using provider = 0.34.0 and unifi os 1.11.0-16 (EA) for UDM Pro...can provide screenshots logs if needed...thanks

I added a test for this, but I suspect I need to upgrade the unifi version that it's run against: https://github.com/paultyng/terraform-provider-unifi/pull/197/checks?check_run_id=3887403832

Hmm, the tests should already be running against the latest versions

Good stuff....still super new to terraform, today was the first day I could actually even get any response, I am a software dev, but very green....Thank you guys for your work!!!

I think this is the same issue as #107, but I may not be able to investigate further for another few weeks. In the mean time, if anyone can produce a reproduction case (ideally an acceptance test in the repo), that should help a lot.

I'm running into the same issue at present, however the workaround mentioned above doesn't always work.

adding

  lifecycle {
    ignore_changes = [
      wan_gateway,
    ]
  }

still results in a 400 InvalidPayload error when a change to the network needs to be applied

removing this and having just a dummy "wan_gateway" results in a successful run when another change is needing to be applied (ie IP Range change), but without the lifecycle block it results in wanting to apply the wan_gateway on every run but results in a very misleading "Not Found" error. All API calls return 200 so not sure where that particular error is coming from.

Also on 6.4.54 (Cloud Key)
TF snippet that I'm seeing the error with

resource "unifi_network" "Main" {
  dhcp_dns      = ["172.16.12.102"]
  dhcp_enabled  = true
  dhcp_lease    = "3600"
  dhcp_start    = "172.16.10.6"
  dhcp_stop     = "172.16.10.254"
  name	        = "Main"
  purpose	= "corporate"
  site          = var.site
  subnet        = "172.16.10.1/24"
  vlan_id       = 10
  wan_gateway   = "0.0.0.0" 
  
  lifecycle {
    ignore_changes = [
      wan_gateway,
    ]
  }
}

I have the same issue...
Unifi: 6.4.54

Any changes to a network, be it a dhcp scope or a name or other parameter results in:

╷
│ Error: api.err.InvalidPayload (400 ) for PUT https://x.x.x.x:8443/api/s/default/rest/networkconf/6194484b5764540124b3c76e
│ 
│   with unifi_network.network["iot"],
│   on testing.tf line 49, in resource "unifi_network" "network":
│   49: resource "unifi_network" "network" {
│ 
╵

network.tf:

output "network" {
  value = local.network
}

locals {
  network = defaults(var.network, {
    site                = "default"
    dhcp_dns            = ""
    dhcp_enabled        = true
    dhcp_lease          = 86400
    dhcp_relay_enabled  = false
    dhcpd_boot_enabled  = false
    domain_name         = "mydomain.com"
    igmp_snooping       = false
    ipv6_interface_type = "none"
    ipv6_ra_enable      = false
    network_group       = "LAN"
    wan_dns             = ""
    wan_egress_qos      = 0
    wan_gateway         = "0.0.0.0"
    purpose             = "corporate"
  })
}

resource "unifi_network" "network" {
  for_each            = { for i, v in local.network : i => v }
  name                = each.value.name
  dhcp_enabled        = each.value.dhcp_enabled
  dhcp_lease          = each.value.dhcp_lease
  dhcp_relay_enabled  = each.value.dhcp_relay_enabled
  dhcp_start          = each.value.dhcp_start
  dhcp_stop           = each.value.dhcp_stop
  dhcpd_boot_enabled  = each.value.dhcpd_boot_enabled
  domain_name         = each.value.domain_name
  igmp_snooping       = each.value.igmp_snooping
  ipv6_interface_type = each.value.ipv6_interface_type
  ipv6_ra_enable      = each.value.ipv6_ra_enable
  network_group       = each.value.network_group
  purpose             = each.value.purpose
  site                = unifi_site.sirfragalot.name
  subnet              = each.value.subnet
  vlan_id             = each.value.vlan_id
  wan_egress_qos      = each.value.wan_egress_qos
  wan_gateway         = each.value.wan_gateway
  wan_dns             = each.value.wan_dns
  dhcp_dns            = each.value.dhcp_dns

  lifecycle {
    ignore_changes = [
      wan_gateway,
    ]
  }
}

network-variables.tf:

variable "network" {
  type = map(object({
    name                = string
    site                = optional(string)
    dhcp_dns            = optional(list(string))
    dhcp_enabled        = optional(bool)
    dhcp_lease          = optional(number)
    dhcp_relay_enabled  = optional(bool)
    dhcpd_boot_enabled  = optional(bool)
    domain_name         = optional(string)
    igmp_snooping       = optional(bool)
    ipv6_interface_type = optional(string)
    ipv6_ra_enable      = optional(bool)
    network_group       = optional(string)
    wan_dns             = optional(list(string))
    wan_egress_qos      = optional(number)
    wan_gateway         = optional(string)
    purpose             = optional(string)
    dhcp_start          = string
    dhcp_stop           = string
    subnet              = string
    vlan_id             = number
  }))
}

network.auto.tfvars:

network = {
  main = {
    name       = "LAN"
    dhcp_start = "192.168.1.6"
    dhcp_stop  = "192.168.1.254"
    subnet     = "192.168.1.0/24"
    vlan_id    = 0
  }
  iot = {
    name       = "IoT"
    dhcp_start = "10.32.12.10"
    dhcp_stop  = "10.32.12.100"
    subnet     = "10.32.12.0/24"
    vlan_id    = 107
    dhcp_dns   = ["192.168.1.53", "192.168.1.153"]
    wan_dns    = []
  }
  cctv = {
    name       = "CCTV"
    dhcp_start = "10.32.13.10"
    dhcp_stop  = "10.32.13.100"
    subnet     = "10.32.13.0/24"
    vlan_id    = 999
    dhcp_dns   = []
    wan_dns    = []
  }
}

Did some more investigation here, most of these are already omitempty. I think explicit null should probably be used instead of 0.0.0.0 or empty strings. I couldn't get it to fail with exactly the same issues in my testing though, but working on it here: #203

My guess though is this has to do with how certain fields are only roundtripped if the purpose is WAN, but its having issues with the validation. My understanding was the validation should allow for empty, but I guess its explicitly looking for null / unset.

evenh commented

I'm encountering this specific issue as well, in the same way that @kurtmc reports.