Skip to content

Commit

Permalink
SKS-2193: Support setting the owner of ElfCluster and virtual machines (
Browse files Browse the repository at this point in the history
  • Loading branch information
haijianyang authored Dec 7, 2023
1 parent 319a4fb commit b2fb22f
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 65 deletions.
7 changes: 7 additions & 0 deletions api/v1beta1/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ const (

// CAPEVersionAnnotation is the annotation identifying the version of CAPE that the resource reconciled by.
CAPEVersionAnnotation = "cape.infrastructure.cluster.x-k8s.io/cape-version"

// CreatedByAnnotation is the annotation identifying the creator of the resource.
//
// The creator can be in one of the following two formats:
// 1. ${Tower username}@${Tower auth_config_id}, e.g. caas.smartx@7e98ecbb-779e-43f6-8330-1bc1d29fffc7.
// 2. ${Tower username}, e.g. root. If auth_config_id is not set, it means it is a LOCAL user.
CreatedByAnnotation = "cape.infrastructure.cluster.x-k8s.io/created-by"
)

// Labels.
Expand Down
7 changes: 4 additions & 3 deletions controllers/elfmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
labelsutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/labels"
machineutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/machine"
patchutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/patch"
typesutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/types"
)

//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=elfmachines,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -612,14 +613,14 @@ func (r *ElfMachineReconciler) reconcileVM(ctx *context.MachineContext) (*models

vmRef := util.GetVMRef(vm)
// If vmRef is in UUID format, it means that the ELF VM created.
if !machineutil.IsUUID(vmRef) {
if !typesutil.IsUUID(vmRef) {
ctx.Logger.Info("The VM is being created", "vmRef", vmRef)

return vm, false, nil
}

// When ELF VM created, set UUID to VMRef
if !machineutil.IsUUID(ctx.ElfMachine.Status.VMRef) {
if !typesutil.IsUUID(ctx.ElfMachine.Status.VMRef) {
ctx.ElfMachine.SetVM(vmRef)
}

Expand Down Expand Up @@ -660,7 +661,7 @@ func (r *ElfMachineReconciler) getVM(ctx *context.MachineContext) (*models.VM, e
return nil, err
}

if machineutil.IsUUID(ctx.ElfMachine.Status.VMRef) {
if typesutil.IsUUID(ctx.ElfMachine.Status.VMRef) {
vmDisconnectionTimestamp := ctx.ElfMachine.GetVMDisconnectionTimestamp()
if vmDisconnectionTimestamp == nil {
now := metav1.Now()
Expand Down
5 changes: 3 additions & 2 deletions controllers/elfmachine_controller_placement_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
annotationsutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/annotations"
kcputil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/kcp"
machineutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/machine"
typesutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/types"
"github.com/smartxworks/cluster-api-provider-elf/pkg/version"
)

Expand Down Expand Up @@ -258,7 +259,7 @@ func (r *ElfMachineReconciler) getVMHostForRollingUpdate(ctx *context.MachineCon

elfMachineMap := make(map[string]*infrav1.ElfMachine)
for i := 0; i < len(elfMachines); i++ {
if machineutil.IsUUID(elfMachines[i].Status.VMRef) {
if typesutil.IsUUID(elfMachines[i].Status.VMRef) {
elfMachineMap[elfMachines[i].Name] = elfMachines[i]
}
}
Expand Down Expand Up @@ -359,7 +360,7 @@ func (r *ElfMachineReconciler) getPlacementGroup(ctx *context.MachineContext, pl
}

// Placement group is performing an operation
if !machineutil.IsUUID(*placementGroup.LocalID) || placementGroup.EntityAsyncStatus != nil {
if !typesutil.IsUUID(*placementGroup.LocalID) || placementGroup.EntityAsyncStatus != nil {
ctx.Logger.Info("Waiting for placement group task done", "placementGroup", *placementGroup.Name)

return nil, nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/service/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ const (
// SKSVMTemplateUIDLabel is the label used to find the virtual machine template.
SKSVMTemplateUIDLabel = "system.cloudtower/sks-template-uid"
)

// VMOwnerSearchForUsername is used to specify the ower source of the virtual machine.
// TODO: Tower SDK will provide the VMOwnerSearchFor enumeration types in the new version.
const VMOwnerSearchForUsername = "username"
29 changes: 29 additions & 0 deletions pkg/service/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
"github.com/smartxworks/cluster-api-provider-elf/pkg/config"
typesutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/types"
)

// GetUpdatedVMRestrictedFields returns the updated restricted fields of the VM compared to ElfMachine.
Expand Down Expand Up @@ -309,3 +310,31 @@ func calGPUAvailableVgpusNum(vgpuInstanceNum, assignedVGPUsNum int32) int32 {

return count
}

// parseOwnerFromCreatedByAnnotation parse owner from createdBy annotation.
//
// The createdBy can be in one of the following two formats:
// 1. ${Tower username}@${Tower auth_config_id}.
// 2. ${Tower username}.
//
// The owner can be in one of the following two formats:
// 1. ${Tower username}_${Tower auth_config_id}, e.g. caas.smartx_7e98ecbb-779e-43f6-8330-1bc1d29fffc7.
// 2. ${Tower username}, e.g. root. If auth_config_id is not set, it means it is a LOCAL user.
func parseOwnerFromCreatedByAnnotation(createdBy string) string {
lastIndex := strings.LastIndex(createdBy, "@")
if len(createdBy) <= 1 || lastIndex <= 0 || lastIndex == len(createdBy) {
return createdBy
}

username := createdBy[0:lastIndex]
authConfigID := createdBy[lastIndex+1:]

// If authConfigID is not in UUID format, it means username contains the last `@` character,
// return createdBy directly.
if !typesutil.IsUUID(authConfigID) {
return createdBy
}

// last `@` replaced with `_`.
return fmt.Sprintf("%s_%s", username, authConfigID)
}
23 changes: 23 additions & 0 deletions pkg/service/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,26 @@ func TestHasGPUsCanNotBeUsedForVM(t *testing.T) {
}), elfMachine)).To(gomega.BeFalse())
})
}

func TestParseOwnerFromCreatedByAnnotation(t *testing.T) {
g := gomega.NewGomegaWithT(t)

t.Run("parseOwnerFromCreatedByAnnotation", func(t *testing.T) {
g.Expect(parseOwnerFromCreatedByAnnotation("")).To(gomega.Equal(""))
g.Expect(parseOwnerFromCreatedByAnnotation("a")).To(gomega.Equal("a"))
g.Expect(parseOwnerFromCreatedByAnnotation("@")).To(gomega.Equal("@"))
g.Expect(parseOwnerFromCreatedByAnnotation("a@")).To(gomega.Equal("a@"))
g.Expect(parseOwnerFromCreatedByAnnotation("@a")).To(gomega.Equal("@a"))
g.Expect(parseOwnerFromCreatedByAnnotation("@@")).To(gomega.Equal("@@"))
g.Expect(parseOwnerFromCreatedByAnnotation("root")).To(gomega.Equal("root"))
g.Expect(parseOwnerFromCreatedByAnnotation("@root")).To(gomega.Equal("@root"))
g.Expect(parseOwnerFromCreatedByAnnotation("ro@ot")).To(gomega.Equal("ro@ot"))
g.Expect(parseOwnerFromCreatedByAnnotation("root@")).To(gomega.Equal("root@"))
g.Expect(parseOwnerFromCreatedByAnnotation("@ro@ot@")).To(gomega.Equal("@ro@ot@"))
g.Expect(parseOwnerFromCreatedByAnnotation("root@123456")).To(gomega.Equal("root@123456"))
g.Expect(parseOwnerFromCreatedByAnnotation("root@d8dc20fc-e197-41da-83b6-c903c88663fd")).To(gomega.Equal("root_d8dc20fc-e197-41da-83b6-c903c88663fd"))
g.Expect(parseOwnerFromCreatedByAnnotation("@root@d8dc20fc-e197-41da-83b6-c903c88663fd")).To(gomega.Equal("@root_d8dc20fc-e197-41da-83b6-c903c88663fd"))
g.Expect(parseOwnerFromCreatedByAnnotation("root@@d8dc20fc-e197-41da-83b6-c903c88663fd")).To(gomega.Equal("root@_d8dc20fc-e197-41da-83b6-c903c88663fd"))
g.Expect(parseOwnerFromCreatedByAnnotation("root@d8dc20fc-e197-41da-83b6-c903c88663fd@")).To(gomega.Equal("root@d8dc20fc-e197-41da-83b6-c903c88663fd@"))
})
}
11 changes: 11 additions & 0 deletions pkg/service/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
"github.com/smartxworks/cluster-api-provider-elf/pkg/config"
"github.com/smartxworks/cluster-api-provider-elf/pkg/session"
annotationsutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/annotations"
)

type VMService interface {
Expand Down Expand Up @@ -241,11 +242,21 @@ func (svr *TowerVMService) Clone(
hostID = *host.ID
}

var owner *models.VMOwnerParams
if createdBy := annotationsutil.GetCreatedBy(elfCluster); createdBy != "" {
creator := parseOwnerFromCreatedByAnnotation(createdBy)
owner = &models.VMOwnerParams{
SearchFor: TowerString(VMOwnerSearchForUsername),
Value: TowerString(creator),
}
}

vmCreateVMFromTemplateParams := &models.VMCreateVMFromContentLibraryTemplateParams{
ClusterID: cluster.ID,
HostID: TowerString(hostID),
Name: TowerString(elfMachine.Name),
Description: TowerString(fmt.Sprintf(config.VMDescription, elfCluster.Spec.Tower.Server)),
Owner: owner,
Vcpu: vCPU,
CPUCores: cpuCores,
CPUSockets: cpuSockets,
Expand Down
9 changes: 9 additions & 0 deletions pkg/util/annotations/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ func GetPlacementGroupName(o metav1.Object) string {
return annotations[infrav1.PlacementGroupNameAnnotation]
}

func GetCreatedBy(o metav1.Object) string {
annotations := o.GetAnnotations()
if annotations == nil {
return ""
}

return annotations[infrav1.CreatedByAnnotation]
}

// AddAnnotations sets the desired annotations on the object and returns true if the annotations have changed.
func AddAnnotations(o metav1.Object, desired map[string]string) bool {
return annotations.AddAnnotations(o, desired)
Expand Down
17 changes: 2 additions & 15 deletions pkg/util/machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
labelsutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/labels"
typesutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/types"
)

const (
Expand All @@ -39,10 +40,6 @@ const (
// ProviderIDPattern is a regex pattern and is used by ConvertProviderIDToUUID
// to convert a providerID into a UUID string.
ProviderIDPattern = `(?i)^` + ProviderIDPrefix + `([a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12})$`

// UUIDPattern is a regex pattern and is used by ConvertUUIDToProviderID
// to convert a UUID into a providerID string.
UUIDPattern = `(?i)^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$`
)

// ErrNoMachineIPAddr indicates that no valid IP addresses were found in a machine context.
Expand Down Expand Up @@ -132,23 +129,13 @@ func ConvertProviderIDToUUID(providerID *string) string {
}

func ConvertUUIDToProviderID(uuid string) string {
if !IsUUID(uuid) {
if !typesutil.IsUUID(uuid) {
return ""
}

return ProviderIDPrefix + uuid
}

func IsUUID(uuid string) bool {
if uuid == "" {
return false
}

pattern := regexp.MustCompile(UUIDPattern)

return pattern.MatchString(uuid)
}

func GetNetworkStatus(ipsStr string) []infrav1.NetworkStatus {
networks := []infrav1.NetworkStatus{}

Expand Down
43 changes: 0 additions & 43 deletions pkg/util/machine/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,49 +183,6 @@ func TestConvertUUIDtoProviderID(t *testing.T) {
}
}

func TestIsUUID(t *testing.T) {
g := gomega.NewGomegaWithT(t)

testCases := []struct {
name string
uuid string
isUUID bool
}{
{
name: "empty uuid",
uuid: "",
isUUID: false,
},
{
name: "invalid uuid",
uuid: "1234",
isUUID: false,
},
{
name: "valid uuid",
uuid: "12345678-1234-1234-1234-123456789abc",
isUUID: true,
},
{
name: "mixed case",
uuid: "12345678-1234-1234-1234-123456789AbC",
isUUID: true,
},
{
name: "invalid hex chars",
uuid: "12345678-1234-1234-1234-123456789abg",
isUUID: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
isUUID := IsUUID(tc.uuid)
g.Expect(isUUID).To(gomega.Equal(tc.isUUID))
})
}
}

func TestGetNetworkStatus(t *testing.T) {
g := gomega.NewGomegaWithT(t)

Expand Down
4 changes: 2 additions & 2 deletions pkg/util/tower.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/smartxworks/cloudtower-go-sdk/v2/models"

"github.com/smartxworks/cluster-api-provider-elf/pkg/service"
machineutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/machine"
typesutil "github.com/smartxworks/cluster-api-provider-elf/pkg/util/types"
)

// GetVMRef returns the ID or localID of the VM.
Expand All @@ -34,7 +34,7 @@ func GetVMRef(vm *models.VM) string {
}

vmLocalID := service.GetTowerString(vm.LocalID)
if machineutil.IsUUID(vmLocalID) {
if typesutil.IsUUID(vmLocalID) {
return vmLocalID
}

Expand Down
35 changes: 35 additions & 0 deletions pkg/util/types/uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2023.
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 types

import "regexp"

const (
// UUIDPattern is a regex pattern and is used by ConvertUUIDToProviderID
// to convert a UUID into a providerID string.
UUIDPattern = `(?i)^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$`
)

func IsUUID(uuid string) bool {
if uuid == "" {
return false
}

pattern := regexp.MustCompile(UUIDPattern)

return pattern.MatchString(uuid)
}
Loading

0 comments on commit b2fb22f

Please sign in to comment.