diff --git a/.github/workflows/crdgen.yaml b/.github/workflows/crdgen.yaml index ebd8f6d91f..4e5e2ad0af 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 NodeInfo CRD + run: make -C crd/nodeinfo - name: Regenerate MultitenantPodNetworkConfig CRD run: make -C crd/multitenantpodnetworkconfig - name: Fail if the tree is dirty diff --git a/crd/nodeinfo/Makefile b/crd/nodeinfo/Makefile new file mode 100644 index 0000000000..c1fd004a43 --- /dev/null +++ b/crd/nodeinfo/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/nodeinfo/README.md b/crd/nodeinfo/README.md new file mode 100644 index 0000000000..34d6e16827 --- /dev/null +++ b/crd/nodeinfo/README.md @@ -0,0 +1,5 @@ +# NodeInfo CRDs + +This CRD is added to enable VNET multitenancy – which will be watched and managed by the control plane. + +NodeInfo objects are created by CNS as part of the node registration flow, and is used to pass any metadata from the VM needed by control plane. E.g.: vmUniqueID etc diff --git a/crd/nodeinfo/api/v1alpha1/groupversion_info.go b/crd/nodeinfo/api/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000..88c30b03ed --- /dev/null +++ b/crd/nodeinfo/api/v1alpha1/groupversion_info.go @@ -0,0 +1,23 @@ +//go:build !ignore_uncovered +// +build !ignore_uncovered + +// Package v1alpha1 contains API Schema definitions for the acn 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/nodeinfo/api/v1alpha1/nodeinfo.go b/crd/nodeinfo/api/v1alpha1/nodeinfo.go new file mode 100644 index 0000000000..58701fabf7 --- /dev/null +++ b/crd/nodeinfo/api/v1alpha1/nodeinfo.go @@ -0,0 +1,43 @@ +//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 + +// NodeInfo is the Schema for the NodeInfo API +// +kubebuilder:resource:scope=Namespaced +// +kubebuilder:resource:shortName=ni +// +kubebuilder:resource:path=nodeinfo +// +kubebuilder:printcolumn:name="VMUniqueID",type=string,priority=0,JSONPath=`.spec.vmUniqueID` +type NodeInfo struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NodeInfoSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// NodeInfoList contains a list of NodeInfo +type NodeInfoList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NodeInfo `json:"items"` +} + +// NodeInfoSpec defines the desired state of NodeInfo +type NodeInfoSpec struct { + // +kubebuilder:validation:Optional + VMUniqueID string `json:"vmUniqueID,omitempty"` +} + +func init() { + SchemeBuilder.Register(&NodeInfo{}, &NodeInfoList{}) +} diff --git a/crd/nodeinfo/api/v1alpha1/zz_generated.deepcopy.go b/crd/nodeinfo/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..91563a78cb --- /dev/null +++ b/crd/nodeinfo/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,83 @@ +//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 *NodeInfo) DeepCopyInto(out *NodeInfo) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeInfo. +func (in *NodeInfo) DeepCopy() *NodeInfo { + if in == nil { + return nil + } + out := new(NodeInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeInfo) 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 *NodeInfoList) DeepCopyInto(out *NodeInfoList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NodeInfo, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeInfoList. +func (in *NodeInfoList) DeepCopy() *NodeInfoList { + if in == nil { + return nil + } + out := new(NodeInfoList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeInfoList) 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 *NodeInfoSpec) DeepCopyInto(out *NodeInfoSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeInfoSpec. +func (in *NodeInfoSpec) DeepCopy() *NodeInfoSpec { + if in == nil { + return nil + } + out := new(NodeInfoSpec) + in.DeepCopyInto(out) + return out +} diff --git a/crd/nodeinfo/client.go b/crd/nodeinfo/client.go new file mode 100644 index 0000000000..cb6314fc16 --- /dev/null +++ b/crd/nodeinfo/client.go @@ -0,0 +1,84 @@ +package nodeinfo + +import ( + "context" + "reflect" + + "github.com/Azure/azure-container-networking/crd" + "github.com/Azure/azure-container-networking/crd/nodeinfo/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 NodeInfo scheme. +var Scheme = runtime.NewScheme() + +func init() { + _ = scheme.AddToScheme(Scheme) + _ = v1alpha1.AddToScheme(Scheme) +} + +// Installer provides methods to manage the lifecycle of the NodeInfo 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 nodeinfo crd") + } + return res, nil +} + +// Install installs the embedded NodeInfo CRD definition in the cluster. +func (i *Installer) Install(ctx context.Context) (*v1.CustomResourceDefinition, error) { + nodeinfo, err := GetNodeInfo() + if err != nil { + return nil, errors.Wrap(err, "failed to get embedded nodeinfo crd") + } + return i.create(ctx, nodeinfo) +} + +// InstallOrUpdate installs the embedded NodeInfo CRD definition in the cluster or updates it if present. +func (i *Installer) InstallOrUpdate(ctx context.Context) (*v1.CustomResourceDefinition, error) { + nodeinfo, err := GetNodeInfo() + if err != nil { + return nil, errors.Wrap(err, "failed to get embedded nodeinfo crd") + } + current, err := i.create(ctx, nodeinfo) + if !apierrors.IsAlreadyExists(err) { + return current, err + } + if current == nil { + current, err = i.cli.Get(ctx, nodeinfo.Name, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to get existing nodeinfo crd") + } + } + if !reflect.DeepEqual(nodeinfo.Spec.Versions, current.Spec.Versions) { + nodeinfo.SetResourceVersion(current.GetResourceVersion()) + previous := *current + current, err = i.cli.Update(ctx, nodeinfo, metav1.UpdateOptions{}) + if err != nil { + return &previous, errors.Wrap(err, "failed to update existing nodeinfo crd") + } + } + return current, nil +} diff --git a/crd/nodeinfo/embed.go b/crd/nodeinfo/embed.go new file mode 100644 index 0000000000..259602bb57 --- /dev/null +++ b/crd/nodeinfo/embed.go @@ -0,0 +1,24 @@ +package nodeinfo + +import ( + _ "embed" + + "github.com/pkg/errors" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/yaml" +) + +// NodeInfoYAML embeds the CRD YAML for downstream consumers. +// +//go:embed manifests/acn.azure.com_nodeinfo.yaml +var NodeInfoYAML []byte + +// GetNodeInfo parses the raw []byte NodeInfo in +// to a CustomResourceDefinition and returns it or an unmarshalling error. +func GetNodeInfo() (*apiextensionsv1.CustomResourceDefinition, error) { + nodeInfo := &apiextensionsv1.CustomResourceDefinition{} + if err := yaml.Unmarshal(NodeInfoYAML, &nodeInfo); err != nil { + return nil, errors.Wrap(err, "error unmarshalling embedded nodeInfo") + } + return nodeInfo, nil +} diff --git a/crd/nodeinfo/embed_test.go b/crd/nodeinfo/embed_test.go new file mode 100644 index 0000000000..ed42924962 --- /dev/null +++ b/crd/nodeinfo/embed_test.go @@ -0,0 +1,21 @@ +package nodeinfo + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +const filename = "manifests/acn.azure.com_nodeinfo.yaml" + +func TestEmbed(t *testing.T) { + b, err := os.ReadFile(filename) + assert.NoError(t, err) + assert.Equal(t, b, NodeInfoYAML) +} + +func TestGetNodeInfo(t *testing.T) { + _, err := GetNodeInfo() + assert.NoError(t, err) +} diff --git a/crd/nodeinfo/manifests/acn.azure.com_nodeinfo.yaml b/crd/nodeinfo/manifests/acn.azure.com_nodeinfo.yaml new file mode 100644 index 0000000000..9718299055 --- /dev/null +++ b/crd/nodeinfo/manifests/acn.azure.com_nodeinfo.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: nodeinfo.acn.azure.com +spec: + group: acn.azure.com + names: + kind: NodeInfo + listKind: NodeInfoList + plural: nodeinfo + singular: nodeinfo + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.vmUniqueID + name: VMUniqueID + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: NodeInfo is the Schema for the NodeInfo 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: NodeInfoSpec defines the desired state of NodeInfo + properties: + vmUniqueID: + type: string + type: object + type: object + served: true + storage: true + subresources: {} diff --git a/crd/nodeinfo/manifests/doc.go b/crd/nodeinfo/manifests/doc.go new file mode 100644 index 0000000000..b08acc397f --- /dev/null +++ b/crd/nodeinfo/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