diff --git a/.github/workflows/crdgen.yaml b/.github/workflows/crdgen.yaml index 4e5e2ad0af..9885ad64f0 100644 --- a/.github/workflows/crdgen.yaml +++ b/.github/workflows/crdgen.yaml @@ -26,6 +26,8 @@ jobs: run: make -C crd/nodenetworkconfig - name: Regenerate MultitenantNetworkContainer CRD run: make -C crd/multitenantnetworkcontainer + - name: Regenerate PodNetwork CRD + run: make -C crd/external/podnetwork - name: Regenerate NodeInfo CRD run: make -C crd/nodeinfo - name: Regenerate MultitenantPodNetworkConfig CRD diff --git a/crd/external/podnetwork/Makefile b/crd/external/podnetwork/Makefile new file mode 100644 index 0000000000..6b91fd5c9a --- /dev/null +++ b/crd/external/podnetwork/Makefile @@ -0,0 +1,19 @@ +.DEFAULT_GOAL: all + +REPO_ROOT = $(shell git rev-parse --show-toplevel) +TOOLS_DIR = $(REPO_ROOT)/build/tools +TOOLS_BIN_DIR = $(REPO_ROOT)/build/tools/bin +CONTROLLER_GEN = $(TOOLS_BIN_DIR)/controller-gen + +all: generate manifests + +generate: $(CONTROLLER_GEN) + $(CONTROLLER_GEN) object paths="./..." + +.PHONY: manifests +manifests: $(CONTROLLER_GEN) + mkdir -p manifests + $(CONTROLLER_GEN) crd paths="./..." output:crd:artifacts:config=manifests/ + +$(CONTROLLER_GEN): + @make -C $(REPO_ROOT) $(CONTROLLER_GEN) diff --git a/crd/external/podnetwork/README.md b/crd/external/podnetwork/README.md new file mode 100644 index 0000000000..986f283759 --- /dev/null +++ b/crd/external/podnetwork/README.md @@ -0,0 +1,6 @@ +# PodNetwork CRDs + +This CRD is added to enable VNET multitenancy – which will be watched and managed by the control plane. + +PodNetwork objects need to be created by Orchestrator in the subnet delegation flow. +These represent a Cx subnet already delegated by the customer to the Orchestrator and locked with a Service Association Link (SAL) on network RP. diff --git a/crd/external/podnetwork/api/v1alpha1/groupversion_info.go b/crd/external/podnetwork/api/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000..29d54de7b1 --- /dev/null +++ b/crd/external/podnetwork/api/v1alpha1/groupversion_info.go @@ -0,0 +1,23 @@ +//go:build !ignore_uncovered +// +build !ignore_uncovered + +// Package v1alpha1 contains API Schema definitions for the networking v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=acn.azure.com +package v1alpha1 + +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: "acn.azure.com", Version: "v1alpha1"} + + // 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/crd/external/podnetwork/api/v1alpha1/podnetwork.go b/crd/external/podnetwork/api/v1alpha1/podnetwork.go new file mode 100644 index 0000000000..273bdfcbc7 --- /dev/null +++ b/crd/external/podnetwork/api/v1alpha1/podnetwork.go @@ -0,0 +1,67 @@ +//go:build !ignore_uncovered +// +build !ignore_uncovered + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Important: Run "make" to regenerate code after modifying this file + +// +kubebuilder:object:root=true + +// PodNetwork is the Schema for the PodNetworks API +// +kubebuilder:resource:scope=Namespaced +// +kubebuilder:resource:shortName=pn +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,priority=1,JSONPath=`.status.status` +// +kubebuilder:printcolumn:name="Address Prefixes",type=string,priority=1,JSONPath=`.status.addressPrefixes` +// +kubebuilder:printcolumn:name="Network",type=string,priority=1,JSONPath=`.spec.vnetGUID` +// +kubebuilder:printcolumn:name="Subnet",type=string,priority=1,JSONPath=`.spec.subnetResourceID` +type PodNetwork struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PodNetworkSpec `json:"spec,omitempty"` + Status PodNetworkStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PodNetworkList contains a list of PodNetwork +type PodNetworkList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PodNetwork `json:"items"` +} + +// PodNetworkSpec defines the desired state of PodNetwork +type PodNetworkSpec struct { + // +kubebuilder:validation:Optional + // customer vnet guid + VnetGUID string `json:"vnetGUID,omitempty"` + // customer subnet id + SubnetResourceID string `json:"subnetResourceID,omitempty"` +} + +// Status indicates the status of PN +// +kubebuilder:validation:Enum=Ready;InUse;SubnetNotDelegated +type Status string + +const ( + Ready Status = "Ready" + InUse Status = "InUse" + SubnetNotDelegated Status = "SubnetNotDelegated" +) + +// PodNetworkStatus defines the observed state of PodNetwork +type PodNetworkStatus struct { + // +kubebuilder:validation:Optional + Status Status `json:"status,omitempty"` + AddressPrefixes []string `json:"addressPrefixes,omitempty"` +} + +func init() { + SchemeBuilder.Register(&PodNetwork{}, &PodNetworkList{}) +} diff --git a/crd/external/podnetwork/api/v1alpha1/zz_generated.deepcopy.go b/crd/external/podnetwork/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..d8e0d8d270 --- /dev/null +++ b/crd/external/podnetwork/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,104 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +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 *PodNetwork) DeepCopyInto(out *PodNetwork) { + *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 PodNetwork. +func (in *PodNetwork) DeepCopy() *PodNetwork { + if in == nil { + return nil + } + out := new(PodNetwork) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodNetwork) 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 *PodNetworkList) DeepCopyInto(out *PodNetworkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PodNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNetworkList. +func (in *PodNetworkList) DeepCopy() *PodNetworkList { + if in == nil { + return nil + } + out := new(PodNetworkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodNetworkList) 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 *PodNetworkSpec) DeepCopyInto(out *PodNetworkSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNetworkSpec. +func (in *PodNetworkSpec) DeepCopy() *PodNetworkSpec { + if in == nil { + return nil + } + out := new(PodNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNetworkStatus) DeepCopyInto(out *PodNetworkStatus) { + *out = *in + if in.AddressPrefixes != nil { + in, out := &in.AddressPrefixes, &out.AddressPrefixes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNetworkStatus. +func (in *PodNetworkStatus) DeepCopy() *PodNetworkStatus { + if in == nil { + return nil + } + out := new(PodNetworkStatus) + in.DeepCopyInto(out) + return out +} diff --git a/crd/external/podnetwork/client.go b/crd/external/podnetwork/client.go new file mode 100644 index 0000000000..679cb8b351 --- /dev/null +++ b/crd/external/podnetwork/client.go @@ -0,0 +1,84 @@ +package podnetwork + +import ( + "context" + "reflect" + + "github.com/Azure/azure-container-networking/crd" + "github.com/Azure/azure-container-networking/crd/external/podnetwork/api/v1alpha1" + "github.com/pkg/errors" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + typedv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" +) + +// Scheme is a runtime scheme containing the client-go scheme and the PodNetwork scheme. +var Scheme = runtime.NewScheme() + +func init() { + _ = scheme.AddToScheme(Scheme) + _ = v1alpha1.AddToScheme(Scheme) +} + +// Installer provides methods to manage the lifecycle of the PodNetwork resource definition. +type Installer struct { + cli typedv1.CustomResourceDefinitionInterface +} + +func NewInstaller(c *rest.Config) (*Installer, error) { + cli, err := crd.NewCRDClientFromConfig(c) + if err != nil { + return nil, errors.Wrap(err, "failed to init crd client") + } + return &Installer{ + cli: cli, + }, nil +} + +func (i *Installer) create(ctx context.Context, res *v1.CustomResourceDefinition) (*v1.CustomResourceDefinition, error) { + res, err := i.cli.Create(ctx, res, metav1.CreateOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to create podnetwork crd") + } + return res, nil +} + +// Install installs the embedded PodNetwork CRD definition in the cluster. +func (i *Installer) Install(ctx context.Context) (*v1.CustomResourceDefinition, error) { + podnetwork, err := GetPodNetworks() + if err != nil { + return nil, errors.Wrap(err, "failed to get embedded podnetwork crd") + } + return i.create(ctx, podnetwork) +} + +// InstallOrUpdate installs the embedded PodNetwork CRD definition in the cluster or updates it if present. +func (i *Installer) InstallOrUpdate(ctx context.Context) (*v1.CustomResourceDefinition, error) { + podNetwork, err := GetPodNetworks() + if err != nil { + return nil, errors.Wrap(err, "failed to get embedded podnetwork crd") + } + current, err := i.create(ctx, podNetwork) + if !apierrors.IsAlreadyExists(err) { + return current, err + } + if current == nil { + current, err = i.cli.Get(ctx, podNetwork.Name, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to get existing podnetwork crd") + } + } + if !reflect.DeepEqual(podNetwork.Spec.Versions, current.Spec.Versions) { + podNetwork.SetResourceVersion(current.GetResourceVersion()) + previous := *current + current, err = i.cli.Update(ctx, podNetwork, metav1.UpdateOptions{}) + if err != nil { + return &previous, errors.Wrap(err, "failed to update existing podnetwork crd") + } + } + return current, nil +} diff --git a/crd/external/podnetwork/embed.go b/crd/external/podnetwork/embed.go new file mode 100644 index 0000000000..fedbd4ee40 --- /dev/null +++ b/crd/external/podnetwork/embed.go @@ -0,0 +1,24 @@ +package podnetwork + +import ( + _ "embed" + + "github.com/pkg/errors" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/yaml" +) + +// PodNetworkYAML embeds the CRD YAML for downstream consumers. +// +//go:embed manifests/acn.azure.com_podnetworks.yaml +var PodNetworkYAML []byte + +// GetPodNetworks parses the raw []byte PodNetwork in +// to a CustomResourceDefinition and returns it or an unmarshalling error. +func GetPodNetworks() (*apiextensionsv1.CustomResourceDefinition, error) { + podNetworks := &apiextensionsv1.CustomResourceDefinition{} + if err := yaml.Unmarshal(PodNetworkYAML, &podNetworks); err != nil { + return nil, errors.Wrap(err, "error unmarshalling embedded PodNetwork") + } + return podNetworks, nil +} diff --git a/crd/external/podnetwork/embed_test.go b/crd/external/podnetwork/embed_test.go new file mode 100644 index 0000000000..3d36147d25 --- /dev/null +++ b/crd/external/podnetwork/embed_test.go @@ -0,0 +1,21 @@ +package podnetwork + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +const filename = "manifests/acn.azure.com_podnetworks.yaml" + +func TestEmbed(t *testing.T) { + b, err := os.ReadFile(filename) + assert.NoError(t, err) + assert.Equal(t, b, PodNetworkYAML) +} + +func TestGetPodNetworks(t *testing.T) { + _, err := GetPodNetworks() + assert.NoError(t, err) +} diff --git a/crd/external/podnetwork/manifests/acn.azure.com_podnetworks.yaml b/crd/external/podnetwork/manifests/acn.azure.com_podnetworks.yaml new file mode 100644 index 0000000000..7e17cf65ad --- /dev/null +++ b/crd/external/podnetwork/manifests/acn.azure.com_podnetworks.yaml @@ -0,0 +1,82 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: podnetworks.acn.azure.com +spec: + group: acn.azure.com + names: + kind: PodNetwork + listKind: PodNetworkList + plural: podnetworks + shortNames: + - pn + singular: podnetwork + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + priority: 1 + type: string + - jsonPath: .status.addressPrefixes + name: Address Prefixes + priority: 1 + type: string + - jsonPath: .spec.vnetGUID + name: Network + priority: 1 + type: string + - jsonPath: .spec.subnetResourceID + name: Subnet + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: PodNetwork is the Schema for the PodNetworks 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: PodNetworkSpec defines the desired state of PodNetwork + properties: + subnetResourceID: + description: customer subnet id + type: string + vnetGUID: + description: customer vnet guid + type: string + type: object + status: + description: PodNetworkStatus defines the observed state of PodNetwork + properties: + addressPrefixes: + items: + type: string + type: array + status: + description: Status indicates the status of PN + enum: + - Ready + - InUse + - SubnetNotDelegated + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/crd/external/podnetwork/manifests/doc.go b/crd/external/podnetwork/manifests/doc.go new file mode 100644 index 0000000000..b08acc397f --- /dev/null +++ b/crd/external/podnetwork/manifests/doc.go @@ -0,0 +1,3 @@ +// Package manifests exists to allow the rendered CRD manifests to be +// packaged in to dependent components. +package manifests