From 74bb7707b497d68e58398923e8005277fbd4d938 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Thu, 24 Jun 2021 18:44:59 +0800 Subject: [PATCH 1/2] Support NAS Mount Target Implemented managed resource NASMountTarge Signed-off-by: Zheng Xi Zhou --- apis/nas/v1alpha1/nas_fs_types.go | 2 +- apis/nas/v1alpha1/nas_mount-target_types.go | 75 ++++++ apis/nas/v1alpha1/register.go | 12 + apis/nas/v1alpha1/zz_generated.deepcopy.go | 158 ++++++++++++ apis/nas/v1alpha1/zz_generated.managed.go | 56 +++++ apis/nas/v1alpha1/zz_generated.managedlist.go | 9 + examples/nas/nas-mount-target.yaml | 15 ++ ....alibaba.crossplane.io_nasfilesystems.yaml | 2 + ...alibaba.crossplane.io_nasmounttargets.yaml | 159 ++++++++++++ pkg/clients/nas/nas.go | 92 +++++++ pkg/controller/alibaba.go | 1 + pkg/controller/nas/mountpoint_controller.go | 196 +++++++++++++++ .../nas/mountpoint_controller_test.go | 231 ++++++++++++++++++ pkg/util/endpoint.go | 2 +- 14 files changed, 1008 insertions(+), 2 deletions(-) create mode 100644 apis/nas/v1alpha1/nas_mount-target_types.go create mode 100644 examples/nas/nas-mount-target.yaml create mode 100644 package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml create mode 100644 pkg/controller/nas/mountpoint_controller.go create mode 100644 pkg/controller/nas/mountpoint_controller_test.go diff --git a/apis/nas/v1alpha1/nas_fs_types.go b/apis/nas/v1alpha1/nas_fs_types.go index cc3a541..9ce77eb 100644 --- a/apis/nas/v1alpha1/nas_fs_types.go +++ b/apis/nas/v1alpha1/nas_fs_types.go @@ -38,7 +38,7 @@ type NASFileSystemList struct { // +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:subresource:status -// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,alibaba} +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,alibaba},shortName=nasfs type NASFileSystem struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/apis/nas/v1alpha1/nas_mount-target_types.go b/apis/nas/v1alpha1/nas_mount-target_types.go new file mode 100644 index 0000000..cd1ff6b --- /dev/null +++ b/apis/nas/v1alpha1/nas_mount-target_types.go @@ -0,0 +1,75 @@ +/* +Copyright 2021 The Crossplane Authors. + +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 ( + runtimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true + +// NASMountTargetList contains a list of NASMountTarget +type NASMountTargetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NASMountTarget `json:"items"` +} + +// +kubebuilder:object:root=true + +// NASMountTarget is a managed resource that represents an NASMountTarget instance +// +kubebuilder:printcolumn:name="FILE-SYSTEM-ID",type="string",JSONPath=".spec.atProvider.fileSystemID" +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,alibaba},shortName=nasmt +type NASMountTarget struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NASMountTargetSpec `json:"spec,omitempty"` + Status NASMountTargetStatus `json:"status,omitempty"` +} + +// NASMountTargetSpec defines the desired state of NASMountTarget +type NASMountTargetSpec struct { + runtimev1.ResourceSpec `json:",inline"` + ForProvider NASMountTargetParameter `json:"forProvider"` +} + +// NASMountTargetStatus defines the observed state of NASMountTarget +type NASMountTargetStatus struct { + runtimev1.ResourceStatus `json:",inline"` + AtProvider NASMountTargetObservation `json:"atProvider,omitempty"` +} + +// NASMountTargetParameter is the isolated place to store files +type NASMountTargetParameter struct { + FileSystemID *string `json:"fileSystemID"` + AccessGroupName *string `json:"accessGroupName,omitempty"` + NetworkType *string `json:"networkType"` + VpcID *string `json:"vpcId,omitempty"` + VSwitchID *string `json:"vSwitchId,omitempty"` + SecurityGroupID *string `json:"securityGroupId,omitempty"` +} + +// NASMountTargetObservation is the representation of the current state that is observed. +type NASMountTargetObservation struct { + MountTargetDomain *string `json:"mountTargetDomain,omitempty"` +} diff --git a/apis/nas/v1alpha1/register.go b/apis/nas/v1alpha1/register.go index 88a1d7e..c8fe8bc 100644 --- a/apis/nas/v1alpha1/register.go +++ b/apis/nas/v1alpha1/register.go @@ -46,6 +46,18 @@ var ( NASFileSystemGroupVersionKind = GroupVersion.WithKind(NASFileSystemKind) ) +var ( + // NASMountTargetKind is the kind of NASMountTarget resource type + NASMountTargetKind = reflect.TypeOf(NASMountTarget{}).Name() + // NASMountTargetGroupKind is the group and kind information of NASMountTarget resource type + NASMountTargetGroupKind = schema.GroupKind{Group: GroupVersion.Group, Kind: NASMountTargetKind}.String() + // NASMountTargetKindAPIVersion is the kind and apiversion of NASMountTarget resource type + NASMountTargetKindAPIVersion = NASMountTargetKind + "." + GroupVersion.String() + // NASMountTargetGroupVersionKind is the GVK of NASMountTarget resource type + NASMountTargetGroupVersionKind = GroupVersion.WithKind(NASMountTargetKind) +) + func init() { SchemeBuilder.Register(&NASFileSystem{}, &NASFileSystemList{}) + SchemeBuilder.Register(&NASMountTarget{}, &NASMountTargetList{}) } diff --git a/apis/nas/v1alpha1/zz_generated.deepcopy.go b/apis/nas/v1alpha1/zz_generated.deepcopy.go index 48a8d38..40a5f60 100644 --- a/apis/nas/v1alpha1/zz_generated.deepcopy.go +++ b/apis/nas/v1alpha1/zz_generated.deepcopy.go @@ -176,3 +176,161 @@ func (in *NASFileSystemStatus) DeepCopy() *NASFileSystemStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NASMountTarget) DeepCopyInto(out *NASMountTarget) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTarget. +func (in *NASMountTarget) DeepCopy() *NASMountTarget { + if in == nil { + return nil + } + out := new(NASMountTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NASMountTarget) 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 *NASMountTargetList) DeepCopyInto(out *NASMountTargetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NASMountTarget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTargetList. +func (in *NASMountTargetList) DeepCopy() *NASMountTargetList { + if in == nil { + return nil + } + out := new(NASMountTargetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NASMountTargetList) 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 *NASMountTargetObservation) DeepCopyInto(out *NASMountTargetObservation) { + *out = *in + if in.MountTargetDomain != nil { + in, out := &in.MountTargetDomain, &out.MountTargetDomain + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTargetObservation. +func (in *NASMountTargetObservation) DeepCopy() *NASMountTargetObservation { + if in == nil { + return nil + } + out := new(NASMountTargetObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NASMountTargetParameter) DeepCopyInto(out *NASMountTargetParameter) { + *out = *in + if in.FileSystemID != nil { + in, out := &in.FileSystemID, &out.FileSystemID + *out = new(string) + **out = **in + } + if in.AccessGroupName != nil { + in, out := &in.AccessGroupName, &out.AccessGroupName + *out = new(string) + **out = **in + } + if in.NetworkType != nil { + in, out := &in.NetworkType, &out.NetworkType + *out = new(string) + **out = **in + } + if in.VpcID != nil { + in, out := &in.VpcID, &out.VpcID + *out = new(string) + **out = **in + } + if in.VSwitchID != nil { + in, out := &in.VSwitchID, &out.VSwitchID + *out = new(string) + **out = **in + } + if in.SecurityGroupID != nil { + in, out := &in.SecurityGroupID, &out.SecurityGroupID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTargetParameter. +func (in *NASMountTargetParameter) DeepCopy() *NASMountTargetParameter { + if in == nil { + return nil + } + out := new(NASMountTargetParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NASMountTargetSpec) DeepCopyInto(out *NASMountTargetSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTargetSpec. +func (in *NASMountTargetSpec) DeepCopy() *NASMountTargetSpec { + if in == nil { + return nil + } + out := new(NASMountTargetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NASMountTargetStatus) DeepCopyInto(out *NASMountTargetStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NASMountTargetStatus. +func (in *NASMountTargetStatus) DeepCopy() *NASMountTargetStatus { + if in == nil { + return nil + } + out := new(NASMountTargetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/nas/v1alpha1/zz_generated.managed.go b/apis/nas/v1alpha1/zz_generated.managed.go index 106c6d3..8f50c45 100644 --- a/apis/nas/v1alpha1/zz_generated.managed.go +++ b/apis/nas/v1alpha1/zz_generated.managed.go @@ -75,3 +75,59 @@ func (mg *NASFileSystem) SetProviderReference(r *xpv1.Reference) { func (mg *NASFileSystem) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r } + +// GetCondition of this NASMountTarget. +func (mg *NASMountTarget) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this NASMountTarget. +func (mg *NASMountTarget) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetProviderConfigReference of this NASMountTarget. +func (mg *NASMountTarget) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +/* +GetProviderReference of this NASMountTarget. +Deprecated: Use GetProviderConfigReference. +*/ +func (mg *NASMountTarget) GetProviderReference() *xpv1.Reference { + return mg.Spec.ProviderReference +} + +// GetWriteConnectionSecretToReference of this NASMountTarget. +func (mg *NASMountTarget) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this NASMountTarget. +func (mg *NASMountTarget) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this NASMountTarget. +func (mg *NASMountTarget) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetProviderConfigReference of this NASMountTarget. +func (mg *NASMountTarget) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +/* +SetProviderReference of this NASMountTarget. +Deprecated: Use SetProviderConfigReference. +*/ +func (mg *NASMountTarget) SetProviderReference(r *xpv1.Reference) { + mg.Spec.ProviderReference = r +} + +// SetWriteConnectionSecretToReference of this NASMountTarget. +func (mg *NASMountTarget) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/nas/v1alpha1/zz_generated.managedlist.go b/apis/nas/v1alpha1/zz_generated.managedlist.go index 66eefbb..ea4f546 100644 --- a/apis/nas/v1alpha1/zz_generated.managedlist.go +++ b/apis/nas/v1alpha1/zz_generated.managedlist.go @@ -28,3 +28,12 @@ func (l *NASFileSystemList) GetItems() []resource.Managed { } return items } + +// GetItems of this NASMountTargetList. +func (l *NASMountTargetList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/examples/nas/nas-mount-target.yaml b/examples/nas/nas-mount-target.yaml new file mode 100644 index 0000000..7848308 --- /dev/null +++ b/examples/nas/nas-mount-target.yaml @@ -0,0 +1,15 @@ +apiVersion: nas.alibaba.crossplane.io/v1alpha1 +kind: NASMountTarget +metadata: + name: na-mount-target-test + namespace: default +spec: + forProvider: + fileSystemID: 130434a20d + accessGroupName: DEFAULT_VPC_GROUP_NAME + networkType: Vpc + vpcId: vpc-2ze75wu7vcrgpddnsri09 + vSwitchId: vsw-2zed2c4a52pg8hmp8fafw + writeConnectionSecretToRef: + name: nas-mount-target + namespace: default diff --git a/package/crds/nas.alibaba.crossplane.io_nasfilesystems.yaml b/package/crds/nas.alibaba.crossplane.io_nasfilesystems.yaml index 3ba37ad..d41c674 100644 --- a/package/crds/nas.alibaba.crossplane.io_nasfilesystems.yaml +++ b/package/crds/nas.alibaba.crossplane.io_nasfilesystems.yaml @@ -15,6 +15,8 @@ spec: kind: NASFileSystem listKind: NASFileSystemList plural: nasfilesystems + shortNames: + - nasfs singular: nasfilesystem scope: Cluster versions: diff --git a/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml b/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml new file mode 100644 index 0000000..1e77929 --- /dev/null +++ b/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml @@ -0,0 +1,159 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: nasmounttargets.nas.alibaba.crossplane.io +spec: + group: nas.alibaba.crossplane.io + names: + categories: + - crossplane + - managed + - alibaba + kind: NASMountTarget + listKind: NASMountTargetList + plural: nasmounttargets + shortNames: + - nasmt + singular: nasmounttarget + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.atProvider.fileSystemID + name: FILE-SYSTEM-ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: NASMountTarget is a managed resource that represents an NASMountTarget instance + 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: NASMountTargetSpec defines the desired state of NASMountTarget + properties: + deletionPolicy: + description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + enum: + - Orphan + - Delete + type: string + forProvider: + description: NASMountTargetParameter is the isolated place to store files + properties: + accessGroupName: + type: string + fileSystemID: + type: string + networkType: + type: string + securityGroupId: + type: string + vSwitchId: + type: string + vpcId: + type: string + required: + - fileSystemID + - networkType + type: object + providerConfigRef: + description: ProviderConfigReference specifies how the provider that will be used to create, observe, update, and delete this managed resource should be configured. + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + providerRef: + description: 'ProviderReference specifies the provider that will be used to create, observe, update, and delete this managed resource. Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`' + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + writeConnectionSecretToRef: + description: WriteConnectionSecretToReference specifies the namespace and name of a Secret to which any connection details for this managed resource should be written. Connection details frequently include the endpoint, username, and password required to connect to the managed resource. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - forProvider + type: object + status: + description: NASMountTargetStatus defines the observed state of NASMountTarget + properties: + atProvider: + description: NASMountTargetObservation is the representation of the current state that is observed. + properties: + mountTargetDomain: + type: string + type: object + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time this condition transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from one status to another. + type: string + status: + description: Status of this condition; is it currently True, False, or Unknown? + type: string + type: + description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/clients/nas/nas.go b/pkg/clients/nas/nas.go index 220e050..bd181ec 100644 --- a/pkg/clients/nas/nas.go +++ b/pkg/clients/nas/nas.go @@ -31,6 +31,7 @@ import ( const ( errFailedToCreateNASClient = "failed to crate NAS client" errCodeFileSystemNotExist = "InvalidFileSystem.NotFound" + errMountTargetNotExisted = "InvalidMountTarget.NotFound" ) // ClientInterface create a client inferface @@ -38,6 +39,10 @@ type ClientInterface interface { DescribeFileSystems(fileSystemID, fileSystemType, vpcID *string) (*sdk.DescribeFileSystemsResponse, error) CreateFileSystem(fs v1alpha1.NASFileSystemParameter) (*sdk.CreateFileSystemResponse, error) DeleteFileSystem(fileSystemID string) error + + DescribeMountTargets(fileSystemID, mountTargetDomain *string) (*sdk.DescribeMountTargetsResponse, error) + CreateMountTarget(fs v1alpha1.NASMountTargetParameter) (*sdk.CreateMountTargetResponse, error) + DeleteMountTarget(fileSystemID, mountTargetDomain *string) error } // SDKClient is the SDK client for NASFileSystem @@ -60,6 +65,8 @@ func NewClient(ctx context.Context, endpoint string, accessKeyID string, accessK return &SDKClient{Client: client}, nil } +// -------------------------------- FileSystem ---------------------------------------------------- + // DescribeFileSystems describes NAS FileSystem func (c *SDKClient) DescribeFileSystems(fileSystemID, fileSystemType, vpcID *string) (*sdk.DescribeFileSystemsResponse, error) { describeFileSystemsRequest := &sdk.DescribeFileSystemsRequest{} @@ -145,3 +152,88 @@ func IsNotFoundError(err error) bool { } return false } + +// -------------------------------- MountTarget ---------------------------------------------------- + +// DescribeMountTargets describes NAS MountTarget +func (c *SDKClient) DescribeMountTargets(fileSystemID, mountTargetDomain *string) (*sdk.DescribeMountTargetsResponse, error) { + describeMountTargetsRequest := &sdk.DescribeMountTargetsRequest{} + if fileSystemID != nil { + describeMountTargetsRequest.FileSystemId = tea.String(*fileSystemID) + } + if mountTargetDomain != nil { + describeMountTargetsRequest.MountTargetDomain = tea.String(*mountTargetDomain) + } + fs, err := c.Client.DescribeMountTargets(describeMountTargetsRequest) + if err != nil { + return nil, err + } + return fs, nil +} + +// CreateMountTarget creates NASMountTarget +func (c *SDKClient) CreateMountTarget(fs v1alpha1.NASMountTargetParameter) (*sdk.CreateMountTargetResponse, error) { + createMountTargetRequest := &sdk.CreateMountTargetRequest{ + FileSystemId: fs.FileSystemID, + AccessGroupName: fs.AccessGroupName, + NetworkType: fs.NetworkType, + VpcId: fs.VpcID, + VSwitchId: fs.VSwitchID, + SecurityGroupId: fs.SecurityGroupID, + } + res, err := c.Client.CreateMountTarget(createMountTargetRequest) + return res, err +} + +// DeleteMountTarget deletes NASMountTarget +func (c *SDKClient) DeleteMountTarget(fileSystemID, mountTargetDomain *string) error { + deleteMountTargetRequest := &sdk.DeleteMountTargetRequest{ + FileSystemId: fileSystemID, + MountTargetDomain: mountTargetDomain, + } + _, err := c.Client.DeleteMountTarget(deleteMountTargetRequest) + return err +} + +// GenerateObservation4MountTarget generates observation information from fileSystem mount point +func GenerateObservation4MountTarget(res *sdk.CreateMountTargetResponse) v1alpha1.NASMountTargetObservation { + return v1alpha1.NASMountTargetObservation{MountTargetDomain: res.Body.MountTargetDomain} +} + +// IsMountTargetUpdateToDate checks whether cr is up to date +func IsMountTargetUpdateToDate(cr *v1alpha1.NASMountTarget, mountTargetResponse *sdk.DescribeMountTargetsResponse) bool { + if *mountTargetResponse.Body.TotalCount == 0 { + return false + } + res := mountTargetResponse.Body.MountTargets.MountTarget[0] + mt := cr.Spec.ForProvider + + if (mt.VpcID == nil && res.VpcId != nil) || (mt.VpcID != nil && res.VpcId == nil) || (*mt.VpcID != *res.VpcId) { + return false + } + + if (mt.VSwitchID == nil && res.VswId != nil) || (mt.VSwitchID != nil && res.VswId == nil) || (*mt.VSwitchID != *res.VswId) { + return false + } + + if (mt.AccessGroupName == nil && res.AccessGroup != nil) || (mt.AccessGroupName != nil && res.AccessGroup == nil) || (*mt.AccessGroupName != *res.AccessGroup) { + return false + } + + if (mt.NetworkType == nil && res.NetworkType != nil) || (mt.NetworkType != nil && res.NetworkType == nil) || (*mt.NetworkType != *res.NetworkType) { + return false + } + + return true +} + +// IsMountTargetNotFoundError helper function to test for SLS project not found error +func IsMountTargetNotFoundError(err error) bool { + if err == nil { + return false + } + if e, ok := errors.Cause(err).(*tea.SDKError); ok && (*e.Code == errMountTargetNotExisted) { + return true + } + return false +} diff --git a/pkg/controller/alibaba.go b/pkg/controller/alibaba.go index ad4c2be..9186fe9 100644 --- a/pkg/controller/alibaba.go +++ b/pkg/controller/alibaba.go @@ -35,6 +35,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger) error { sls.SetupProject, oss.SetupBucket, nas.SetupNASFileSystem, + nas.SetupNASMountTarget, } { if err := setup(mgr, l); err != nil { return err diff --git a/pkg/controller/nas/mountpoint_controller.go b/pkg/controller/nas/mountpoint_controller.go new file mode 100644 index 0000000..9d017d2 --- /dev/null +++ b/pkg/controller/nas/mountpoint_controller.go @@ -0,0 +1,196 @@ +/* +Copyright 2021 The Crossplane Authors. + +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 nas + +import ( + "context" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/provider-alibaba/apis/nas/v1alpha1" + aliv1alpha1 "github.com/crossplane/provider-alibaba/apis/v1alpha1" + nasclient "github.com/crossplane/provider-alibaba/pkg/clients/nas" + "github.com/crossplane/provider-alibaba/pkg/util" +) + +const ( + errFailedToCreateNASMountTarget = "failed to create NAS filesystem" + errFailedToDeleteNASMountTarget = "failed to delete NAS filesystem" + errFailedToDescribeNASMountTarget = "failed to describe NAS filesystem" + errNotNASMountTarget = "managed resource is not a NASMountTarget custom resource" +) + +// SetupNASMountTarget adds a controller that reconciles NASMountTarget. +func SetupNASMountTarget(mgr ctrl.Manager, l logging.Logger) error { + name := managed.ControllerName(v1alpha1.NASMountTargetGroupKind) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&v1alpha1.NASMountTarget{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(v1alpha1.NASMountTargetGroupVersionKind), + managed.WithLogger(l.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + managed.WithExternalConnecter(&mtConnector{ + Client: mgr.GetClient(), + Usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &aliv1alpha1.ProviderConfigUsage{}), + NewClientFn: nasclient.NewClient, + }))) +} + +// mtConnector stores Kubernetes client and NAS client +type mtConnector struct { + Client client.Client + Usage resource.Tracker + NewClientFn func(ctx context.Context, endpoint, accessKeyID, accessKeySecret, stsToken string) (*nasclient.SDKClient, error) +} + +// Connect initials cloud resource client +func (c *mtConnector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*v1alpha1.NASMountTarget) + if !ok { + return nil, errors.New(errNotNASMountTarget) + } + + var ( + secretKeySelector *xpv1.SecretKeySelector + region string + ) + + if err := c.Usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackUsage) + } + + providerConfig, err := util.GetProviderConfig(ctx, c.Client, cr.Spec.ProviderConfigReference.Name) + if err != nil { + return nil, err + } + + if s := providerConfig.Spec.Credentials.Source; s != xpv1.CredentialsSourceSecret { + return nil, errors.Errorf(errFmtUnsupportedCredSource, s) + } + secretKeySelector = providerConfig.Spec.Credentials.SecretRef + region = providerConfig.Spec.Region + + if secretKeySelector == nil { + return nil, errors.New(errNoConnectionSecret) + } + s := &corev1.Secret{} + nn := types.NamespacedName{Namespace: secretKeySelector.Namespace, Name: secretKeySelector.Name} + if err := c.Client.Get(ctx, nn, s); err != nil { + return nil, errors.Wrap(err, errGetConnectionSecret) + } + + endpoint, err := util.GetEndpoint(cr.DeepCopyObject(), region) + if err != nil { + return nil, err + } + + client, err := c.NewClientFn(ctx, endpoint, string(s.Data[util.AccessKeyID]), string(s.Data[util.AccessKeySecret]), string(s.Data[util.SecurityToken])) + return &mountTargetExternal{ExternalClient: client}, errors.Wrap(err, errCreateClient) +} + +// mountTargetExternal includes external NAS client +type mountTargetExternal struct { + ExternalClient nasclient.ClientInterface +} + +// Observe managed resource NAS filesystem +func (e *mountTargetExternal) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*v1alpha1.NASMountTarget) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotNASMountTarget) + } + + domain := cr.Status.AtProvider.MountTargetDomain + if domain == nil { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + + mountTarget, err := e.ExternalClient.DescribeMountTargets(cr.Spec.ForProvider.FileSystemID, domain) + if err != nil { + // Managed resource `NASMountTarget` is special, the identifier of if `name` is different to the cloud resource identifier `MountTargetDomain` + if nasclient.IsMountTargetNotFoundError(err) { + return managed.ExternalObservation{ResourceExists: false, ResourceUpToDate: true}, nil + } + return managed.ExternalObservation{ResourceExists: false}, errors.Wrap(err, errFailedToDescribeNASMountTarget) + } + + var upToDate = nasclient.IsMountTargetUpdateToDate(cr, mountTarget) + if upToDate { + cr.SetConditions(xpv1.Available()) + } + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: upToDate, + ConnectionDetails: GetMountTargetConnectionDetails(cr), + }, nil +} + +// Create managed resource NASFilesystem +func (e *mountTargetExternal) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*v1alpha1.NASMountTarget) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotNASMountTarget) + } + cr.SetConditions(xpv1.Creating()) + res, err := e.ExternalClient.CreateMountTarget(cr.Spec.ForProvider) + if err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errFailedToCreateNASMountTarget) + } + cr.Status.AtProvider = nasclient.GenerateObservation4MountTarget(res) + return managed.ExternalCreation{ConnectionDetails: GetMountTargetConnectionDetails(cr)}, nil +} + +// Update managed resource NASFilesystem +func (e *mountTargetExternal) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + return managed.ExternalUpdate{}, nil +} + +// Delete managed resource NASFilesystem +func (e *mountTargetExternal) Delete(ctx context.Context, mg resource.Managed) error { + cr, ok := mg.(*v1alpha1.NASMountTarget) + if !ok { + return errors.New(errNotNASMountTarget) + } + cr.SetConditions(xpv1.Deleting()) + if err := e.ExternalClient.DeleteMountTarget(cr.Spec.ForProvider.FileSystemID, cr.Status.AtProvider.MountTargetDomain); err != nil { + return errors.Wrap(err, errFailedToDeleteNASMountTarget) + } + return nil +} + +// GetMountTargetConnectionDetails generates connection details +func GetMountTargetConnectionDetails(cr *v1alpha1.NASMountTarget) managed.ConnectionDetails { + cd := managed.ConnectionDetails{} + + if cr.Status.AtProvider.MountTargetDomain != nil { + cd["MountTargetDomain"] = []byte(*cr.Status.AtProvider.MountTargetDomain) + } + return cd +} diff --git a/pkg/controller/nas/mountpoint_controller_test.go b/pkg/controller/nas/mountpoint_controller_test.go new file mode 100644 index 0000000..bd0e271 --- /dev/null +++ b/pkg/controller/nas/mountpoint_controller_test.go @@ -0,0 +1,231 @@ +/* +Copyright 2021 The Crossplane Authors. + +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 nas + +import ( + "context" + "testing" + + sdk "github.com/alibabacloud-go/nas-20170626/v2/client" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + "github.com/crossplane/provider-alibaba/apis/nas/v1alpha1" +) + +func (c *fakeSDKClient) DescribeMountTargets(fileSystemID, mountTargetDomain *string) (*sdk.DescribeMountTargetsResponse, error) { + switch *fileSystemID { + case "123": + return nil, errors.New("unknown error") + default: + body := &sdk.DescribeMountTargetsResponseBody{ + MountTargets: &sdk.DescribeMountTargetsResponseBodyMountTargets{ + MountTarget: []*sdk.DescribeMountTargetsResponseBodyMountTargetsMountTarget{}}, TotalCount: pointer.Int32Ptr(0)} + NASMountTargetInfoResult := sdk.DescribeMountTargetsResponse{ + Body: body, + } + return &NASMountTargetInfoResult, nil + } +} + +func (c *fakeSDKClient) CreateMountTarget(fs v1alpha1.NASMountTargetParameter) (*sdk.CreateMountTargetResponse, error) { + res := &sdk.CreateMountTargetResponse{Body: &sdk.CreateMountTargetResponseBody{MountTargetDomain: pointer.StringPtr("abc.com")}} + return res, nil +} + +func (c *fakeSDKClient) DeleteMountTarget(fileSystemID, mountTargetDomain *string) error { + return nil +} + +func TestMountTargetObserve(t *testing.T) { + var ctx = context.Background() + + invalidCR := &v1alpha1.NASMountTarget{Spec: v1alpha1.NASMountTargetSpec{ForProvider: v1alpha1.NASMountTargetParameter{ + FileSystemID: pointer.StringPtr("123")}}, + Status: v1alpha1.NASMountTargetStatus{AtProvider: v1alpha1.NASMountTargetObservation{ + MountTargetDomain: pointer.StringPtr("abc.com"), + }}} + invalidCR.ObjectMeta.Annotations = map[string]string{meta.AnnotationKeyExternalName: "abc"} + + validCR := &v1alpha1.NASMountTarget{Spec: v1alpha1.NASMountTargetSpec{ForProvider: v1alpha1.NASMountTargetParameter{ + FileSystemID: pointer.StringPtr("456")}}, + Status: v1alpha1.NASMountTargetStatus{AtProvider: v1alpha1.NASMountTargetObservation{ + MountTargetDomain: pointer.StringPtr("abc.com"), + }}} + validCR.ObjectMeta.Annotations = map[string]string{meta.AnnotationKeyExternalName: "def"} + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + mg resource.Managed + want want + }{ + "NotNASMountTarget": { + reason: "We should return an error if the supplied managed resource is not NASMountTarget", + mg: nil, + want: want{ + o: managed.ExternalObservation{}, + err: errors.New(errNotNASMountTarget), + }, + }, + "NASMountTargetNotFound": { + reason: "We should report a NotFound error", + mg: &v1alpha1.NASMountTarget{}, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: false, + }, + err: nil, + }, + }, + "NASMountTargetOtherError": { + reason: "We should report an unknown error", + mg: invalidCR, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + err: errors.Wrap(errors.New("unknown error"), errFailedToDescribeNASMountTarget), + }, + }, + "Success": { + reason: "Observing NASMountTarget successfully should return an ExternalObservation and nil error", + mg: validCR, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + ConnectionDetails: GetMountTargetConnectionDetails(validCR)}, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + external := &mountTargetExternal{ExternalClient: &fakeSDKClient{}} + got, err := external.Observe(ctx, tc.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.o, got); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestMountTargetCreate(t *testing.T) { + var ctx = context.Background() + + validCR := &v1alpha1.NASMountTarget{Spec: v1alpha1.NASMountTargetSpec{ForProvider: v1alpha1.NASMountTargetParameter{}}} + validCR.Status.AtProvider.MountTargetDomain = pointer.StringPtr("abc.com") + + type want struct { + o managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + mg resource.Managed + want want + }{ + "NotNASMountTarget": { + reason: "Not NASMountTarget object", + mg: nil, + want: want{ + o: managed.ExternalCreation{}, + err: errors.New(errNotNASMountTarget), + }, + }, + "Success": { + reason: "Creating NASMountTarget successfully", + mg: validCR, + want: want{ + o: managed.ExternalCreation{ + ConnectionDetails: GetMountTargetConnectionDetails(validCR)}, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + external := &mountTargetExternal{ExternalClient: &fakeSDKClient{}} + got, err := external.Create(ctx, tc.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.o, got); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestMountTargetDelete(t *testing.T) { + var ctx = context.Background() + + validCR := &v1alpha1.NASMountTarget{ + Spec: v1alpha1.NASMountTargetSpec{}, + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: "def"}}, + } + + type want struct { + err error + } + + cases := map[string]struct { + reason string + mg resource.Managed + want want + }{ + "NotNASMountTarget": { + reason: "Not NASMountTarget object", + mg: nil, + want: want{ + err: errors.New(errNotNASMountTarget), + }, + }, + "Success": { + reason: "Deleting NASMountTarget successfully", + mg: validCR, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + external := &mountTargetExternal{ExternalClient: &fakeSDKClient{}} + err := external.Delete(ctx, tc.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Delete(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + }) + } +} diff --git a/pkg/util/endpoint.go b/pkg/util/endpoint.go index 02174a6..fb05565 100644 --- a/pkg/util/endpoint.go +++ b/pkg/util/endpoint.go @@ -48,7 +48,7 @@ func GetEndpoint(res runtime.Object, region string) (string, error) { switch res.GetObjectKind().GroupVersionKind().Kind { case ossapi.BucketKind: endpoint = fmt.Sprintf("http://oss-%s.%s", region, Domain) - case nasapi.NASFileSystemKind: + case nasapi.NASFileSystemKind, nasapi.NASMountTargetKind: endpoint = fmt.Sprintf("nas.%s.aliyuncs.com", region) default: return "", errors.New(errCloudResourceNotSupported) From bf238d3afd2d06395bb68d05d06a7ce5433e8eed Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Thu, 24 Jun 2021 19:22:02 +0800 Subject: [PATCH 2/2] Fix column File-System-ID and format issue Signed-off-by: Zheng Xi Zhou --- apis/nas/v1alpha1/nas_mount-target_types.go | 2 +- package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml | 2 +- pkg/clients/nas/nas.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apis/nas/v1alpha1/nas_mount-target_types.go b/apis/nas/v1alpha1/nas_mount-target_types.go index cd1ff6b..c56ed9b 100644 --- a/apis/nas/v1alpha1/nas_mount-target_types.go +++ b/apis/nas/v1alpha1/nas_mount-target_types.go @@ -33,7 +33,7 @@ type NASMountTargetList struct { // +kubebuilder:object:root=true // NASMountTarget is a managed resource that represents an NASMountTarget instance -// +kubebuilder:printcolumn:name="FILE-SYSTEM-ID",type="string",JSONPath=".spec.atProvider.fileSystemID" +// +kubebuilder:printcolumn:name="FILE-SYSTEM-ID",type="string",JSONPath=".spec.forProvider.fileSystemID" // +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" // +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml b/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml index 1e77929..25a3886 100644 --- a/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml +++ b/package/crds/nas.alibaba.crossplane.io_nasmounttargets.yaml @@ -21,7 +21,7 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .spec.atProvider.fileSystemID + - jsonPath: .spec.forProvider.fileSystemID name: FILE-SYSTEM-ID type: string - jsonPath: .status.conditions[?(@.type=='Ready')].status diff --git a/pkg/clients/nas/nas.go b/pkg/clients/nas/nas.go index bd181ec..054da8b 100644 --- a/pkg/clients/nas/nas.go +++ b/pkg/clients/nas/nas.go @@ -201,6 +201,7 @@ func GenerateObservation4MountTarget(res *sdk.CreateMountTargetResponse) v1alpha } // IsMountTargetUpdateToDate checks whether cr is up to date +//nolint:gocyclo func IsMountTargetUpdateToDate(cr *v1alpha1.NASMountTarget, mountTargetResponse *sdk.DescribeMountTargetsResponse) bool { if *mountTargetResponse.Body.TotalCount == 0 { return false