Skip to content

Commit

Permalink
New string generator for the hash string in NSX resources
Browse files Browse the repository at this point in the history
1. Use chars 0-9,a-z,A-Z to generate the hash string for ID/DisplayName in NSX
resources with VPC scenario. It also works on the Security Policy related
resources' display name in T1.
2. The length is 6 chars when using the new hash string function.
  • Loading branch information
wenyingd committed Jan 10, 2025
1 parent ccc66e7 commit b8425ed
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 33 deletions.
1 change: 1 addition & 0 deletions pkg/nsx/services/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

const (
HashLength int = 8
Base62HashLength int = 6
MaxTagsCount int = 26
MaxTagScopeLength int = 128
MaxTagValueLength int = 256
Expand Down
4 changes: 2 additions & 2 deletions pkg/nsx/services/securitypolicy/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1274,8 +1274,8 @@ func Test_BuildSecurityPolicyName(t *testing.T) {
},
},
createdFor: common.ResourceTypeNetworkPolicy,
expName: fmt.Sprintf("%s_c64163f0", strings.Repeat("a", 246)),
expId: fmt.Sprintf("%s_fb85d834", strings.Repeat("a", 246)),
expName: fmt.Sprintf("%s_shQDwf", strings.Repeat("a", 248)),
expId: fmt.Sprintf("%s_zT4Byn", strings.Repeat("a", 248)),
},
} {
t.Run(tc.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/nsx/services/vpc/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func TestBuildNSXVPC(t *testing.T) {
},
expVPC: &model.Vpc{
Id: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_ecc6eb9f-92b5-4893-b809-e3ebc1fcf59e"),
DisplayName: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_f4f0080e"),
DisplayName: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_yWOLBB"),
PrivateIps: []string{"192.168.3.0/24"},
IpAddressType: common.String("IPV4"),
Tags: []model.Tag{
Expand Down
69 changes: 52 additions & 17 deletions pkg/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"net"
"strconv"
"strings"
Expand All @@ -31,6 +32,7 @@ import (

const (
wcpSystemResource = "vmware-system-shared-t1"
base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

var (
Expand All @@ -39,44 +41,47 @@ var (

var log = &logger.Log

func truncateLabelHash(data string) string {
return Sha1(data)[:common.HashLength]
}
func NormalizeLabels(matchLabels *map[string]string) *map[string]string {
newLabels := make(map[string]string)
for k, v := range *matchLabels {
newLabels[NormalizeLabelKey(k)] = NormalizeName(v)
newLabels[NormalizeLabelKey(k, truncateLabelHash)] = NormalizeName(v, truncateLabelHash)
}
return &newLabels
}

func NormalizeLabelKey(key string) string {
func NormalizeLabelKey(key string, shaFn func(data string) string) string {
if len(key) <= common.MaxTagScopeLength {
return key
}
splitted := strings.Split(key, "/")
key = splitted[len(splitted)-1]
return normalizeNameByLimit(key, "", common.MaxTagScopeLength)
return normalizeNameByLimit(key, "", common.MaxTagScopeLength, shaFn)
}

func NormalizeName(name string) string {
return normalizeNameByLimit(name, "", common.MaxTagValueLength)
func NormalizeName(name string, shaFn func(data string) string) string {
return normalizeNameByLimit(name, "", common.MaxTagValueLength, shaFn)
}

func normalizeNameByLimit(name string, suffix string, limit int) string {
func normalizeNameByLimit(name string, suffix string, limit int, hashFn func(data string) string) string {
newName := connectStrings(common.ConnectorUnderline, name, suffix)
if len(newName) <= limit {
return newName
}

var hashString string
hashedTarget := name
if len(suffix) > 0 {
hashString = Sha1(suffix)
} else {
hashString = Sha1(name)
hashedTarget = suffix
}
nameLength := limit - common.HashLength - 1

hashString := hashFn(hashedTarget)
nameLength := limit - len(hashString) - 1
if len(name) < nameLength {
nameLength = len(name)
}
return strings.Join([]string{name[:nameLength], hashString[:common.HashLength]}, common.ConnectorUnderline)
return strings.Join([]string{name[:nameLength], hashString}, common.ConnectorUnderline)
}

func NormalizeId(name string) string {
Expand All @@ -94,10 +99,36 @@ func NormalizeId(name string) string {
}

func Sha1(data string) string {
sum := getSha1Bytes(data)
return fmt.Sprintf("%x", sum)
}

func getSha1Bytes(data string) []byte {
h := sha1.New() // #nosec G401: not used for security purposes
h.Write([]byte(data))
sum := h.Sum(nil)
return fmt.Sprintf("%x", sum)
return sum
}

// Sha1WithBase62 uses the chars in `base62Chars` to present the hash result on the input data. We now use Sha1 as
// the hash algorithm.
func Sha1WithBase62(data string) string {
sum := getSha1Bytes(data)
value := new(big.Int).SetBytes(sum[:])
base := big.NewInt(int64(len(base62Chars)))
var result []byte
for value.Cmp(big.NewInt(0)) > 0 {
mod := new(big.Int).Mod(value, base)
result = append(result, base62Chars[mod.Int64()])
value.Div(value, base)
}

// Reverse the result because the encoding process generates characters in reverse order
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}

return string(result)
}

func RemoveDuplicateStr(strSlice []string) []string {
Expand Down Expand Up @@ -339,11 +370,15 @@ func UpdateK8sResourceAnnotation(client client.Client, ctx context.Context, k8sO
return nil
}

func truncateNameOrIDHash(data string) string {
return Sha1WithBase62(data)[:common.Base62HashLength]
}

// GenerateIDByObject generate string id for NSX resource using the provided Object's name and uid. Note,
// this function is used on the resources with VPC scenario, and the provided obj is the K8s CR which is
// used to generate the NSX resource.
func GenerateIDByObject(obj metav1.Object) string {
return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), common.MaxIdLength)
return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), common.MaxIdLength, truncateNameOrIDHash)
}

// GenerateIDByObjectByLimit generate string id for NSX resource using the provided Object's name and uid,
Expand All @@ -352,13 +387,13 @@ func GenerateIDByObjectByLimit(obj metav1.Object, limit int) string {
if limit == 0 {
limit = common.MaxIdLength
}
return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit)
return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit, truncateNameOrIDHash)
}

func GenerateIDByObjectWithSuffix(obj metav1.Object, suffix string) string {
limit := common.MaxIdLength
limit -= len(suffix) + 1
return connectStrings(common.ConnectorUnderline, normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit), suffix)
return connectStrings(common.ConnectorUnderline, normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit, truncateNameOrIDHash), suffix)
}

// GenerateID generate id for NSX resource, some resources has complex index, so set it type to string
Expand Down Expand Up @@ -391,7 +426,7 @@ func GenerateTruncName(limit int, resName string, prefix, suffix, project, clust
}
oldName := generateDisplayName(common.ConnectorUnderline, resName, "", "", project, cluster)
if len(oldName) > adjustedLimit {
newName := normalizeNameByLimit(oldName, "", adjustedLimit)
newName := normalizeNameByLimit(oldName, "", adjustedLimit, truncateNameOrIDHash)
return generateDisplayName(common.ConnectorUnderline, newName, prefix, suffix, "", "")
}
return generateDisplayName(common.ConnectorUnderline, resName, prefix, suffix, project, cluster)
Expand Down
45 changes: 32 additions & 13 deletions pkg/util/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import (
"strings"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"

"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"

"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
)

func TestSha1(t *testing.T) {
Expand All @@ -28,16 +30,16 @@ func TestSha1(t *testing.T) {

func TestNormalizeName(t *testing.T) {
shortName := strings.Repeat("a", 256)
assert.Equal(t, NormalizeName(shortName), shortName)
assert.Equal(t, NormalizeName(shortName, truncateLabelHash), shortName)
longName := strings.Repeat("a", 257)
assert.Equal(t, NormalizeName(longName), fmt.Sprintf("%s_%s", strings.Repeat("a", 256-common.HashLength-1), "0c103888"))
assert.Equal(t, NormalizeName(longName, truncateLabelHash), fmt.Sprintf("%s_%s", strings.Repeat("a", 256-common.HashLength-1), "0c103888"))
}

func TestNormalizeLabelKey(t *testing.T) {
shortKey := strings.Repeat("a", 128)
assert.Equal(t, NormalizeLabelKey(shortKey), shortKey)
assert.Equal(t, NormalizeLabelKey(shortKey, truncateLabelHash), shortKey)
longKey := strings.Repeat("a", 129) + "/def"
assert.Equal(t, NormalizeLabelKey(longKey), "def")
assert.Equal(t, NormalizeLabelKey(longKey, truncateLabelHash), "def")
}

func TestNormalizeLabels(t *testing.T) {
Expand All @@ -55,7 +57,7 @@ func TestNormalizeLabels(t *testing.T) {
longKey: longValue,
},
expectedLabels: &map[string]string{
"def": NormalizeName(longValue),
"def": NormalizeName(longValue, truncateLabelHash),
},
},
{
Expand All @@ -64,7 +66,7 @@ func TestNormalizeLabels(t *testing.T) {
shortKey: longValue,
},
expectedLabels: &map[string]string{
shortKey: NormalizeName(longValue),
shortKey: NormalizeName(longValue, truncateLabelHash),
},
},
}
Expand Down Expand Up @@ -500,7 +502,7 @@ func TestGenerateTruncName(t *testing.T) {
project: strings.Repeat("s", 300),
cluster: "k8scl-one",
},
want: "sr_k8scl-one_1234-456_ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_e89b45cc_scope",
want: "sr_k8scl-one_1234-456_ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_xbJrtX_scope",
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -660,13 +662,13 @@ func TestGenerateIDByObject(t *testing.T) {
name: "truncate with hash on uid",
obj: &metav1.ObjectMeta{Name: "abcdefg", UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"},
limit: 20,
expID: "abcdefg_df78acb2",
expID: "abcdefg_vSV1eZ",
},
{
name: "longer name with truncate",
obj: &metav1.ObjectMeta{Name: strings.Repeat("a", 256), UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"},
limit: 0,
expID: fmt.Sprintf("%s_df78acb2", strings.Repeat("a", 246)),
expID: fmt.Sprintf("%s_vSV1eZ", strings.Repeat("a", 248)),
},
} {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -701,7 +703,7 @@ func TestGenerateIDByObjectWithSuffix(t *testing.T) {
obj: &metav1.ObjectMeta{Name: strings.Repeat("a", 256), UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"},
limit: 0,
suffix: "28e85c0b-21e4-4cab-b1c3-597639dfe752",
expID: fmt.Sprintf("%s_df78acb2_28e85c0b-21e4-4cab-b1c3-597639dfe752", strings.Repeat("a", 209)),
expID: fmt.Sprintf("%s_vSV1eZ_28e85c0b-21e4-4cab-b1c3-597639dfe752", strings.Repeat("a", 211)),
},
} {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -734,3 +736,20 @@ func TestConnectStrings(t *testing.T) {
expString = fmt.Sprintf("%s%s%d", string1, common.ConnectorUnderline, int2)
assert.Equal(t, connectString, expString)
}

func TestNewSha1(t *testing.T) {
assert.Equal(t, "ffN5UpVkkQYbocYDKFXOAMN4AsA", Sha1WithBase62("name"))
assert.Equal(t, "hZBZpydbX1XIFhgs9m6Lt2for9m", Sha1WithBase62("namee"))

allowedChars := sets.New[rune]()
for _, c := range base62Chars {
allowedChars.Insert(c)
}
randUID, err := uuid.NewRandom()
require.NoError(t, err)
hashString := Sha1WithBase62(randUID.String())
// Verify all chars in the hash string are contained in base62Chars.
for _, c := range hashString {
assert.True(t, allowedChars.Has(c))
}
}

0 comments on commit b8425ed

Please sign in to comment.