diff --git a/configmap/parse.go b/configmap/parse.go index e93f9af2a..b2f8d4f20 100644 --- a/configmap/parse.go +++ b/configmap/parse.go @@ -244,3 +244,16 @@ func Parse(data map[string]string, parsers ...ParseFunc) error { } return nil } + +// AsOptionalMap parses the data into the target as a map[string]string, if it exists. +// The map is represented as a list of key-value pairs with a common prefix. +func AsOptionalMap(prefix string, target map[string]string) ParseFunc { + return func(data map[string]string) error { + for k, v := range data { + if strings.HasPrefix(k, prefix) && len(k) > len(prefix)+1 { + target[k[len(prefix)+1: /* remove dot `.` */]] = v + } + } + return nil + } +} diff --git a/configmap/parse_test.go b/configmap/parse_test.go index 4ea896e6c..725b11bd2 100644 --- a/configmap/parse_test.go +++ b/configmap/parse_test.go @@ -42,6 +42,8 @@ type testConfig struct { nsn types.NamespacedName onsn *types.NamespacedName + + dict map[string]string } func TestParse(t *testing.T) { @@ -70,6 +72,12 @@ func TestParse(t *testing.T) { "test-namespaced-name": "some-namespace/some-name", "test-optional-namespaced-name": "some-other-namespace/some-other-name", + + "test-dict.k": "v", + "test-dict.k1": "v1", + }, + conf: testConfig{ + dict: map[string]string{}, }, want: testConfig{ str: "foo.bar", @@ -92,6 +100,10 @@ func TestParse(t *testing.T) { Name: "some-other-name", Namespace: "some-other-namespace", }, + dict: map[string]string{ + "k": "v", + "k1": "v1", + }, }, }, { name: "respect defaults", @@ -175,6 +187,18 @@ func TestParse(t *testing.T) { "test-namespaced-name": "default/resource/whut", }, expectErr: true, + }, { + name: "dict without key and dot", + data: map[string]string{ + "test-dict": "v", + }, + expectErr: false, + }, { + name: "dict without key", + data: map[string]string{ + "test-dict.": "v", + }, + expectErr: false, }} for _, test := range tests { @@ -194,6 +218,7 @@ func TestParse(t *testing.T) { AsQuantity("test-quantity", &test.conf.qua), AsNamespacedName("test-namespaced-name", &test.conf.nsn), AsOptionalNamespacedName("test-optional-namespaced-name", &test.conf.onsn), + AsOptionalMap("test-dict", test.conf.dict), ); (err == nil) == test.expectErr { t.Fatal("Failed to parse data:", err) } diff --git a/leaderelection/config.go b/leaderelection/config.go index f07c30cea..3ef47483d 100644 --- a/leaderelection/config.go +++ b/leaderelection/config.go @@ -56,6 +56,8 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { cm.AsDuration("retry-period", &config.RetryPeriod), cm.AsUint32("buckets", &config.Buckets), + + cm.AsOptionalMap("map-lease-prefix", config.LeaseNamesPrefixMapping), ); err != nil { return nil, err } @@ -79,19 +81,21 @@ func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*Config, error) { // contained within a single namespace. Typically these will correspond to a // single source repository, viz: serving or eventing. type Config struct { - Buckets uint32 - LeaseDuration time.Duration - RenewDeadline time.Duration - RetryPeriod time.Duration + Buckets uint32 + LeaseDuration time.Duration + RenewDeadline time.Duration + RetryPeriod time.Duration + LeaseNamesPrefixMapping map[string]string } func (c *Config) GetComponentConfig(name string) ComponentConfig { return ComponentConfig{ - Component: name, - Buckets: c.Buckets, - LeaseDuration: c.LeaseDuration, - RenewDeadline: c.RenewDeadline, - RetryPeriod: c.RetryPeriod, + Component: name, + Buckets: c.Buckets, + LeaseDuration: c.LeaseDuration, + RenewDeadline: c.RenewDeadline, + RetryPeriod: c.RetryPeriod, + LeaseNamesPrefixMapping: c.LeaseNamesPrefixMapping, } } @@ -123,6 +127,11 @@ type ComponentConfig struct { // be generated to be used as identity for each BuildElector call. // Autoscaler uses the pod IP as identity. Identity string + + // LeaseNamesPrefixMapping maps lease prefixes + // from .. to the + // associated value when using standardBuilder. + LeaseNamesPrefixMapping map[string]string } // statefulSetID is a envconfig Decodable controller ordinal and name. diff --git a/leaderelection/context.go b/leaderelection/context.go index 4758ccba5..4e4c46a47 100644 --- a/leaderelection/context.go +++ b/leaderelection/context.go @@ -27,6 +27,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" + "knative.dev/pkg/hash" "knative.dev/pkg/logging" "knative.dev/pkg/network" @@ -192,7 +193,11 @@ func newStandardBuckets(queueName string, cc ComponentConfig) []reconciler.Bucke } func standardBucketName(ordinal uint32, queueName string, cc ComponentConfig) string { - return strings.ToLower(fmt.Sprintf("%s.%s.%02d-of-%02d", cc.Component, queueName, ordinal, cc.Buckets)) + prefix := fmt.Sprintf("%s.%s", cc.Component, queueName) + if v, ok := cc.LeaseNamesPrefixMapping[prefix]; ok && len(v) > 0 { + prefix = v + } + return strings.ToLower(fmt.Sprintf("%s.%02d-of-%02d", prefix, ordinal, cc.Buckets)) } type statefulSetBuilder struct { diff --git a/leaderelection/context_test.go b/leaderelection/context_test.go index 1732e9557..618bd4965 100644 --- a/leaderelection/context_test.go +++ b/leaderelection/context_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" fakekube "k8s.io/client-go/kubernetes/fake" ktesting "k8s.io/client-go/testing" + "knative.dev/pkg/reconciler" _ "knative.dev/pkg/system/testing" ) @@ -355,3 +356,42 @@ func TestWithUnopposedElector(t *testing.T) { // Wait to see if PromoteFunc is called with nil or our enq function. <-time.After(time.Second) } + +func TestStandardBucketName(t *testing.T) { + tests := []struct { + name string + ordinal uint32 + queueName string + cc ComponentConfig + want string + }{ + { + name: "identity", + ordinal: 0, + queueName: "queue-queue", + cc: ComponentConfig{ + Component: "my-comp", + }, + want: "my-comp.queue-queue.00-of-00", + }, + { + name: "remapping", + ordinal: 0, + queueName: "queue-queue", + cc: ComponentConfig{ + Component: "my-comp", + LeaseNamesPrefixMapping: map[string]string{ + "my-comp.queue-queue": "my-comp-2.queue", + }, + }, + want: "my-comp-2.queue.00-of-00", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := standardBucketName(tt.ordinal, tt.queueName, tt.cc); got != tt.want { + t.Errorf("got %v, want %v", got, tt.want) + } + }) + } +}