Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions operator/.downstream_manifests
Original file line number Diff line number Diff line change
Expand Up @@ -5755,11 +5755,35 @@ spec:
destination:
description: Destination network.
properties:
calico:
description: |-
Calico marks the destination's default pod network as Calico-provided
and opts the VM's primary NIC into Calico identity preservation: the
source MAC is carried over, and the source IPs too when the Plan sets
preserveStaticIPs. Applies only when Type == "pod".
properties:
network:
description: |-
Name of a cluster-scoped projectcalico.org/v3 Network CR for L2
primary attach. Empty value means no L2 attach: Calico's default L3
IPAM is used. Applies only when the entry's Type == "pod".
type: string
vlan:
description: |-
802.1Q VLAN ID within the named Calico Network. Applies only when
Network != "" and is then required: it must match a VLAN entry's
vlan.id in the named Network. Value 0 (or omitted) means "not set"
and is rejected at Plan validation whenever a Network is named.
maximum: 4094
minimum: 0
type: integer
type: object
name:
description: The name.
description: The name. Applies only when Type == "multus"
(the NetworkAttachmentDefinition name).
type: string
namespace:
description: The namespace (multus only).
description: The namespace. Applies only when Type == "multus".
type: string
type:
description: |-
Expand Down Expand Up @@ -11222,6 +11246,17 @@ rules:
- get
- list
- watch
- apiGroups:
- projectcalico.org
resources:
- networks
- ippools
- felixconfigurations
- bgppeers
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand Down
39 changes: 37 additions & 2 deletions operator/.upstream_manifests
Original file line number Diff line number Diff line change
Expand Up @@ -5755,11 +5755,35 @@ spec:
destination:
description: Destination network.
properties:
calico:
description: |-
Calico marks the destination's default pod network as Calico-provided
and opts the VM's primary NIC into Calico identity preservation: the
source MAC is carried over, and the source IPs too when the Plan sets
preserveStaticIPs. Applies only when Type == "pod".
properties:
network:
description: |-
Name of a cluster-scoped projectcalico.org/v3 Network CR for L2
primary attach. Empty value means no L2 attach: Calico's default L3
IPAM is used. Applies only when the entry's Type == "pod".
type: string
vlan:
description: |-
802.1Q VLAN ID within the named Calico Network. Applies only when
Network != "" and is then required: it must match a VLAN entry's
vlan.id in the named Network. Value 0 (or omitted) means "not set"
and is rejected at Plan validation whenever a Network is named.
maximum: 4094
minimum: 0
type: integer
type: object
name:
description: The name.
description: The name. Applies only when Type == "multus"
(the NetworkAttachmentDefinition name).
type: string
namespace:
description: The namespace (multus only).
description: The namespace. Applies only when Type == "multus".
type: string
type:
description: |-
Expand Down Expand Up @@ -11222,6 +11246,17 @@ rules:
- get
- list
- watch
- apiGroups:
- projectcalico.org
resources:
- networks
- ippools
- felixconfigurations
- bgppeers
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand Down
28 changes: 26 additions & 2 deletions operator/config/crd/bases/forklift.konveyor.io_networkmaps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,35 @@ spec:
destination:
description: Destination network.
properties:
calico:
description: |-
Calico marks the destination's default pod network as Calico-provided
and opts the VM's primary NIC into Calico identity preservation: the
source MAC is carried over, and the source IPs too when the Plan sets
preserveStaticIPs. Applies only when Type == "pod".
properties:
network:
description: |-
Name of a cluster-scoped projectcalico.org/v3 Network CR for L2
primary attach. Empty value means no L2 attach: Calico's default L3
IPAM is used. Applies only when the entry's Type == "pod".
type: string
vlan:
description: |-
802.1Q VLAN ID within the named Calico Network. Applies only when
Network != "" and is then required: it must match a VLAN entry's
vlan.id in the named Network. Value 0 (or omitted) means "not set"
and is rejected at Plan validation whenever a Network is named.
maximum: 4094
minimum: 0
type: integer
type: object
name:
description: The name.
description: The name. Applies only when Type == "multus"
(the NetworkAttachmentDefinition name).
type: string
namespace:
description: The namespace (multus only).
description: The namespace. Applies only when Type == "multus".
type: string
type:
description: |-
Expand Down
17 changes: 16 additions & 1 deletion operator/config/rbac/forklift-controller_role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,19 @@ rules:
- create
- get
- list
- watch
- watch
# The Plan validator reads Calico Network and IPPool resources on the
# destination cluster to validate L2-bridge NAD destinations and per-VM
# static IPs, and BGPPeer resources to check that a VRF network's routes
# are distributed across nodes.
- apiGroups:
- projectcalico.org
resources:
- networks
- ippools
- felixconfigurations
- bgppeers
verbs:
- get
- list
- watch
26 changes: 24 additions & 2 deletions pkg/apis/forklift/v1beta1/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,32 @@ type DestinationNetwork struct {
// - ignored: Network is excluded from mapping
// +kubebuilder:validation:Enum=pod;multus;ignored
Type string `json:"type"`
// The namespace (multus only).
// The namespace. Applies only when Type == "multus".
Namespace string `json:"namespace,omitempty"`
// The name.
// The name. Applies only when Type == "multus" (the NetworkAttachmentDefinition name).
Name string `json:"name,omitempty"`
// Calico marks the destination's default pod network as Calico-provided
// and opts the VM's primary NIC into Calico identity preservation: the
// source MAC is carried over, and the source IPs too when the Plan sets
// preserveStaticIPs. Applies only when Type == "pod".
Calico *CalicoDestination `json:"calico,omitempty"`
}

// CalicoDestination qualifies a type: pod destination whose default pod
// network is provided by Calico. Its presence (even empty) is the opt-in
// for primary-NIC identity preservation.
type CalicoDestination struct {
// Name of a cluster-scoped projectcalico.org/v3 Network CR for L2
// primary attach. Empty value means no L2 attach: Calico's default L3
// IPAM is used. Applies only when the entry's Type == "pod".
Network string `json:"network,omitempty"`
// 802.1Q VLAN ID within the named Calico Network. Applies only when
// Network != "" and is then required: it must match a VLAN entry's
// vlan.id in the named Network. Value 0 (or omitted) means "not set"
// and is rejected at Plan validation whenever a Network is named.
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=4094
Vlan uint16 `json:"vlan,omitempty"`
}

// NetworkSourceRef extends Ref with an optional VLAN qualifier for network disambiguation.
Expand Down
106 changes: 106 additions & 0 deletions pkg/apis/forklift/v1beta1/mapping_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package v1beta1

import (
"encoding/json"
"reflect"
"strings"
"testing"
)

func TestDestinationNetwork_RoundTrip_NoCalico(t *testing.T) {
// Entries without the calico field should round-trip without a "calico"
// key appearing in the JSON.
for _, typ := range []string{"pod", "multus", "ignored"} {
in := DestinationNetwork{Type: typ, Namespace: "ns", Name: "n"}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal %q: %v", typ, err)
}
if strings.Contains(string(raw), "calico") {
t.Errorf("type=%q JSON includes calico key: %s", typ, raw)
}
var out DestinationNetwork
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("unmarshal %q: %v", typ, err)
}
if !reflect.DeepEqual(out, in) {
t.Errorf("round-trip mismatch for %q: got %+v, want %+v", typ, out, in)
}
}
}

func TestDestinationNetwork_RoundTrip_CalicoEmpty(t *testing.T) {
// The empty calico block is the minimal opt-in — its presence must
// survive a round trip (nil vs empty-struct distinction is load-bearing).
in := DestinationNetwork{Type: "pod", Calico: &CalicoDestination{}}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(raw), `"calico":{}`) {
t.Errorf("JSON missing empty calico block: %s", raw)
}
var out DestinationNetwork
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if out.Calico == nil {
t.Fatalf("calico block lost in round trip: %s", raw)
}
if !reflect.DeepEqual(out, in) {
t.Errorf("round-trip mismatch: got %+v, want %+v", out, in)
}
}

func TestDestinationNetwork_RoundTrip_CalicoWithNetwork(t *testing.T) {
in := DestinationNetwork{Type: "pod", Calico: &CalicoDestination{Network: "vlan100"}}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(raw), `"calico":{"network":"vlan100"}`) {
t.Errorf("JSON missing calico.network: %s", raw)
}
var out DestinationNetwork
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if !reflect.DeepEqual(out, in) {
t.Errorf("round-trip mismatch: got %+v, want %+v", out, in)
}
}

func TestDestinationNetwork_RoundTrip_CalicoWithNetworkAndVlan(t *testing.T) {
in := DestinationNetwork{Type: "pod", Calico: &CalicoDestination{Network: "vlan100", Vlan: 100}}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(raw), `"network":"vlan100"`) {
t.Errorf("JSON missing calico.network: %s", raw)
}
if !strings.Contains(string(raw), `"vlan":100`) {
t.Errorf("JSON missing calico.vlan: %s", raw)
}
var out DestinationNetwork
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if !reflect.DeepEqual(out, in) {
t.Errorf("round-trip mismatch: got %+v, want %+v", out, in)
}
}

func TestCalicoDestination_OmitsZeroVlan(t *testing.T) {
// Zero vlan means "not set" (a named Network requires an explicit VLAN,
// enforced at Plan validation) and must serialize as omitted so a stored
// object round-trips without growing a spurious vlan: 0 field.
in := DestinationNetwork{Type: "pod", Calico: &CalicoDestination{Network: "prod", Vlan: 0}}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if strings.Contains(string(raw), `"vlan":`) {
t.Errorf("Vlan=0 should be omitted, got: %s", raw)
}
}
26 changes: 24 additions & 2 deletions pkg/apis/forklift/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading