From b9a39e94ddd1f4a277c62d1b5643ea5952e381b4 Mon Sep 17 00:00:00 2001 From: michaelawyu Date: Mon, 3 Jul 2023 17:58:29 +0800 Subject: [PATCH] feat: scheduler (9/): add uniquename utility for preparing binding names (#404) --- .../framework/uniquename/uniquename.go | 57 +++++++++++++ .../framework/uniquename/uniquename_test.go | 79 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 pkg/scheduler/framework/uniquename/uniquename.go create mode 100644 pkg/scheduler/framework/uniquename/uniquename_test.go diff --git a/pkg/scheduler/framework/uniquename/uniquename.go b/pkg/scheduler/framework/uniquename/uniquename.go new file mode 100644 index 000000000..12b11dcb3 --- /dev/null +++ b/pkg/scheduler/framework/uniquename/uniquename.go @@ -0,0 +1,57 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +// package uniquename features some utilities that are used to generate unique names in use +// by the scheduler. +package uniquename + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/apimachinery/pkg/util/validation" +) + +const ( + uuidLength = 6 +) + +// minInt returns the smaller one of two integers. +func minInt(a, b int) int { + if a < b { + return a + } + return b +} + +// NewClusterResourceBindingName returns a unique name for a cluster resource binding in the +// format of DNS subdomain names (RFC 1123). +// +// The name is generated using the following format: +// * [CRP-NAME] - [TARGET-CLUSTER-NAME] - [RANDOM-SUFFIX] +// +// Segments will be truncated if necessary. +// +// Note that the name generation is, in essence, a best-effort process, though the chances +// of name collisions are extremely low. +// +// In addition, note that this function assumes that both the CRP name and the cluster name +// are valid DNS subdomain names (RFC 1123). +func NewClusterResourceBindingName(CRPName string, clusterName string) (string, error) { + reservedSlots := 2 + uuidLength // 2 dashs + 6 character UUID string + + slotsPerSeg := (validation.DNS1123SubdomainMaxLength - reservedSlots) / 2 + uniqueName := fmt.Sprintf("%s-%s-%s", + CRPName[:minInt(slotsPerSeg, len(CRPName))], + clusterName[:minInt(slotsPerSeg, len(clusterName))], + uuid.NewUUID()[:uuidLength], + ) + + if errs := validation.IsDNS1123Subdomain(uniqueName); len(errs) != 0 { + // Do a sanity check here; normally this would not occur. + return "", fmt.Errorf("failed to format a unique RFC 1123 DNS subdomain name with namespace %s, name %s: %v", CRPName, clusterName, errs) + } + return uniqueName, nil +} diff --git a/pkg/scheduler/framework/uniquename/uniquename_test.go b/pkg/scheduler/framework/uniquename/uniquename_test.go new file mode 100644 index 000000000..76b75c4b7 --- /dev/null +++ b/pkg/scheduler/framework/uniquename/uniquename_test.go @@ -0,0 +1,79 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package uniquename + +import ( + "fmt" + "strings" + "testing" +) + +const ( + crpName = "app" + clusterName = "bravelion" + + longName = "c7t2c6oppjnryqcihwweexeobs7tlmf08ha4qb5htc4cifzpalhb5ec2lbh3" + + "j73reciaz2f0jfd2rl5qba6rzuuwgyw6d9e6la19bo89k41lphln4s4dy1gr" + + "h1dvua17iu4ro61dxo91ayovns8cgnmshlsflmi68e3najm7dw5dqe17pih7" + + "up0dtyvrqxyp90sxedbf" +) + +// TO-DO (chenyu1): Expand the test cases as development proceeds. + +// TestClusterResourceBindingUniqueName tests the ClusterResourceBindingUniqueName function. +func TestClusterResourceBindingUniqueName(t *testing.T) { + testCases := []struct { + name string + crpName string + clusterName string + wantPrefix string + wantLength int + expectedToFail bool + }{ + { + name: "valid name", + crpName: crpName, + clusterName: clusterName, + wantPrefix: fmt.Sprintf("%s-%s", crpName, clusterName), + wantLength: len(crpName) + len(clusterName) + 2 + uuidLength, + }, + { + name: "valid name (truncated)", + crpName: longName, + clusterName: longName, + wantPrefix: fmt.Sprintf("%s-%s", longName[:122], longName[:122]), + wantLength: 252, + }, + { + name: "invalid name", + crpName: crpName, + clusterName: clusterName + "!", + expectedToFail: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + name, err := NewClusterResourceBindingName(tc.crpName, tc.clusterName) + + if tc.expectedToFail { + if err == nil { + t.Errorf("ClusterResourceBindingUniqueName(%s, %s) = %v, %v, want error", tc.crpName, tc.clusterName, name, err) + } + return + } + if err != nil { + t.Errorf("ClusterResourceBindingUniqueName(%s, %s) = %v, %v, want no error", tc.crpName, tc.clusterName, name, err) + } + if !strings.HasPrefix(name, tc.wantPrefix) { + t.Errorf("ClusterResourceBindingUniqueName(%s, %s) = %s, want to have prefix %s", tc.crpName, tc.clusterName, name, tc.wantPrefix) + } + if len(name) != tc.wantLength { + t.Errorf("ClusterResourceBindingUniqueName(%s, %s) = %s, want to have length %d", tc.crpName, tc.clusterName, name, tc.wantLength) + } + }) + } +}