From 745a016070f064f74637eee753bc6e2d94fd2556 Mon Sep 17 00:00:00 2001 From: Gor <37933026+pogossian@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:11:12 +0400 Subject: [PATCH] Added Subnet CRD (#53) * feat: add subnet support * added printcolumns * fix: NStorage: Search subnets in children subnets too * fix: subnet: right message in status field * fix: subnet: fields validation * fix: allocation: right message in status field * fix: site: right message in status field * fix: site: right message in status field * fix: finalizers names for delete * refactor: update go version from 1.16 to 1.17 * fix: subnet example * typos * added subnet samples * update subnet sample Co-authored-by: Artashes Balabekyan --- PROJECT | 18 ++ api/v1alpha1/allocation_types.go | 1 + api/v1alpha1/site_types.go | 1 + api/v1alpha1/subnet_types.go | 82 ++++++ api/v1alpha1/subnetmeta_types.go | 72 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 188 ++++++++++++++ calicowatcher/main.go | 2 +- .../crd/bases/k8s.netris.ai_allocations.yaml | 3 + config/crd/bases/k8s.netris.ai_sites.yaml | 3 + .../crd/bases/k8s.netris.ai_subnetmeta.yaml | 82 ++++++ config/crd/bases/k8s.netris.ai_subnets.yaml | 101 ++++++++ config/crd/kustomization.yaml | 6 + .../patches/cainjection_in_subnetmeta.yaml | 7 + .../crd/patches/cainjection_in_subnets.yaml | 7 + config/crd/patches/webhook_in_subnetmeta.yaml | 16 ++ config/crd/patches/webhook_in_subnets.yaml | 16 ++ config/rbac/role.yaml | 52 ++++ config/rbac/subnet_editor_role.yaml | 24 ++ config/rbac/subnet_viewer_role.yaml | 20 ++ config/rbac/subnetmeta_editor_role.yaml | 24 ++ config/rbac/subnetmeta_viewer_role.yaml | 20 ++ controllers/allocation_controller.go | 2 +- controllers/allocationmeta_controller.go | 2 +- controllers/bgp_controller.go | 2 +- controllers/controller.go | 15 ++ controllers/l4lb_controller.go | 2 +- controllers/site_controller.go | 2 +- controllers/sitemeta_controller.go | 2 +- controllers/subnet_controller.go | 230 +++++++++++++++++ controllers/subnet_translations.go | 192 ++++++++++++++ controllers/subnetmeta_controller.go | 242 ++++++++++++++++++ controllers/vnet_controller.go | 2 +- go.mod | 59 ++++- main.go | 20 ++ netrisstorage/subnets.go | 30 +++ samples/README.md | 25 ++ samples/subnet.yaml | 10 + 37 files changed, 1570 insertions(+), 12 deletions(-) create mode 100644 api/v1alpha1/subnet_types.go create mode 100644 api/v1alpha1/subnetmeta_types.go create mode 100644 config/crd/bases/k8s.netris.ai_subnetmeta.yaml create mode 100644 config/crd/bases/k8s.netris.ai_subnets.yaml create mode 100644 config/crd/patches/cainjection_in_subnetmeta.yaml create mode 100644 config/crd/patches/cainjection_in_subnets.yaml create mode 100644 config/crd/patches/webhook_in_subnetmeta.yaml create mode 100644 config/crd/patches/webhook_in_subnets.yaml create mode 100644 config/rbac/subnet_editor_role.yaml create mode 100644 config/rbac/subnet_viewer_role.yaml create mode 100644 config/rbac/subnetmeta_editor_role.yaml create mode 100644 config/rbac/subnetmeta_viewer_role.yaml create mode 100644 controllers/subnet_controller.go create mode 100644 controllers/subnet_translations.go create mode 100644 controllers/subnetmeta_controller.go create mode 100644 samples/subnet.yaml diff --git a/PROJECT b/PROJECT index 619ff55..76e943b 100644 --- a/PROJECT +++ b/PROJECT @@ -97,4 +97,22 @@ resources: kind: AllocationMeta path: github.com/netrisai/netris-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netris.ai + group: k8s + kind: Subnet + path: github.com/netrisai/netris-operator/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netris.ai + group: k8s + kind: SubnetMeta + path: github.com/netrisai/netris-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/allocation_types.go b/api/v1alpha1/allocation_types.go index dc63d4a..ce2ba79 100644 --- a/api/v1alpha1/allocation_types.go +++ b/api/v1alpha1/allocation_types.go @@ -46,6 +46,7 @@ type AllocationStatus struct { // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Prefix",type=string,JSONPath=`.spec.prefix` // +kubebuilder:printcolumn:name="Tenant",type=string,JSONPath=`.spec.tenant` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // Allocation is the Schema for the allocations API diff --git a/api/v1alpha1/site_types.go b/api/v1alpha1/site_types.go index b64c54d..766f804 100644 --- a/api/v1alpha1/site_types.go +++ b/api/v1alpha1/site_types.go @@ -66,6 +66,7 @@ type SiteSpec struct { // +kubebuilder:printcolumn:name="ROH Routing Profile",type=string,JSONPath=`.spec.rohRoutingProfile` // +kubebuilder:printcolumn:name="Site Mesh",type=string,JSONPath=`.spec.siteMesh` // +kubebuilder:printcolumn:name="ACL Default Policy",type=string,JSONPath=`.spec.aclDefaultPolicy` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // Site is the Schema for the sites API diff --git a/api/v1alpha1/subnet_types.go b/api/v1alpha1/subnet_types.go new file mode 100644 index 0000000..c68313b --- /dev/null +++ b/api/v1alpha1/subnet_types.go @@ -0,0 +1,82 @@ +/* +Copyright 2021. Netris, Inc. + +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// SubnetSpec defines the desired state of Subnet +type SubnetSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // +kubebuilder:validation:Pattern=`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$)` + Prefix string `json:"prefix,omitempty"` + Tenant string `json:"tenant,omitempty"` + + // +kubebuilder:validation:Enum=common;loopback;management;load-balancer;nat;inactive + Purpose string `json:"purpose,omitempty"` + + // +kubebuilder:validation:Pattern=`^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + DefaultGateway string `json:"defaultGateway,omitempty"` + Sites []string `json:"sites,omitempty"` +} + +// SubnetStatus defines the observed state of Subnet +type SubnetStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Prefix",type=string,JSONPath=`.spec.prefix` +// +kubebuilder:printcolumn:name="Tenant",type=string,JSONPath=`.spec.tenant` +// +kubebuilder:printcolumn:name="Purpose",type=string,JSONPath=`.spec.purpose` +// +kubebuilder:printcolumn:name="Sites",type=string,JSONPath=`.spec.sites` +// +kubebuilder:printcolumn:name="Default Gateway",type=string,JSONPath=`.spec.defaultGateway` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// Subnet is the Schema for the subnets API +type Subnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SubnetSpec `json:"spec,omitempty"` + Status SubnetStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SubnetList contains a list of Subnet +type SubnetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Subnet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Subnet{}, &SubnetList{}) +} diff --git a/api/v1alpha1/subnetmeta_types.go b/api/v1alpha1/subnetmeta_types.go new file mode 100644 index 0000000..245b3dd --- /dev/null +++ b/api/v1alpha1/subnetmeta_types.go @@ -0,0 +1,72 @@ +/* +Copyright 2021. Netris, Inc. + +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// SubnetMetaSpec defines the desired state of SubnetMeta +type SubnetMetaSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + Imported bool `json:"imported"` + Reclaim bool `json:"reclaimPolicy"` + SubnetCRGeneration int64 `json:"subnetGeneration"` + ID int `json:"id"` + SubnetName string `json:"subnetName"` + + Prefix string `json:"prefix,omitempty"` + TenantID int `json:"tenantid,omitempty"` + Purpose string `json:"purpose,omitempty"` + DefaultGateway string `json:"defaultGateway,omitempty"` + Sites []int `json:"sites,omitempty"` +} + +// SubnetMetaStatus defines the observed state of SubnetMeta +type SubnetMetaStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// SubnetMeta is the Schema for the subnetmeta API +type SubnetMeta struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SubnetMetaSpec `json:"spec,omitempty"` + Status SubnetMetaStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SubnetMetaList contains a list of SubnetMeta +type SubnetMetaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SubnetMeta `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SubnetMeta{}, &SubnetMetaList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e93d8bc..3ee871f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -902,6 +902,194 @@ func (in *SiteStatus) DeepCopy() *SiteStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subnet) DeepCopyInto(out *Subnet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subnet. +func (in *Subnet) DeepCopy() *Subnet { + if in == nil { + return nil + } + out := new(Subnet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Subnet) 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 *SubnetList) DeepCopyInto(out *SubnetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Subnet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetList. +func (in *SubnetList) DeepCopy() *SubnetList { + if in == nil { + return nil + } + out := new(SubnetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetList) 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 *SubnetMeta) DeepCopyInto(out *SubnetMeta) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetMeta. +func (in *SubnetMeta) DeepCopy() *SubnetMeta { + if in == nil { + return nil + } + out := new(SubnetMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetMeta) 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 *SubnetMetaList) DeepCopyInto(out *SubnetMetaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SubnetMeta, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetMetaList. +func (in *SubnetMetaList) DeepCopy() *SubnetMetaList { + if in == nil { + return nil + } + out := new(SubnetMetaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetMetaList) 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 *SubnetMetaSpec) DeepCopyInto(out *SubnetMetaSpec) { + *out = *in + if in.Sites != nil { + in, out := &in.Sites, &out.Sites + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetMetaSpec. +func (in *SubnetMetaSpec) DeepCopy() *SubnetMetaSpec { + if in == nil { + return nil + } + out := new(SubnetMetaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetMetaStatus) DeepCopyInto(out *SubnetMetaStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetMetaStatus. +func (in *SubnetMetaStatus) DeepCopy() *SubnetMetaStatus { + if in == nil { + return nil + } + out := new(SubnetMetaStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSpec) DeepCopyInto(out *SubnetSpec) { + *out = *in + if in.Sites != nil { + in, out := &in.Sites, &out.Sites + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSpec. +func (in *SubnetSpec) DeepCopy() *SubnetSpec { + if in == nil { + return nil + } + out := new(SubnetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetStatus) DeepCopyInto(out *SubnetStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetStatus. +func (in *SubnetStatus) DeepCopy() *SubnetStatus { + if in == nil { + return nil + } + out := new(SubnetStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VNet) DeepCopyInto(out *VNet) { *out = *in diff --git a/calicowatcher/main.go b/calicowatcher/main.go index 945d212..80ab209 100644 --- a/calicowatcher/main.go +++ b/calicowatcher/main.go @@ -375,7 +375,7 @@ func (w *Watcher) deleteProcess() error { if err := w.Calico.DeleteBGPPeer(netrisPeer, w.restClient); err != nil { return err } - logger.Info("netris-controller pee deleted", "deleteMode", w.data.deleteMode) + logger.Info("Peers in netris-controller are deleted", "deleteMode", w.data.deleteMode) } return nil diff --git a/config/crd/bases/k8s.netris.ai_allocations.yaml b/config/crd/bases/k8s.netris.ai_allocations.yaml index bf8d40e..1f44525 100644 --- a/config/crd/bases/k8s.netris.ai_allocations.yaml +++ b/config/crd/bases/k8s.netris.ai_allocations.yaml @@ -23,6 +23,9 @@ spec: - jsonPath: .spec.tenant name: Tenant type: string + - jsonPath: .status.status + name: Status + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/k8s.netris.ai_sites.yaml b/config/crd/bases/k8s.netris.ai_sites.yaml index f8cd373..9678013 100644 --- a/config/crd/bases/k8s.netris.ai_sites.yaml +++ b/config/crd/bases/k8s.netris.ai_sites.yaml @@ -35,6 +35,9 @@ spec: - jsonPath: .spec.aclDefaultPolicy name: ACL Default Policy type: string + - jsonPath: .status.status + name: Status + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/k8s.netris.ai_subnetmeta.yaml b/config/crd/bases/k8s.netris.ai_subnetmeta.yaml new file mode 100644 index 0000000..8162b5f --- /dev/null +++ b/config/crd/bases/k8s.netris.ai_subnetmeta.yaml @@ -0,0 +1,82 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: subnetmeta.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: SubnetMeta + listKind: SubnetMetaList + plural: subnetmeta + singular: subnetmeta + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: SubnetMeta is the Schema for the subnetmeta 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: SubnetMetaSpec defines the desired state of SubnetMeta + properties: + defaultGateway: + type: string + id: + type: integer + imported: + type: boolean + prefix: + type: string + purpose: + type: string + reclaimPolicy: + type: boolean + sites: + items: + type: integer + type: array + subnetGeneration: + format: int64 + type: integer + subnetName: + type: string + tenantid: + type: integer + required: + - id + - imported + - reclaimPolicy + - subnetGeneration + - subnetName + type: object + status: + description: SubnetMetaStatus defines the observed state of SubnetMeta + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/k8s.netris.ai_subnets.yaml b/config/crd/bases/k8s.netris.ai_subnets.yaml new file mode 100644 index 0000000..3490cf9 --- /dev/null +++ b/config/crd/bases/k8s.netris.ai_subnets.yaml @@ -0,0 +1,101 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: subnets.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: Subnet + listKind: SubnetList + plural: subnets + singular: subnet + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.prefix + name: Prefix + type: string + - jsonPath: .spec.tenant + name: Tenant + type: string + - jsonPath: .spec.purpose + name: Purpose + type: string + - jsonPath: .spec.sites + name: Sites + type: string + - jsonPath: .spec.defaultGateway + name: Default Gateway + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Subnet is the Schema for the subnets 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: SubnetSpec defines the desired state of Subnet + properties: + defaultGateway: + pattern: ^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + type: string + prefix: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + purpose: + enum: + - common + - loopback + - management + - load-balancer + - nat + - inactive + type: string + sites: + items: + type: string + type: array + tenant: + type: string + type: object + status: + description: SubnetStatus defines the observed state of Subnet + properties: + message: + type: string + status: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 3ad42fc..f41b77d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,8 @@ resources: - bases/k8s.netris.ai_sitemeta.yaml - bases/k8s.netris.ai_allocations.yaml - bases/k8s.netris.ai_allocationmeta.yaml +- bases/k8s.netris.ai_subnets.yaml +- bases/k8s.netris.ai_subnetmeta.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -27,6 +29,8 @@ patchesStrategicMerge: #- patches/webhook_in_sitemeta.yaml #- patches/webhook_in_allocations.yaml #- patches/webhook_in_allocationmeta.yaml +#- patches/webhook_in_subnets.yaml +#- patches/webhook_in_subnetmeta.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -41,6 +45,8 @@ patchesStrategicMerge: #- patches/cainjection_in_sitemeta.yaml #- patches/cainjection_in_allocations.yaml #- patches/cainjection_in_allocationmeta.yaml +#- patches/cainjection_in_subnets.yaml +#- patches/cainjection_in_subnetmeta.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_subnetmeta.yaml b/config/crd/patches/cainjection_in_subnetmeta.yaml new file mode 100644 index 0000000..b30c7eb --- /dev/null +++ b/config/crd/patches/cainjection_in_subnetmeta.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: subnetmeta.k8s.netris.ai diff --git a/config/crd/patches/cainjection_in_subnets.yaml b/config/crd/patches/cainjection_in_subnets.yaml new file mode 100644 index 0000000..b5c1494 --- /dev/null +++ b/config/crd/patches/cainjection_in_subnets.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: subnets.k8s.netris.ai diff --git a/config/crd/patches/webhook_in_subnetmeta.yaml b/config/crd/patches/webhook_in_subnetmeta.yaml new file mode 100644 index 0000000..dfcd3f7 --- /dev/null +++ b/config/crd/patches/webhook_in_subnetmeta.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: subnetmeta.k8s.netris.ai +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_subnets.yaml b/config/crd/patches/webhook_in_subnets.yaml new file mode 100644 index 0000000..4b617d5 --- /dev/null +++ b/config/crd/patches/webhook_in_subnets.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: subnets.k8s.netris.ai +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 8cf81cf..170d0f3 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -285,6 +285,58 @@ rules: - get - patch - update +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta/finalizers + verbs: + - update +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta/status + verbs: + - get + - patch + - update +- apiGroups: + - k8s.netris.ai + resources: + - subnets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnets/finalizers + verbs: + - update +- apiGroups: + - k8s.netris.ai + resources: + - subnets/status + verbs: + - get + - patch + - update - apiGroups: - k8s.netris.ai resources: diff --git a/config/rbac/subnet_editor_role.yaml b/config/rbac/subnet_editor_role.yaml new file mode 100644 index 0000000..63c6d69 --- /dev/null +++ b/config/rbac/subnet_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit subnets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: subnet-editor-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - subnets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnets/status + verbs: + - get diff --git a/config/rbac/subnet_viewer_role.yaml b/config/rbac/subnet_viewer_role.yaml new file mode 100644 index 0000000..e6664d4 --- /dev/null +++ b/config/rbac/subnet_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view subnets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: subnet-viewer-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - subnets + verbs: + - get + - list + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnets/status + verbs: + - get diff --git a/config/rbac/subnetmeta_editor_role.yaml b/config/rbac/subnetmeta_editor_role.yaml new file mode 100644 index 0000000..b2b70e8 --- /dev/null +++ b/config/rbac/subnetmeta_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit subnetmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: subnetmeta-editor-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta/status + verbs: + - get diff --git a/config/rbac/subnetmeta_viewer_role.yaml b/config/rbac/subnetmeta_viewer_role.yaml new file mode 100644 index 0000000..049e7c0 --- /dev/null +++ b/config/rbac/subnetmeta_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view subnetmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: subnetmeta-viewer-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta + verbs: + - get + - list + - watch +- apiGroups: + - k8s.netris.ai + resources: + - subnetmeta/status + verbs: + - get diff --git a/controllers/allocation_controller.go b/controllers/allocation_controller.go index 9a11796..58c42db 100644 --- a/controllers/allocation_controller.go +++ b/controllers/allocation_controller.go @@ -141,7 +141,7 @@ func (r *AllocationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) } else { debugLogger.Info("Meta not found") if allocation.GetFinalizers() == nil { - allocation.SetFinalizers([]string{"vnet.k8s.netris.ai/delete"}) + allocation.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) allocationPatchCtx, allocationPatchCancel := context.WithTimeout(cntxt, contextTimeout) defer allocationPatchCancel() diff --git a/controllers/allocationmeta_controller.go b/controllers/allocationmeta_controller.go index 4b625a1..7fa43a7 100644 --- a/controllers/allocationmeta_controller.go +++ b/controllers/allocationmeta_controller.go @@ -80,7 +80,7 @@ func (r *AllocationMetaReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err NStorage: r.NStorage, } - provisionState := "Provisioning" + provisionState := "OK" allocationNN := req.NamespacedName allocationNN.Name = allocationMeta.Spec.AllocationName diff --git a/controllers/bgp_controller.go b/controllers/bgp_controller.go index 2537326..67e3825 100644 --- a/controllers/bgp_controller.go +++ b/controllers/bgp_controller.go @@ -140,7 +140,7 @@ func (r *BGPReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } else { debugLogger.Info("Meta not found") if bgp.GetFinalizers() == nil { - bgp.SetFinalizers([]string{"vnet.k8s.netris.ai/delete"}) + bgp.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) bgpPatchCtx, bgpPatchCancel := context.WithTimeout(cntxt, contextTimeout) defer bgpPatchCancel() diff --git a/controllers/controller.go b/controllers/controller.go index 251aff9..48c771c 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -146,3 +146,18 @@ func (u *uniReconciler) patchAllocationStatus(allocation *k8sv1alpha1.Allocation } return ctrl.Result{RequeueAfter: requeueInterval}, nil } + +func (u *uniReconciler) patchSubnetStatus(subnet *k8sv1alpha1.Subnet, status, message string) (ctrl.Result, error) { + u.DebugLogger.Info("Patching Status", "status", status, "message", message) + + subnet.Status.Status = status + subnet.Status.Message = message + + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + err := u.Status().Patch(ctx, subnet.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + u.DebugLogger.Info("{r.Status().Patch}", "error", err, "action", "status update") + } + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} diff --git a/controllers/l4lb_controller.go b/controllers/l4lb_controller.go index 3d75df5..0fa9041 100644 --- a/controllers/l4lb_controller.go +++ b/controllers/l4lb_controller.go @@ -140,7 +140,7 @@ func (r *L4LBReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } else { debugLogger.Info("Meta not found") if l4lb.GetFinalizers() == nil { - l4lb.SetFinalizers([]string{"l4lb.k8s.netris.ai/delete"}) + l4lb.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) l4lbCtx, l4lbCancel := context.WithTimeout(cntxt, contextTimeout) defer l4lbCancel() err := r.Patch(l4lbCtx, l4lb.DeepCopyObject(), client.Merge, &client.PatchOptions{}) diff --git a/controllers/site_controller.go b/controllers/site_controller.go index d7a9862..e93f0bc 100644 --- a/controllers/site_controller.go +++ b/controllers/site_controller.go @@ -136,7 +136,7 @@ func (r *SiteReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } else { debugLogger.Info("Meta not found") if site.GetFinalizers() == nil { - site.SetFinalizers([]string{"vnet.k8s.netris.ai/delete"}) + site.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) sitePatchCtx, sitePatchCancel := context.WithTimeout(cntxt, contextTimeout) defer sitePatchCancel() diff --git a/controllers/sitemeta_controller.go b/controllers/sitemeta_controller.go index 78f35c9..37f12cd 100644 --- a/controllers/sitemeta_controller.go +++ b/controllers/sitemeta_controller.go @@ -75,7 +75,7 @@ func (r *SiteMetaReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { NStorage: r.NStorage, } - provisionState := "Provisioning" + provisionState := "OK" siteNN := req.NamespacedName siteNN.Name = siteMeta.Spec.SiteName diff --git a/controllers/subnet_controller.go b/controllers/subnet_controller.go new file mode 100644 index 0000000..17bcc74 --- /dev/null +++ b/controllers/subnet_controller.go @@ -0,0 +1,230 @@ +/* +Copyright 2021. Netris, Inc. + +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 controllers + +import ( + "context" + "fmt" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/go-logr/logr" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/netrisstorage" + "github.com/netrisai/netriswebapi/http" + api "github.com/netrisai/netriswebapi/v2" +) + +// SubnetReconciler reconciles a Subnet object +type SubnetReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Cred *api.Clientset + NStorage *netrisstorage.Storage +} + +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnets/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnets/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Subnet object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +func (r *SubnetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + logger := r.Log.WithValues("name", req.NamespacedName) + debugLogger := logger.V(int(zapcore.WarnLevel)) + subnet := &k8sv1alpha1.Subnet{} + + u := uniReconciler{ + Client: r.Client, + Logger: logger, + DebugLogger: debugLogger, + Cred: r.Cred, + NStorage: r.NStorage, + } + + subnetCtx, subnetCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetCancel() + if err := r.Get(subnetCtx, req.NamespacedName, subnet); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + subnetMetaNamespaced := req.NamespacedName + subnetMetaNamespaced.Name = string(subnet.GetUID()) + subnetMeta := &k8sv1alpha1.SubnetMeta{} + metaFound := true + + subnetMetaCtx, subnetMetaCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetMetaCancel() + if err := r.Get(subnetMetaCtx, subnetMetaNamespaced, subnetMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + metaFound = false + subnetMeta = nil + } else { + return ctrl.Result{}, err + } + } + + if subnet.DeletionTimestamp != nil { + logger.Info("Go to delete") + _, err := r.deleteSubnet(subnet, subnetMeta) + if err != nil { + logger.Error(fmt.Errorf("{deleteSubnet} %s", err), "") + return u.patchSubnetStatus(subnet, "Failure", err.Error()) + } + logger.Info("Subnet deleted") + return ctrl.Result{}, nil + } + + if subnetMustUpdateAnnotations(subnet) { + debugLogger.Info("Setting default annotations") + subnetUpdateDefaultAnnotations(subnet) + subnetPatchCtx, subnetPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetPatchCancel() + err := r.Patch(subnetPatchCtx, subnet.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{Patch Subnet default annotations} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + return ctrl.Result{}, nil + } + + if metaFound { + debugLogger.Info("Meta found") + if subnetCompareFieldsForNewMeta(subnet, subnetMeta) { + debugLogger.Info("Generating New Meta") + subnetID := subnetMeta.Spec.ID + newSubnetMeta, err := r.SubnetToSubnetMeta(subnet) + if err != nil { + logger.Error(fmt.Errorf("{SubnetToSubnetMeta} %s", err), "") + return u.patchSubnetStatus(subnet, "Failure", err.Error()) + } + subnetMeta.Spec = newSubnetMeta.DeepCopy().Spec + subnetMeta.Spec.ID = subnetID + subnetMeta.Spec.SubnetCRGeneration = subnet.GetGeneration() + + subnetMetaUpdateCtx, subnetMetaUpdateCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetMetaUpdateCancel() + err = r.Update(subnetMetaUpdateCtx, subnetMeta.DeepCopyObject(), &client.UpdateOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{subnetMeta Update} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + } + } else { + debugLogger.Info("Meta not found") + if subnet.GetFinalizers() == nil { + subnet.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) + + subnetPatchCtx, subnetPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetPatchCancel() + err := r.Patch(subnetPatchCtx, subnet.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{Patch Subnet Finalizer} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + return ctrl.Result{}, nil + } + + subnetMeta, err := r.SubnetToSubnetMeta(subnet) + if err != nil { + logger.Error(fmt.Errorf("{SubnetToSubnetMeta} %s", err), "") + return u.patchSubnetStatus(subnet, "Failure", err.Error()) + } + + subnetMeta.Spec.SubnetCRGeneration = subnet.GetGeneration() + + subnetMetaCreateCtx, subnetMetaCreateCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetMetaCreateCancel() + if err := r.Create(subnetMetaCreateCtx, subnetMeta.DeepCopyObject(), &client.CreateOptions{}); err != nil { + logger.Error(fmt.Errorf("{subnetMeta Create} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + } + + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} + +func (r *SubnetReconciler) deleteSubnet(subnet *k8sv1alpha1.Subnet, subnetMeta *k8sv1alpha1.SubnetMeta) (ctrl.Result, error) { + if subnetMeta != nil && subnetMeta.Spec.ID > 0 && !subnetMeta.Spec.Reclaim { + reply, err := r.Cred.IPAM().Delete("subnet", subnetMeta.Spec.ID) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteSubnet} %s", err) + } + resp, err := http.ParseAPIResponse(reply.Data) + if err != nil { + return ctrl.Result{}, err + } + if !resp.IsSuccess && resp.Meta.StatusCode != 400 { + return ctrl.Result{}, fmt.Errorf("{deleteSubnet} %s", fmt.Errorf(resp.Message)) + } + } + return r.deleteCRs(subnet, subnetMeta) +} + +func (r *SubnetReconciler) deleteCRs(subnet *k8sv1alpha1.Subnet, subnetMeta *k8sv1alpha1.SubnetMeta) (ctrl.Result, error) { + if subnetMeta != nil { + _, err := r.deleteSubnetMetaCR(subnetMeta) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteCRs} %s", err) + } + } + + return r.deleteSubnetCR(subnet) +} + +func (r *SubnetReconciler) deleteSubnetCR(subnet *k8sv1alpha1.Subnet) (ctrl.Result, error) { + subnet.ObjectMeta.SetFinalizers(nil) + subnet.SetFinalizers(nil) + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + if err := r.Update(ctx, subnet.DeepCopyObject(), &client.UpdateOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteSubnetCR} %s", err) + } + + return ctrl.Result{}, nil +} + +func (r *SubnetReconciler) deleteSubnetMetaCR(subnetMeta *k8sv1alpha1.SubnetMeta) (ctrl.Result, error) { + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + if err := r.Delete(ctx, subnetMeta.DeepCopyObject(), &client.DeleteOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteSubnetMetaCR} %s", err) + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SubnetReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&k8sv1alpha1.Subnet{}). + Complete(r) +} diff --git a/controllers/subnet_translations.go b/controllers/subnet_translations.go new file mode 100644 index 0000000..d81974f --- /dev/null +++ b/controllers/subnet_translations.go @@ -0,0 +1,192 @@ +/* +Copyright 2021. Netris, Inc. + +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 controllers + +import ( + "fmt" + + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netriswebapi/v2/types/ipam" + "github.com/r3labs/diff/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SubnetToSubnetMeta converts the Subnet resource to SubnetMeta type and used for add the Subnet for Netris API. +func (r *SubnetReconciler) SubnetToSubnetMeta(subnet *k8sv1alpha1.Subnet) (*k8sv1alpha1.SubnetMeta, error) { + var ( + imported = false + reclaim = false + ) + + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = true + } + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = true + } + + sites := []int{} + for _, s := range subnet.Spec.Sites { + if site, ok := r.NStorage.SitesStorage.FindByName(s); ok { + sites = append(sites, site.ID) + } else { + return nil, fmt.Errorf("Invalid site '%s'", s) + } + } + + tenantID := 0 + if tenant, ok := r.NStorage.TenantsStorage.FindByName(subnet.Spec.Tenant); ok { + tenantID = tenant.ID + } else { + return nil, fmt.Errorf("Invalid tenant '%s'", subnet.Spec.Tenant) + } + + subnetMeta := &k8sv1alpha1.SubnetMeta{ + ObjectMeta: metav1.ObjectMeta{ + Name: string(subnet.GetUID()), + Namespace: subnet.GetNamespace(), + }, + TypeMeta: metav1.TypeMeta{}, + Spec: k8sv1alpha1.SubnetMetaSpec{ + Imported: imported, + Reclaim: reclaim, + SubnetName: subnet.Name, + Prefix: subnet.Spec.Prefix, + TenantID: tenantID, + Purpose: subnet.Spec.Purpose, + DefaultGateway: subnet.Spec.DefaultGateway, + Sites: sites, + }, + } + + return subnetMeta, nil +} + +func subnetCompareFieldsForNewMeta(subnet *k8sv1alpha1.Subnet, subnetMeta *k8sv1alpha1.SubnetMeta) bool { + imported := false + reclaim := false + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = true + } + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = true + } + return subnet.GetGeneration() != subnetMeta.Spec.SubnetCRGeneration || imported != subnetMeta.Spec.Imported || reclaim != subnetMeta.Spec.Reclaim +} + +func subnetMustUpdateAnnotations(subnet *k8sv1alpha1.Subnet) bool { + update := false + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/import"]; !(ok && (i == "true" || i == "false")) { + update = true + } + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; !(ok && (i == "retain" || i == "delete")) { + update = true + } + return update +} + +func subnetUpdateDefaultAnnotations(subnet *k8sv1alpha1.Subnet) { + imported := "false" + reclaim := "delete" + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = "true" + } + if i, ok := subnet.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = "retain" + } + annotations := subnet.GetAnnotations() + annotations["resource.k8s.netris.ai/import"] = imported + annotations["resource.k8s.netris.ai/reclaimPolicy"] = reclaim + subnet.SetAnnotations(annotations) +} + +// SubnetMetaToNetris converts the k8s Subnet resource to Netris type and used for add the Subnet for Netris API. +func SubnetMetaToNetris(subnetMeta *k8sv1alpha1.SubnetMeta) (*ipam.Subnet, error) { + sites := []ipam.IDName{} + for _, site := range subnetMeta.Spec.Sites { + sites = append(sites, ipam.IDName{ID: site}) + } + subnetAdd := &ipam.Subnet{ + Name: subnetMeta.Spec.SubnetName, + Prefix: subnetMeta.Spec.Prefix, + Tenant: ipam.IDName{ID: subnetMeta.Spec.TenantID}, + Purpose: subnetMeta.Spec.Purpose, + DefaultGateway: subnetMeta.Spec.DefaultGateway, + Sites: sites, + } + + return subnetAdd, nil +} + +// SubnetMetaToNetrisUpdate converts the k8s Subnet resource to Netris type and used for update the Subnet for Netris API. +func SubnetMetaToNetrisUpdate(subnetMeta *k8sv1alpha1.SubnetMeta) (*ipam.Subnet, error) { + sites := []ipam.IDName{} + for _, site := range subnetMeta.Spec.Sites { + sites = append(sites, ipam.IDName{ID: site}) + } + subnetAdd := &ipam.Subnet{ + Name: subnetMeta.Spec.SubnetName, + Prefix: subnetMeta.Spec.Prefix, + Tenant: ipam.IDName{ID: subnetMeta.Spec.TenantID}, + Purpose: subnetMeta.Spec.Purpose, + DefaultGateway: subnetMeta.Spec.DefaultGateway, + Sites: sites, + } + + return subnetAdd, nil +} + +func compareSubnetMetaAPIESubnet(subnetMeta *k8sv1alpha1.SubnetMeta, apiSubnet *ipam.IPAM, u uniReconciler) bool { + if apiSubnet.Name != subnetMeta.Spec.SubnetName { + u.DebugLogger.Info("Name changed", "netrisValue", apiSubnet.Name, "k8sValue", subnetMeta.Spec.SubnetName) + return false + } + + if apiSubnet.Prefix != subnetMeta.Spec.Prefix { + u.DebugLogger.Info("Prefix changed", "netrisValue", apiSubnet.Prefix, "k8sValue", subnetMeta.Spec.Prefix) + return false + } + + if apiSubnet.Purpose != subnetMeta.Spec.Purpose { + u.DebugLogger.Info("Purpose changed", "netrisValue", apiSubnet.Purpose, "k8sValue", subnetMeta.Spec.Purpose) + return false + } + + if apiSubnet.DefaultGateway != subnetMeta.Spec.DefaultGateway { + u.DebugLogger.Info("DefaultGateway changed", "netrisValue", apiSubnet.DefaultGateway, "k8sValue", subnetMeta.Spec.DefaultGateway) + return false + } + + if ok := compareSubnetMetaSiteAPISubnetSite(subnetMeta.Spec.Sites, apiSubnet.Sites); !ok { + u.DebugLogger.Info("Sites changed", "netrisValue", apiSubnet.Sites, "k8sValue", subnetMeta.Spec.Sites) + return false + } + + return true +} + +func compareSubnetMetaSiteAPISubnetSite(subnetMetaSites []int, apiSubnetSites []ipam.IDName) bool { + apiSites := []int{} + + for _, s := range apiSubnetSites { + apiSites = append(apiSites, s.ID) + } + + changelog, _ := diff.Diff(subnetMetaSites, apiSites) + + return len(changelog) <= 0 +} diff --git a/controllers/subnetmeta_controller.go b/controllers/subnetmeta_controller.go new file mode 100644 index 0000000..81eb042 --- /dev/null +++ b/controllers/subnetmeta_controller.go @@ -0,0 +1,242 @@ +/* +Copyright 2021. Netris, Inc. + +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 controllers + +import ( + "context" + "encoding/json" + "fmt" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/go-logr/logr" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/netrisstorage" + "github.com/netrisai/netriswebapi/http" + api "github.com/netrisai/netriswebapi/v2" + "github.com/netrisai/netriswebapi/v2/types/ipam" +) + +// SubnetMetaReconciler reconciles a SubnetMeta object +type SubnetMetaReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Cred *api.Clientset + NStorage *netrisstorage.Storage +} + +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnetmeta,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnetmeta/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=subnetmeta/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the SubnetMeta object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile +func (r *SubnetMetaReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + debugLogger := r.Log.WithValues("name", req.NamespacedName).V(int(zapcore.WarnLevel)) + + subnetMeta := &k8sv1alpha1.SubnetMeta{} + subnetCR := &k8sv1alpha1.Subnet{} + subnetMetaCtx, subnetMetaCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetMetaCancel() + if err := r.Get(subnetMetaCtx, req.NamespacedName, subnetMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + logger := r.Log.WithValues("name", fmt.Sprintf("%s/%s", req.NamespacedName.Namespace, subnetMeta.Spec.SubnetName)) + debugLogger = logger.V(int(zapcore.WarnLevel)) + + u := uniReconciler{ + Client: r.Client, + Logger: logger, + DebugLogger: debugLogger, + Cred: r.Cred, + NStorage: r.NStorage, + } + + provisionState := "OK" + + subnetNN := req.NamespacedName + subnetNN.Name = subnetMeta.Spec.SubnetName + subnetNNCtx, subnetNNCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetNNCancel() + if err := r.Get(subnetNNCtx, subnetNN, subnetCR); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if subnetMeta.DeletionTimestamp != nil { + return ctrl.Result{}, nil + } + + if subnetMeta.Spec.ID == 0 { + debugLogger.Info("ID Not found in meta") + if subnetMeta.Spec.Imported { + logger.Info("Importing subnet") + debugLogger.Info("Imported yaml mode. Finding Subnet by name") + if subnet, ok := r.NStorage.SubnetsStorage.FindByName(subnetMeta.Spec.SubnetName); ok { + debugLogger.Info("Imported yaml mode. Subnet found") + subnetMeta.Spec.ID = subnet.ID + + subnetMetaPatchCtx, subnetMetaPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer subnetMetaPatchCancel() + err := r.Patch(subnetMetaPatchCtx, subnetMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{patch subnetmeta.Spec.ID} %s", err), "") + return u.patchSubnetStatus(subnetCR, "Failure", err.Error()) + } + debugLogger.Info("Imported yaml mode. ID patched") + logger.Info("Subnet imported") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("Subnet not found for import") + debugLogger.Info("Imported yaml mode. Subnet not found") + } + + logger.Info("Creating Subnet") + if _, err, errMsg := r.createSubnet(subnetMeta); err != nil { + logger.Error(fmt.Errorf("{createSubnet} %s", err), "") + return u.patchSubnetStatus(subnetCR, "Failure", errMsg.Error()) + } + logger.Info("Subnet Created") + } else { + if apiSubnet, ok := r.NStorage.SubnetsStorage.FindByID(subnetMeta.Spec.ID); ok { + debugLogger.Info("Comparing SubnetMeta with Netris Subnet") + if ok := compareSubnetMetaAPIESubnet(subnetMeta, apiSubnet, u); ok { + debugLogger.Info("Nothing Changed") + } else { + debugLogger.Info("Go to update Subnet in Netris") + logger.Info("Updating Subnet") + subnetUpdate, err := SubnetMetaToNetrisUpdate(subnetMeta) + if err != nil { + logger.Error(fmt.Errorf("{SubnetMetaToNetrisUpdate} %s", err), "") + return u.patchSubnetStatus(subnetCR, "Failure", err.Error()) + } + + js, _ := json.Marshal(subnetUpdate) + debugLogger.Info("subnetUpdate", "payload", string(js)) + + _, err, errMsg := updateSubnet(subnetMeta.Spec.ID, subnetUpdate, r.Cred) + if err != nil { + logger.Error(fmt.Errorf("{updateSubnet} %s", err), "") + return u.patchSubnetStatus(subnetCR, "Failure", errMsg.Error()) + } + logger.Info("Subnet Updated") + } + } else { + debugLogger.Info("Subnet not found in Netris") + debugLogger.Info("Going to create Subnet") + logger.Info("Creating Subnet") + if _, err, errMsg := r.createSubnet(subnetMeta); err != nil { + logger.Error(fmt.Errorf("{createSubnet} %s", err), "") + return u.patchSubnetStatus(subnetCR, "Failure", errMsg.Error()) + } + logger.Info("Subnet Created") + } + } + return u.patchSubnetStatus(subnetCR, provisionState, "Success") +} + +func (r *SubnetMetaReconciler) createSubnet(subnetMeta *k8sv1alpha1.SubnetMeta) (ctrl.Result, error, error) { + debugLogger := r.Log.WithValues( + "name", fmt.Sprintf("%s/%s", subnetMeta.Namespace, subnetMeta.Spec.SubnetName), + "subnetName", subnetMeta.Spec.SubnetCRGeneration, + ).V(int(zapcore.WarnLevel)) + + subnetAdd, err := SubnetMetaToNetris(subnetMeta) + if err != nil { + return ctrl.Result{}, err, err + } + + js, _ := json.Marshal(subnetAdd) + debugLogger.Info("subnetToAdd", "payload", string(js)) + + reply, err := r.Cred.IPAM().AddSubnet(subnetAdd) + if err != nil { + return ctrl.Result{}, err, err + } + + idStruct := struct { + ID int `json:"id"` + }{} + + data, err := reply.Parse() + if err != nil { + return ctrl.Result{}, err, err + } + + if reply.StatusCode != 200 { + return ctrl.Result{}, fmt.Errorf(data.Message), fmt.Errorf(data.Message) + } + + idStruct.ID = int(data.Data.(map[string]interface{})["id"].(float64)) + + debugLogger.Info("Subnet Created", "id", idStruct.ID) + + subnetMeta.Spec.ID = idStruct.ID + + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + err = r.Patch(ctx, subnetMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) // requeue + if err != nil { + return ctrl.Result{}, err, err + } + + debugLogger.Info("ID patched to meta", "id", idStruct.ID) + return ctrl.Result{}, nil, nil +} + +func updateSubnet(id int, subnet *ipam.Subnet, cred *api.Clientset) (ctrl.Result, error, error) { + reply, err := cred.IPAM().UpdateSubnet(id, subnet) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{updateSubnet} %s", err), err + } + resp, err := http.ParseAPIResponse(reply.Data) + if err != nil { + return ctrl.Result{}, err, err + } + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf("{updateSubnet} %s", fmt.Errorf(resp.Message)), fmt.Errorf(resp.Message) + } + + return ctrl.Result{}, nil, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SubnetMetaReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&k8sv1alpha1.SubnetMeta{}). + Complete(r) +} diff --git a/controllers/vnet_controller.go b/controllers/vnet_controller.go index 7462abe..58142a6 100644 --- a/controllers/vnet_controller.go +++ b/controllers/vnet_controller.go @@ -144,7 +144,7 @@ func (r *VNetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } else { debugLogger.Info("Meta not found") if vnet.GetFinalizers() == nil { - vnet.SetFinalizers([]string{"vnet.k8s.netris.ai/delete"}) + vnet.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) vnetPatchCtx, vnetPatchCancel := context.WithTimeout(cntxt, contextTimeout) defer vnetPatchCancel() err := r.Patch(vnetPatchCtx, vnet.DeepCopyObject(), client.Merge, &client.PatchOptions{}) diff --git a/go.mod b/go.mod index 504a3c9..704d02d 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,73 @@ module github.com/netrisai/netris-operator -go 1.16 +go 1.17 require ( github.com/go-logr/logr v0.1.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/kr/text v0.2.0 // indirect github.com/netrisai/netriswebapi v0.0.0-20220125103148-df073aedbe0f github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 github.com/r3labs/diff/v2 v2.9.1 github.com/sirupsen/logrus v1.8.1 go.uber.org/zap v1.10.0 - golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.3.0 k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 ) + +require ( + cloud.google.com/go v0.38.0 // indirect + github.com/beorn7/perks v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/evanphx/json-patch v4.9.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-logr/zapr v0.1.0 // indirect + github.com/gogo/protobuf v1.3.1 // indirect + github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect + github.com/golang/protobuf v1.4.2 // indirect + github.com/google/go-cmp v0.4.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/googleapis/gnostic v0.3.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/imdario/mergo v0.3.9 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/nxadm/tail v1.4.4 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/prometheus/client_golang v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.4.1 // indirect + github.com/prometheus/procfs v0.0.11 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + go.uber.org/atomic v1.4.0 // indirect + go.uber.org/multierr v1.1.0 // indirect + golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect + golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 // indirect + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect + golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 // indirect + golang.org/x/text v0.3.3 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + gomodules.xyz/jsonpatch/v2 v2.0.1 // indirect + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/protobuf v1.23.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + k8s.io/apiextensions-apiserver v0.18.6 // indirect + k8s.io/klog v1.0.0 // indirect + k8s.io/klog/v2 v2.0.0 // indirect + k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 // indirect + k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 // indirect + sigs.k8s.io/structured-merge-diff/v3 v3.0.0 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/main.go b/main.go index 5ea186d..266a4bc 100644 --- a/main.go +++ b/main.go @@ -205,6 +205,26 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "AllocationMeta") os.Exit(1) } + if err = (&controllers.SubnetReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("Subnet"), + Scheme: mgr.GetScheme(), + Cred: cred, + NStorage: nStorage, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Subnet") + os.Exit(1) + } + if err = (&controllers.SubnetMetaReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("SubnetMeta"), + Scheme: mgr.GetScheme(), + Cred: cred, + NStorage: nStorage, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SubnetMeta") + os.Exit(1) + } // +kubebuilder:scaffold:builder watcherLogLevel := "info" diff --git a/netrisstorage/subnets.go b/netrisstorage/subnets.go index 22fba2f..5fb468e 100644 --- a/netrisstorage/subnets.go +++ b/netrisstorage/subnets.go @@ -63,6 +63,21 @@ func (p *SubnetsStorage) findByName(name string) (*ipam.IPAM, bool) { for _, item := range p.Subnets { if item.Name == name { return item, true + } else { + if s, ok := p.findByNameInChildren(item, name); ok { + return s, true + } + } + } + return nil, false +} + +func (p *SubnetsStorage) findByNameInChildren(ipam *ipam.IPAM, name string) (*ipam.IPAM, bool) { + for _, item := range ipam.Children { + if item.Name == name { + return item, true + } else { + return p.findByNameInChildren(item, name) } } return nil, false @@ -80,10 +95,25 @@ func (p *SubnetsStorage) FindByID(id int) (*ipam.IPAM, bool) { return item, ok } +func (p *SubnetsStorage) findInChildren(ipam *ipam.IPAM, id int) (*ipam.IPAM, bool) { + for _, item := range ipam.Children { + if item.ID == id { + return item, true + } else { + return p.findInChildren(item, id) + } + } + return nil, false +} + func (p *SubnetsStorage) findByID(id int) (*ipam.IPAM, bool) { for _, item := range p.Subnets { if item.ID == id { return item, true + } else { + if s, ok := p.findInChildren(item, id); ok { + return s, true + } } } return nil, false diff --git a/samples/README.md b/samples/README.md index f22d800..06f9209 100644 --- a/samples/README.md +++ b/samples/README.md @@ -44,6 +44,31 @@ Ref | Attribute | Default | Description [2] | tenant | "" | Users of this tenant will be permitted to manage subnets under this allocation. +### Subnet Attributes +``` +apiVersion: k8s.netris.ai/v1alpha1 +kind: Subnet +metadata: + name: my-subnet +spec: + prefix: 192.0.2.0/24 # [1] + tenant: Admin # [2] + purpose: management # [3] + defaultGateway: 192.0.2.1 # [4] optional + sites: # [5] + - santa-clara + +``` + +Ref | Attribute | Default | Description +----| -------------------------------------- | ----------- | ---------------- +[1] | prefix | "" | Subnet ipv4/ipv6 prefix. +[2] | tenant | "" | Users of this tenant will be permitted to edit this subnet. +[3] | purpose | "" | Describes which kind of service will be able to use this subnet. Possible values: `common`, `loopback`, `management`, `load-balancer`, `nat`, `inactive`. +[4] | defaultGateway | "" | Optional. Use when purpose is set to `management`. +[5] | sites | [] | List of sites where this subnet is available. + + ### VNet Attributes ``` diff --git a/samples/subnet.yaml b/samples/subnet.yaml new file mode 100644 index 0000000..dd0dbb2 --- /dev/null +++ b/samples/subnet.yaml @@ -0,0 +1,10 @@ +apiVersion: k8s.netris.ai/v1alpha1 +kind: Subnet +metadata: + name: my-subnet +spec: + prefix: 192.0.2.0/24 + tenant: Admin + purpose: common + sites: + - santa-clara