crossplane-contrib / provider-jet-equinix

Crossplane Provider for Equinix Metal, Fabric, and Network Edge
https://marketplace.upbound.io/providers/equinix/provider-jet-equinix/
Apache License 2.0
8 stars 4 forks source link

Can't create VRF ReservedIPBlock (will not resolve to Ready) #50

Open displague opened 4 months ago

displague commented 4 months ago

What happened?

Given the following XRD, a ReservedIPBlock will fail to reconcile with errors about quantity conflicting with vrf_id even when no quantity is set and vrf_id is supplied through patching.

XRD ("MetalNetwork" creates a VRF + VLAN + Gateway (associated with the VLAN, with IP reservations from the VRF)

```yaml apiVersion: apiextensions.crossplane.io/v1 kind: CompositeResourceDefinition metadata: name: metalnetworks.equinix.xrds.crossplane.io spec: group: equinix.xrds.crossplane.io names: kind: MetalNetwork listKind: MetalNetworkList plural: metalnetworks singular: metalnetwork versions: - name: v1alpha1 served: true referenceable: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: vrfName: type: string description: "Name of the VRF" projectID: type: string description: "Equinix Metal Project ID" description: type: string description: "Description of the VRF" metro: type: string description: "Metro area for the VRF" ipPrefixes: type: array items: type: string description: "IP prefixes for the VRF" default: ["192.168.100.0/24"] vxlan: type: integer description: "VXLAN ID for the VRF" default: 1000 required: - vrfName - projectID - metro status: type: object properties: vlanID: type: string vrfID: type: string gatewayID: type: string ipReservationID: type: string conditions: type: array items: type: object properties: type: type: string status: type: string reason: type: string message: type: string claimNames: kind: MetalNetworkClaim plural: metalnetworkclaims --- apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: name: metalnetworkcomposition spec: compositeTypeRef: apiVersion: equinix.xrds.crossplane.io/v1alpha1 kind: MetalNetwork resources: - name: metalvrf base: apiVersion: metal.equinix.jet.crossplane.io/v1alpha1 kind: Vrf spec: providerConfigRef: name: default patches: - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" transforms: - type: string string: type: Format fmt: 'Crossplane managed VRF: %s' - fromFieldPath: "spec.vrfName" toFieldPath: "spec.forProvider.name" - fromFieldPath: "spec.vrfName" toFieldPath: "metadata.name" - fromFieldPath: "spec.projectID" toFieldPath: "spec.forProvider.projectId" - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" - fromFieldPath: "spec.metro" toFieldPath: "spec.forProvider.metro" - fromFieldPath: "spec.ipPrefixes" toFieldPath: "spec.forProvider.ipRanges" - type: ToCompositeFieldPath fromFieldPath: "status.atProvider.id" toFieldPath: "status.vrfID" - name: metalvlan base: apiVersion: metal.equinix.jet.crossplane.io/v1alpha1 kind: Vlan spec: providerConfigRef: name: default patches: - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" transforms: - type: string string: type: Format fmt: 'Crossplane managed VLAN: %s' - fromFieldPath: "spec.vrfName" toFieldPath: "metadata.name" - fromFieldPath: "spec.projectID" toFieldPath: "spec.forProvider.projectId" - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" - fromFieldPath: "spec.metro" toFieldPath: "spec.forProvider.metro" - fromFieldPath: "spec.vxlan" toFieldPath: "spec.forProvider.vxlan" - type: ToCompositeFieldPath fromFieldPath: "status.atProvider.id" toFieldPath: "status.vlanID" - name: metalgateway base: apiVersion: metal.equinix.jet.crossplane.io/v1alpha1 kind: Gateway spec: providerConfigRef: name: default patches: - fromFieldPath: "spec.vrfName" toFieldPath: "metadata.name" - fromFieldPath: "spec.projectID" toFieldPath: "spec.forProvider.projectId" - fromFieldPath: "status.ipReservationID" toFieldPath: "spec.forProvider.ipReservationId" - fromFieldPath: "status.vlanID" toFieldPath: "spec.forProvider.vlanId" - type: ToCompositeFieldPath fromFieldPath: "status.atProvider.id" toFieldPath: "status.gatewayID" - name: metalgatewayipreservation base: apiVersion: metal.equinix.jet.crossplane.io/v1alpha1 kind: ReservedIPBlock spec: forProvider: type: vrf providerConfigRef: name: default patches: - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" transforms: - type: string string: type: Format fmt: 'Crossplane managed IP Reservation: %s' - fromFieldPath: "spec.vrfName" toFieldPath: "metadata.name" - fromFieldPath: "spec.projectID" toFieldPath: "spec.forProvider.projectId" - fromFieldPath: "spec.description" toFieldPath: "spec.forProvider.description" - fromFieldPath: "spec.metro" toFieldPath: "spec.forProvider.metro" - fromFieldPath: "spec.ipPrefixes[0]" toFieldPath: "spec.forProvider.network" transforms: - type: string string: type: Regexp regexp: match: '(.*)/' group: 1 - fromFieldPath: "spec.ipPrefixes[0]" toFieldPath: "spec.forProvider.cidr" transforms: - type: string string: type: Regexp regexp: match: '/(.*)' group: 1 - type: convert convert: toType: int - type: ToCompositeFieldPath fromFieldPath: "status.atProvider.id" toFieldPath: "status.ipReservationID" - fromFieldPath: "status.vrfID" toFieldPath: "spec.forProvider.vrfId" ```

Claim:

```yaml apiVersion: equinix.xrds.crossplane.io/v1alpha1 kind: MetalNetworkClaim metadata: name: metal-network-instance spec: vrfName: "vrf-via-crossplane-xrd" projectID: "my-project-id" # replace this with yours description: "vrf created using crossplane composition" metro: "da" ipPrefixes: - 192.168.100.0/24 vxlan: 1000 ```
$ kubectl get equinix
NAME                                                             READY   SYNCED   EXTERNAL-NAME   AGE
gateway.metal.equinix.jet.crossplane.io/vrf-via-crossplane-xrd   False   True                     32m

NAME                                                                     READY   SYNCED   EXTERNAL-NAME                          AGE
reservedipblock.metal.equinix.jet.crossplane.io/vrf-via-crossplane-xrd   False   False    740d3177-de36-4538-842e-7ed960c66c54   32m

NAME                                                          READY   SYNCED   EXTERNAL-NAME                          AGE
vlan.metal.equinix.jet.crossplane.io/vrf-via-crossplane-xrd   True    True     e0a787ac-fa37-47cf-ac0e-02657fad2cf9   32m

NAME                                                         READY   SYNCED   EXTERNAL-NAME                          AGE
vrf.metal.equinix.jet.crossplane.io/vrf-via-crossplane-xrd   True    True     40025fe3-2138-4e25-a963-4cca23e136bc   22m
$ kubectl describe metalnetworkclaims.equinix.xrds.crossplane.io metal-network-instance 
Name:         metal-network-instance
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  equinix.xrds.crossplane.io/v1alpha1
Kind:         MetalNetworkClaim
Metadata:
  Creation Timestamp:  2024-07-11T14:19:53Z
  Finalizers:
    finalizer.apiextensions.crossplane.io
  Generation:        8
  Resource Version:  16031
  UID:               c441b433-525e-4fcb-a508-c2835dc79677
Spec:
  Composite Delete Policy:  Background
  Composition Ref:
    Name:  metalnetworkcomposition
  Composition Revision Ref:
    Name:                     metalnetworkcomposition-e870161
  Composition Update Policy:  Automatic
  Description:                vrf created using crossplane composition
  Ip Prefixes:
    192.168.100.0/24
  Metro:       da
  Project ID:  24822e74-bf8c-4637-bcb9-71ef8fd88eed
  Resource Ref:
    API Version:  equinix.xrds.crossplane.io/v1alpha1
    Kind:         MetalNetwork
    Name:         metal-network-instance-p24bt
  Vrf Name:       vrf-via-crossplane-xrd
  Vxlan:          1000
Status:
  Conditions:
    Last Transition Time:  2024-07-11T14:19:53Z
    Reason:                ReconcileSuccess
    Status:                True
    Type:                  Synced
    Last Transition Time:  2024-07-11T14:19:53Z
    Message:               Claim is waiting for composite resource to become Ready
    Reason:                Waiting
    Status:                False
    Type:                  Ready
  Vlan ID:                 e0a787ac-fa37-47cf-ac0e-02657fad2cf9
  Vrf ID:                  40025fe3-2138-4e25-a963-4cca23e136bc
Events:
  Type    Reason                 Age                   From                                                             Message
  ----    ------                 ----                  ----                                                             -------
  Normal  BindCompositeResource  33m                   offered/compositeresourcedefinition.apiextensions.crossplane.io  Successfully bound composite resource
  Normal  BindCompositeResource  8m16s (x17 over 33m)  offered/compositeresourcedefinition.apiextensions.crossplane.io  Composite resource is not yet ready
$ kubectl describe reservedipblocks.metal.equinix.jet.crossplane.io 
...
Spec:
  Deletion Policy:  Delete
  For Provider:
    Cidr:            24
    Custom Data:     {}
    Description:     vrf created using crossplane composition
    Metro:           da
    Network:         192.168.100.0
    Project Id:      *redacted*
    Quantity:        256
    Type:            vrf
    Vrf Id:          40025fe3-2138-4e25-a963-4cca23e136bc
    Wait For State:  created
  Provider Config Ref:
    Name:  default
Status:
  At Provider:
  Conditions:
    Last Transition Time:  2024-07-11T14:54:36Z
    Message:               observe failed: cannot run refresh: refresh failed: Invalid combination of arguments: "vrf_id": only one of `quantity,vrf_id` can be specified, but `quantity,vrf_id` were specified.
Invalid combination of arguments: "quantity": only one of `quantity,vrf_id` can be specified, but `quantity,vrf_id` were specified.

Quantity above is shown as 256 but that is not defined anywhere in the XRD or claim. I tried setting quantity: null in the XRD, but that didn't help either.

How can we reproduce it?

What environment did it happen in?

Crossplane version: v1.16.0 Provider version: v0.6.1

displague commented 4 months ago

The IP reservation is being successfully created. The problem seems to be at refresh, related to the quantity being set to 256 as a computed field despite the field conflicting with the other fields (cidr, vrf_id).

displague commented 4 months ago

The Crossplane provider's LateInitializer of quantity field (optional and computed in Terraform) is problematic in this case.

We'll want to change the default initializer, as discussed here https://github.com/crossplane/upjet/blob/e295e17c7be7af3fe711ed53bae201f04f1fa797/docs/configuring-a-resource.md?plain=1#L732, and as previously implemented for the device managed resources due to similar behavior between the Metro and Facility fields which were mutually exclusive but required and (in some revisions) computed when not specified: https://github.com/crossplane-contrib/provider-jet-equinix/blob/main/config/metal/device/config.go#L24-L35