From 83b7e21e18b4449b79b77863f7f225d7fbcc673d Mon Sep 17 00:00:00 2001 From: Sergei Lukianov Date: Wed, 6 Dec 2023 21:07:01 -0800 Subject: [PATCH] Add custom DHCPSubnet CRD and controller to gen them per vpcsubnet --- PROJECT | 8 + api/dhcp/v1alpha2/dhcpsubnet_types.go | 78 ++++++++++ api/dhcp/v1alpha2/groupversion_info.go | 36 +++++ api/dhcp/v1alpha2/zz_generated.deepcopy.go | 138 ++++++++++++++++++ cmd/main.go | 2 + .../dhcp.githedgehog.com_dhcpsubnets.yaml | 116 +++++++++++++++ config/crd/kustomization.yaml | 5 +- config/rbac/dhcp_dhcpsubnet_editor_role.yaml | 31 ++++ config/rbac/dhcp_dhcpsubnet_viewer_role.yaml | 27 ++++ config/rbac/role.yaml | 26 ++++ docs/api.md | 78 ++++++++++ pkg/agent/dozer/bcm/spec_prefix_lists.go | 1 + pkg/agent/dozer/dozer.go | 17 +++ pkg/ctrl/vpc/dhcpd.go | 88 ++++++++++- pkg/ctrl/vpc/vpc_ctrl.go | 16 +- 15 files changed, 664 insertions(+), 3 deletions(-) create mode 100644 api/dhcp/v1alpha2/dhcpsubnet_types.go create mode 100644 api/dhcp/v1alpha2/groupversion_info.go create mode 100644 api/dhcp/v1alpha2/zz_generated.deepcopy.go create mode 100644 config/crd/bases/dhcp.githedgehog.com_dhcpsubnets.yaml create mode 100644 config/rbac/dhcp_dhcpsubnet_editor_role.yaml create mode 100644 config/rbac/dhcp_dhcpsubnet_viewer_role.yaml create mode 100644 pkg/agent/dozer/bcm/spec_prefix_lists.go diff --git a/PROJECT b/PROJECT index 896be75e..fd257cdb 100644 --- a/PROJECT +++ b/PROJECT @@ -199,4 +199,12 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: githedgehog.com + group: dhcp + kind: DHCPSubnet + path: go.githedgehog.com/fabric/api/dhcp/v1alpha2 + version: v1alpha2 version: "3" diff --git a/api/dhcp/v1alpha2/dhcpsubnet_types.go b/api/dhcp/v1alpha2/dhcpsubnet_types.go new file mode 100644 index 00000000..0e9d7a1d --- /dev/null +++ b/api/dhcp/v1alpha2/dhcpsubnet_types.go @@ -0,0 +1,78 @@ +/* +Copyright 2023 Hedgehog. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// DHCPSubnetSpec defines the desired state of DHCPSubnet +type DHCPSubnetSpec struct { + Subnet string `json:"subnet"` // e.g. vpc-0/default (vpc name + vpc subnet name) + CIDRBlock string `json:"cidrBlock"` // e.g. 10.10.10.0/24 + Gateway string `json:"gateway"` // e.g. 10.10.10.1 + StartIP string `json:"startIP"` // e.g. 10.10.10.10 + EndIP string `json:"endIP"` // e.g. 10.10.10.99 + VRF string `json:"vrf"` // e.g. VrfVvpc-1 as it's named on switch + CircuitID string `json:"circuitID"` // e.g. Vlan1000 as it's named on switch +} + +// DHCPSubnetStatus defines the observed state of DHCPSubnet +type DHCPSubnetStatus struct { + AllocatedIPs map[string]DHCPAllocatedIP `json:"allocatedIPs,omitempty"` +} + +type DHCPAllocatedIP struct { + Expiry metav1.Time `json:"expiry"` + MAC string `json:"mac"` + Hostname string `json:"hostname"` // from dhcp request +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:categories=hedgehog;fabric,shortName=dhcp +// +kubebuilder:printcolumn:name="Subnet",type=string,JSONPath=`.spec.subnet`,priority=0 +// +kubebuilder:printcolumn:name="CIDRBlock",type=string,JSONPath=`.spec.cidrBlock`,priority=0 +// +kubebuilder:printcolumn:name="Gateway",type=string,JSONPath=`.spec.gateway`,priority=0 +// +kubebuilder:printcolumn:name="StartIP",type=string,JSONPath=`.spec.startIP`,priority=0 +// +kubebuilder:printcolumn:name="EndIP",type=string,JSONPath=`.spec.endIP`,priority=0 +// +kubebuilder:printcolumn:name="VRF",type=string,JSONPath=`.spec.vrf`,priority=1 +// +kubebuilder:printcolumn:name="CircuitID",type=string,JSONPath=`.spec.circuitID`,priority=1 +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,priority=0 +// DHCPSubnet is the Schema for the dhcpsubnets API +type DHCPSubnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DHCPSubnetSpec `json:"spec,omitempty"` + Status DHCPSubnetStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// DHCPSubnetList contains a list of DHCPSubnet +type DHCPSubnetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DHCPSubnet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DHCPSubnet{}, &DHCPSubnetList{}) +} diff --git a/api/dhcp/v1alpha2/groupversion_info.go b/api/dhcp/v1alpha2/groupversion_info.go new file mode 100644 index 00000000..7bae3ee8 --- /dev/null +++ b/api/dhcp/v1alpha2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 Hedgehog. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha2 contains API Schema definitions for the dhcp v1alpha2 API group +// +kubebuilder:object:generate=true +// +groupName=dhcp.githedgehog.com +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "dhcp.githedgehog.com", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/dhcp/v1alpha2/zz_generated.deepcopy.go b/api/dhcp/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 00000000..f6b717ee --- /dev/null +++ b/api/dhcp/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,138 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023 Hedgehog. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPAllocatedIP) DeepCopyInto(out *DHCPAllocatedIP) { + *out = *in + in.Expiry.DeepCopyInto(&out.Expiry) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPAllocatedIP. +func (in *DHCPAllocatedIP) DeepCopy() *DHCPAllocatedIP { + if in == nil { + return nil + } + out := new(DHCPAllocatedIP) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPSubnet) DeepCopyInto(out *DHCPSubnet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPSubnet. +func (in *DHCPSubnet) DeepCopy() *DHCPSubnet { + if in == nil { + return nil + } + out := new(DHCPSubnet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DHCPSubnet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPSubnetList) DeepCopyInto(out *DHCPSubnetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DHCPSubnet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPSubnetList. +func (in *DHCPSubnetList) DeepCopy() *DHCPSubnetList { + if in == nil { + return nil + } + out := new(DHCPSubnetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DHCPSubnetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPSubnetSpec) DeepCopyInto(out *DHCPSubnetSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPSubnetSpec. +func (in *DHCPSubnetSpec) DeepCopy() *DHCPSubnetSpec { + if in == nil { + return nil + } + out := new(DHCPSubnetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPSubnetStatus) DeepCopyInto(out *DHCPSubnetStatus) { + *out = *in + if in.AllocatedIPs != nil { + in, out := &in.AllocatedIPs, &out.AllocatedIPs + *out = make(map[string]DHCPAllocatedIP, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPSubnetStatus. +func (in *DHCPSubnetStatus) DeepCopy() *DHCPSubnetStatus { + if in == nil { + return nil + } + out := new(DHCPSubnetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index 4bf14657..e9e05a54 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,6 +37,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" agentv1alpha2 "go.githedgehog.com/fabric/api/agent/v1alpha2" + dhcpv1alpha2 "go.githedgehog.com/fabric/api/dhcp/v1alpha2" vpcv1alpha2 "go.githedgehog.com/fabric/api/vpc/v1alpha2" wiringv1alpha2 "go.githedgehog.com/fabric/api/wiring/v1alpha2" agentcontroller "go.githedgehog.com/fabric/pkg/ctrl/agent" @@ -68,6 +69,7 @@ func init() { utilruntime.Must(agentv1alpha2.AddToScheme(scheme)) utilruntime.Must(wiringv1alpha2.AddToScheme(scheme)) utilruntime.Must(vpcv1alpha2.AddToScheme(scheme)) + utilruntime.Must(dhcpv1alpha2.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/config/crd/bases/dhcp.githedgehog.com_dhcpsubnets.yaml b/config/crd/bases/dhcp.githedgehog.com_dhcpsubnets.yaml new file mode 100644 index 00000000..664017ca --- /dev/null +++ b/config/crd/bases/dhcp.githedgehog.com_dhcpsubnets.yaml @@ -0,0 +1,116 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: dhcpsubnets.dhcp.githedgehog.com +spec: + group: dhcp.githedgehog.com + names: + categories: + - hedgehog + - fabric + kind: DHCPSubnet + listKind: DHCPSubnetList + plural: dhcpsubnets + shortNames: + - dhcp + singular: dhcpsubnet + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.subnet + name: Subnet + type: string + - jsonPath: .spec.cidrBlock + name: CIDRBlock + type: string + - jsonPath: .spec.gateway + name: Gateway + type: string + - jsonPath: .spec.startIP + name: StartIP + type: string + - jsonPath: .spec.endIP + name: EndIP + type: string + - jsonPath: .spec.vrf + name: VRF + priority: 1 + type: string + - jsonPath: .spec.circuitID + name: CircuitID + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: DHCPSubnet is the Schema for the dhcpsubnets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: DHCPSubnetSpec defines the desired state of DHCPSubnet + properties: + cidrBlock: + type: string + circuitID: + type: string + endIP: + type: string + gateway: + type: string + startIP: + type: string + subnet: + type: string + vrf: + type: string + required: + - cidrBlock + - circuitID + - endIP + - gateway + - startIP + - subnet + - vrf + type: object + status: + description: DHCPSubnetStatus defines the observed state of DHCPSubnet + properties: + allocatedIPs: + additionalProperties: + properties: + expiry: + format: date-time + type: string + hostname: + type: string + mac: + type: string + required: + - expiry + - hostname + - mac + type: object + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index cc2942cb..e8a4c54b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -19,6 +19,7 @@ resources: - bases/vpc.githedgehog.com_externals.yaml - bases/vpc.githedgehog.com_externalattachments.yaml - bases/vpc.githedgehog.com_externalpeerings.yaml + - bases/dhcp.githedgehog.com_dhcpsubnets.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: @@ -40,6 +41,7 @@ patches: - path: patches/webhook_in_vpc_externals.yaml - path: patches/webhook_in_vpc_externalattachments.yaml - path: patches/webhook_in_vpc_externalpeerings.yaml + #- path: patches/webhook_in_dhcp_dhcpsubnets.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -60,7 +62,8 @@ patches: - path: patches/cainjection_in_vpc_externals.yaml - path: patches/cainjection_in_vpc_externalattachments.yaml - path: patches/cainjection_in_vpc_externalpeerings.yaml - #+kubebuilder:scaffold:crdkustomizecainjectionpatch +#- path: patches/cainjection_in_dhcp_dhcpsubnets.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: diff --git a/config/rbac/dhcp_dhcpsubnet_editor_role.yaml b/config/rbac/dhcp_dhcpsubnet_editor_role.yaml new file mode 100644 index 00000000..a368a768 --- /dev/null +++ b/config/rbac/dhcp_dhcpsubnet_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit dhcpsubnets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: dhcpsubnet-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: fabric + app.kubernetes.io/part-of: fabric + app.kubernetes.io/managed-by: kustomize + name: dhcpsubnet-editor-role +rules: +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets/status + verbs: + - get diff --git a/config/rbac/dhcp_dhcpsubnet_viewer_role.yaml b/config/rbac/dhcp_dhcpsubnet_viewer_role.yaml new file mode 100644 index 00000000..55952ef6 --- /dev/null +++ b/config/rbac/dhcp_dhcpsubnet_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view dhcpsubnets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: dhcpsubnet-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: fabric + app.kubernetes.io/part-of: fabric + app.kubernetes.io/managed-by: kustomize + name: dhcpsubnet-viewer-role +rules: +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets + verbs: + - get + - list + - watch +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index feabac6c..358060a0 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -100,6 +100,32 @@ rules: - patch - update - watch +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets/finalizers + verbs: + - update +- apiGroups: + - dhcp.githedgehog.com + resources: + - dhcpsubnets/status + verbs: + - get + - patch + - update - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/docs/api.md b/docs/api.md index d932af5e..15187e6b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2,6 +2,7 @@ ## Packages - [agent.githedgehog.com/v1alpha2](#agentgithedgehogcomv1alpha2) +- [dhcp.githedgehog.com/v1alpha2](#dhcpgithedgehogcomv1alpha2) - [vpc.githedgehog.com/v1alpha2](#vpcgithedgehogcomv1alpha2) - [wiring.githedgehog.com/v1alpha2](#wiringgithedgehogcomv1alpha2) @@ -268,6 +269,83 @@ _Appears in:_ +## dhcp.githedgehog.com/v1alpha2 + +Package v1alpha2 contains API Schema definitions for the dhcp v1alpha2 API group + +### Resource Types +- [DHCPSubnet](#dhcpsubnet) + + + +#### DHCPAllocatedIP + + + + + +_Appears in:_ +- [DHCPSubnetStatus](#dhcpsubnetstatus) + +| Field | Description | +| --- | --- | +| `expiry` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#time-v1-meta)_ | | +| `mac` _string_ | | +| `hostname` _string_ | | + + +#### DHCPSubnet + + + +DHCPSubnet is the Schema for the dhcpsubnets API + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `dhcp.githedgehog.com/v1alpha2` +| `kind` _string_ | `DHCPSubnet` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[DHCPSubnetSpec](#dhcpsubnetspec)_ | | +| `status` _[DHCPSubnetStatus](#dhcpsubnetstatus)_ | | + + +#### DHCPSubnetSpec + + + +DHCPSubnetSpec defines the desired state of DHCPSubnet + +_Appears in:_ +- [DHCPSubnet](#dhcpsubnet) + +| Field | Description | +| --- | --- | +| `subnet` _string_ | | +| `cidrBlock` _string_ | | +| `gateway` _string_ | | +| `startIP` _string_ | | +| `endIP` _string_ | | +| `vrf` _string_ | | +| `circuitID` _string_ | | + + +#### DHCPSubnetStatus + + + +DHCPSubnetStatus defines the observed state of DHCPSubnet + +_Appears in:_ +- [DHCPSubnet](#dhcpsubnet) + +| Field | Description | +| --- | --- | +| `allocatedIPs` _object (keys:string, values:[DHCPAllocatedIP](#dhcpallocatedip))_ | | + + + ## vpc.githedgehog.com/v1alpha2 Package v1alpha2 contains API Schema definitions for the vpc v1alpha2 API group diff --git a/pkg/agent/dozer/bcm/spec_prefix_lists.go b/pkg/agent/dozer/bcm/spec_prefix_lists.go new file mode 100644 index 00000000..e543c505 --- /dev/null +++ b/pkg/agent/dozer/bcm/spec_prefix_lists.go @@ -0,0 +1 @@ +package bcm diff --git a/pkg/agent/dozer/dozer.go b/pkg/agent/dozer/dozer.go index 690e677d..97cdf40f 100644 --- a/pkg/agent/dozer/dozer.go +++ b/pkg/agent/dozer/dozer.go @@ -41,6 +41,7 @@ type Spec struct { MCLAGInterfaces map[string]*SpecMCLAGInterface `json:"mclagInterfaces,omitempty"` VRFs map[string]*SpecVRF `json:"vrfs,omitempty"` RouteMaps map[string]*SpecRouteMap `json:"routingMaps,omitempty"` + PrefixLists map[string]*SpecPrefixList `json:"prefixLists,omitempty"` DHCPRelays map[string]*SpecDHCPRelay `json:"dhcpRelays,omitempty"` NATs map[uint32]*SpecNAT `json:"nats,omitempty"` ACLs map[string]*SpecACL `json:"acls,omitempty"` @@ -199,6 +200,22 @@ const ( SpecRouteMapResultReject SpecRouteMapResult = "reject" ) +type SpecPrefixList struct { + Prefixes map[uint32]*SpecPrefixListPrefix `json:"prefixes,omitempty"` +} + +type SpecPrefixListPrefix struct { + Prefix string `json:"prefix,omitempty"` + Action SpecPrefixListAction `json:"action,omitempty"` +} + +type SpecPrefixListAction string + +const ( + SpecPrefixListActionPermit SpecPrefixListAction = "permit" + SpecPrefixListActionDeny SpecPrefixListAction = "deny" +) + const ( SpecVRFBGPTableConnectionConnected = "connected" SpecVRFBGPTableConnectionStatic = "static" diff --git a/pkg/ctrl/vpc/dhcpd.go b/pkg/ctrl/vpc/dhcpd.go index 7c0d9d97..3a304b84 100644 --- a/pkg/ctrl/vpc/dhcpd.go +++ b/pkg/ctrl/vpc/dhcpd.go @@ -3,10 +3,13 @@ package vpc import ( "bytes" "context" + "fmt" "html/template" "net" + "strings" "github.com/pkg/errors" + dhcpapi "go.githedgehog.com/fabric/api/dhcp/v1alpha2" vpcapi "go.githedgehog.com/fabric/api/vpc/v1alpha2" wiringapi "go.githedgehog.com/fabric/api/wiring/v1alpha2" "go.githedgehog.com/fabric/pkg/util/iputil" @@ -59,7 +62,7 @@ type dhcpdSubnet struct { Router string } -func (r *VPCReconciler) updateDHCPConfig(ctx context.Context) error { +func (r *VPCReconciler) updateISCDHCPConfig(ctx context.Context) error { tmpl, err := template.New("dhcp-server-config").Parse(DHCP_SERVER_CONFIF_TMPL) if err != nil { return errors.Wrapf(err, "error parsing dhcp server config template") @@ -170,3 +173,86 @@ func (r *VPCReconciler) updateDHCPConfig(ctx context.Context) error { return nil } + +func (r *VPCReconciler) updateDHCPSubnets(ctx context.Context, vpc *vpcapi.VPC) error { + err := r.deleteDHCPSubnets(ctx, client.ObjectKey{Name: vpc.Name, Namespace: vpc.Namespace}, vpc.Spec.Subnets) + if err != nil { + return errors.Wrapf(err, "error deleting obsolete dhcp subnets") + } + + for subnetName, subnet := range vpc.Spec.Subnets { + if !subnet.DHCP.Enable { + continue + } + + cidr, err := iputil.ParseCIDR(subnet.Subnet) + if err != nil { + return errors.Wrapf(err, "error parsing vpc %s/%s subnet %s", vpc.Name, subnetName, subnet.Subnet) + } + + start := cidr.DHCPRangeStart.String() + end := cidr.DHCPRangeEnd.String() + + if subnet.DHCP.Range != nil { + if subnet.DHCP.Range.Start != "" { + start = subnet.DHCP.Range.Start + } + if subnet.DHCP.Range.End != "" { + end = subnet.DHCP.Range.End + } + } + + gateway := cidr.Gateway.String() + + dhcp := &dhcpapi.DHCPSubnet{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s--%s", vpc.Name, subnetName), Namespace: vpc.Namespace}} + _, err = ctrlutil.CreateOrUpdate(ctx, r.Client, dhcp, func() error { + dhcp.Labels = map[string]string{ + vpcapi.LabelVPC: vpc.Name, + vpcapi.LabelSubnet: subnetName, + } + dhcp.Spec = dhcpapi.DHCPSubnetSpec{ + Subnet: fmt.Sprintf("%s/%s", vpc.Name, subnetName), + CIDRBlock: subnet.Subnet, + Gateway: gateway, + StartIP: start, + EndIP: end, + VRF: fmt.Sprintf("VrfV%s", vpc.Name), // TODO move to utils + CircuitID: fmt.Sprintf("Vlan%s", subnet.VLAN), // TODO move to utils + } + + return nil + }) + if err != nil { + return errors.Wrapf(err, "error creating dhcp subnet for %s/%s", vpc.Name, subnetName) + } + } + + return nil +} + +func (r *VPCReconciler) deleteDHCPSubnets(ctx context.Context, vpcKey client.ObjectKey, subnets map[string]*vpcapi.VPCSubnet) error { + dhcpSubnets := &dhcpapi.DHCPSubnetList{} + err := r.List(ctx, dhcpSubnets, client.MatchingLabels{vpcapi.LabelVPC: vpcKey.Name}) + if err != nil { + return errors.Wrapf(err, "error listing dhcp subnets") + } + + for _, subnet := range dhcpSubnets.Items { + subnetName := "default" + parts := strings.Split(subnet.Spec.Subnet, "/") + if len(parts) == 2 { + subnetName = parts[1] + } + + if _, exists := subnets[subnetName]; exists { + continue + } + + err = r.Delete(ctx, &subnet) + if client.IgnoreNotFound(err) != nil { + return errors.Wrapf(err, "error deleting dhcp subnet %s", subnet.Name) + } + } + + return nil +} diff --git a/pkg/ctrl/vpc/vpc_ctrl.go b/pkg/ctrl/vpc/vpc_ctrl.go index 4c32b02c..a0be364a 100644 --- a/pkg/ctrl/vpc/vpc_ctrl.go +++ b/pkg/ctrl/vpc/vpc_ctrl.go @@ -89,10 +89,14 @@ func (r *VPCReconciler) enqueueOneVPC(ctx context.Context, obj client.Object) [] //+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=dhcp.githedgehog.com,resources=dhcpsubnets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=dhcp.githedgehog.com,resources=dhcpsubnets/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=dhcp.githedgehog.com,resources=dhcpsubnets/finalizers,verbs=update + func (r *VPCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx) - err := r.updateDHCPConfig(ctx) + err := r.updateISCDHCPConfig(ctx) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "error updating dhcp config") } @@ -101,11 +105,21 @@ func (r *VPCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R err = r.Get(ctx, req.NamespacedName, vpc) if err != nil { if apierrors.IsNotFound(err) { + l.Info("vpc deleted, cleaning up dhcp subnets") + err = r.deleteDHCPSubnets(ctx, req.NamespacedName, map[string]*vpcapi.VPCSubnet{}) + if err != nil { + return ctrl.Result{}, errors.Wrapf(err, "error deleting dhcp subnets for removed vpc") + } return ctrl.Result{}, nil } return ctrl.Result{}, errors.Wrapf(err, "error getting vpc %s", req.NamespacedName) } + err = r.updateDHCPSubnets(ctx, vpc) + if err != nil { + return ctrl.Result{}, errors.Wrapf(err, "error updating dhcp subnets") + } + if err := r.ensureVNIs(ctx, vpc); err != nil { return ctrl.Result{}, errors.Wrapf(err, "error ensuring vpc vnis") }