Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New string generator for the hash string in NSX resources #995

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
wenyingd marked this conversation as resolved.
Show resolved Hide resolved
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 {
timdengyun marked this conversation as resolved.
Show resolved Hide resolved
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))
}
}
Loading