Skip to content

Commit a9184ed

Browse files
authored
chore: add managed by label to namespace (#604)
# Description When creating a namespace object in Kubernetes, add label “{prefix}/managed: true”. This will help when we want to modify resources created by CaraML services in a shared cluster, e.g. configuring log for the managed namespace only By default the namespace prefix would `caraml.dev/`, and it can be configured in the config yaml # Modifications - As the creation of label for Kubernetes’ object in Merlin previously is bind to metadata struct, while namespace doesn’t have metada, in this changes the functionality of labelling (labeller) is moved to it’s own package - Add one more prefix in `InitKubernetesLabeller` initiation - Add the `{prefix}/managed: true` when creating namespace # Tests # Checklist - [x] Added PR label - [ ] Added unit test, integration, and/or e2e tests - [x] Tested locally - [ ] Updated documentation - [ ] Update Swagger spec if the PR introduce API changes - [ ] Regenerated Golang and Python client if the PR introduces API changes # Release Notes ```release-note ```
1 parent fa928c3 commit a9184ed

File tree

18 files changed

+203
-133
lines changed

18 files changed

+203
-133
lines changed

api/batch/resource_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919

2020
"github.com/GoogleCloudPlatform/spark-on-k8s-operator/pkg/apis/sparkoperator.k8s.io/v1beta2"
21+
"github.com/caraml-dev/merlin/cluster/labeller"
2122
"github.com/stretchr/testify/assert"
2223
v12 "k8s.io/api/core/v1"
2324
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -134,11 +135,11 @@ var (
134135
)
135136

136137
func TestCreateSparkApplicationResource(t *testing.T) {
137-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
138+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
138139
assert.NoError(t, err)
139140

140141
defer func() {
141-
_ = models.InitKubernetesLabeller("", "")
142+
_ = labeller.InitKubernetesLabeller("", "", "")
142143
}()
143144

144145
tests := []struct {

api/cluster/controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"testing"
2222
"time"
2323

24+
"github.com/caraml-dev/merlin/cluster/labeller"
2425
kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1"
2526
fakekserve "github.com/kserve/kserve/pkg/client/clientset/versioned/fake"
2627
fakekservev1beta1 "github.com/kserve/kserve/pkg/client/clientset/versioned/typed/serving/v1beta1/fake"
@@ -759,7 +760,7 @@ func TestController_DeployInferenceService(t *testing.T) {
759760
}
760761

761762
func TestController_DeployInferenceService_PodDisruptionBudgetsRemoval(t *testing.T) {
762-
err := models.InitKubernetesLabeller("gojek.com/", "dev")
763+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", "dev")
763764
assert.Nil(t, err)
764765

765766
minAvailablePercentage := 80

api/cluster/labeller/labeller.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package labeller
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
)
7+
8+
const (
9+
LabelAppName = "app"
10+
LabelComponent = "component"
11+
LabelEnvironment = "environment"
12+
LabelOrchestratorName = "orchestrator"
13+
LabelStreamName = "stream"
14+
LabelTeamName = "team"
15+
LabelManaged = "managed"
16+
)
17+
18+
var reservedKeys = map[string]bool{
19+
LabelAppName: true,
20+
LabelComponent: true,
21+
LabelEnvironment: true,
22+
LabelOrchestratorName: true,
23+
LabelStreamName: true,
24+
LabelTeamName: true,
25+
}
26+
27+
var (
28+
prefix string
29+
nsPrefix string
30+
environment string
31+
validPrefixRegex = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?(/)$")
32+
)
33+
34+
// InitKubernetesLabeller builds a new KubernetesLabeller Singleton
35+
func InitKubernetesLabeller(p, ns, e string) error {
36+
if err := isValidPrefix(p, "prefix"); err != nil {
37+
return err
38+
}
39+
if err := isValidPrefix(ns, "namespace prefix"); err != nil {
40+
return err
41+
}
42+
43+
prefix = p
44+
nsPrefix = ns
45+
environment = e
46+
return nil
47+
}
48+
49+
// isValidPrefix checks if the given prefix is valid
50+
func isValidPrefix(prefix, name string) error {
51+
if len(prefix) > 253 {
52+
return fmt.Errorf("length of %s is greater than 253 characters", name)
53+
}
54+
if isValidPrefix := validPrefixRegex.MatchString(prefix); !isValidPrefix {
55+
return fmt.Errorf("%s name violates kubernetes label's prefix constraint", name)
56+
}
57+
return nil
58+
}
59+
60+
// GetLabelName prefixes the label with the config specified label and returns the formatted label prefix
61+
func GetLabelName(name string) string {
62+
return fmt.Sprintf("%s%s", prefix, name)
63+
}
64+
65+
// GetNamespaceLabel prefixes the label with the config specified namespace label and returns the formatted label prefix
66+
func GetNamespaceLabel(name string) string {
67+
return fmt.Sprintf("%s%s", nsPrefix, name)
68+
}
69+
70+
// GetPrefix returns the labeller prefix
71+
func GetPrefix() string {
72+
return prefix
73+
}
74+
75+
// GetEnvironment returns the environment
76+
func GetEnvironment() string {
77+
return environment
78+
}
79+
80+
// IsReservedKey checks if the key is a reserved key
81+
func IsReservedKey(key string) bool {
82+
_, usingReservedKeys := reservedKeys[key]
83+
return usingReservedKeys
84+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package labeller
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestInitKubernetesLabeller(t *testing.T) {
10+
defaultNsPrefix := "caraml.dev/"
11+
err := InitKubernetesLabeller("gojek.com/", "caraml.dev/", "dev")
12+
assert.NoError(t, err)
13+
14+
defer func() {
15+
_ = InitKubernetesLabeller("", "", "")
16+
}()
17+
18+
tests := []struct {
19+
prefix string
20+
wantErr bool
21+
}{
22+
{
23+
"gojek.com/",
24+
false,
25+
},
26+
{
27+
"model.caraml.dev/",
28+
false,
29+
},
30+
{
31+
"goto/gojek",
32+
true,
33+
},
34+
{
35+
"gojek",
36+
true,
37+
},
38+
{
39+
"gojek.com/caraml",
40+
true,
41+
},
42+
{
43+
"gojek//",
44+
true,
45+
},
46+
{
47+
"gojek.com//",
48+
true,
49+
},
50+
{
51+
"//gojek.com",
52+
true,
53+
},
54+
}
55+
for _, tt := range tests {
56+
t.Run(tt.prefix, func(t *testing.T) {
57+
if err := InitKubernetesLabeller(tt.prefix, defaultNsPrefix, "dev"); (err != nil) != tt.wantErr {
58+
t.Errorf("InitKubernetesLabeller() error = %v, wantErr %v", err, tt.wantErr)
59+
}
60+
})
61+
}
62+
}

api/cluster/namespace.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package cluster
1717
import (
1818
"context"
1919
"fmt"
20+
"strconv"
2021
"time"
2122

23+
"github.com/caraml-dev/merlin/cluster/labeller"
2224
corev1 "k8s.io/api/core/v1"
2325
kerrors "k8s.io/apimachinery/pkg/api/errors"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -59,6 +61,9 @@ func (k *namespaceCreator) CreateNamespace(ctx context.Context, namespace string
5961
return k.Namespaces().Create(ctx, &corev1.Namespace{
6062
ObjectMeta: metav1.ObjectMeta{
6163
Name: namespace,
64+
Labels: map[string]string{
65+
labeller.GetNamespaceLabel(labeller.LabelManaged): strconv.FormatBool(true),
66+
},
6267
},
6368
}, metav1.CreateOptions{})
6469
}

api/cluster/pdb_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"reflect"
55
"testing"
66

7+
"github.com/caraml-dev/merlin/cluster/labeller"
78
"github.com/stretchr/testify/assert"
89
policyv1 "k8s.io/api/policy/v1"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -14,7 +15,7 @@ import (
1415
)
1516

1617
func Test_NewPodDisruptionBudget(t *testing.T) {
17-
err := models.InitKubernetesLabeller("gojek.com/", "dev")
18+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", "dev")
1819
assert.Nil(t, err)
1920

2021
twenty := 20
@@ -190,7 +191,7 @@ func TestPodDisruptionBudget_BuildPDBSpec(t *testing.T) {
190191
}
191192

192193
func Test_generatePDBSpecs(t *testing.T) {
193-
err := models.InitKubernetesLabeller("gojek.com/", "dev")
194+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", "dev")
194195
assert.Nil(t, err)
195196

196197
ten, twenty := 10, 20

api/cluster/resource/templater_gpu_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/caraml-dev/merlin/cluster/labeller"
78
kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1"
89
kserveconstant "github.com/kserve/kserve/pkg/constants"
910
"github.com/stretchr/testify/assert"
@@ -66,11 +67,11 @@ var (
6667
)
6768

6869
func TestCreateInferenceServiceSpecWithGPU(t *testing.T) {
69-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
70+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
7071
assert.NoError(t, err)
7172

7273
defer func() {
73-
_ = models.InitKubernetesLabeller("", "")
74+
_ = labeller.InitKubernetesLabeller("", "", "")
7475
}()
7576

7677
project := mlp.Project{

api/cluster/resource/templater_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"testing"
2424
"time"
2525

26+
"github.com/caraml-dev/merlin/cluster/labeller"
2627
kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1"
2728
kserveconstant "github.com/kserve/kserve/pkg/constants"
2829
"github.com/stretchr/testify/assert"
@@ -206,11 +207,11 @@ var (
206207
)
207208

208209
func TestCreateInferenceServiceSpec(t *testing.T) {
209-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
210+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
210211
assert.NoError(t, err)
211212

212213
defer func() {
213-
_ = models.InitKubernetesLabeller("", "")
214+
_ = labeller.InitKubernetesLabeller("", "", "")
214215
}()
215216

216217
project := mlp.Project{
@@ -1935,11 +1936,11 @@ func TestCreateInferenceServiceSpec(t *testing.T) {
19351936
}
19361937

19371938
func TestCreateInferenceServiceSpecWithTransformer(t *testing.T) {
1938-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
1939+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
19391940
assert.NoError(t, err)
19401941

19411942
defer func() {
1942-
_ = models.InitKubernetesLabeller("", "")
1943+
_ = labeller.InitKubernetesLabeller("", "", "")
19431944
}()
19441945

19451946
project := mlp.Project{
@@ -2897,11 +2898,11 @@ func TestCreateInferenceServiceSpecWithTransformer(t *testing.T) {
28972898
}
28982899

28992900
func TestCreateInferenceServiceSpecWithLogger(t *testing.T) {
2900-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
2901+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
29012902
assert.NoError(t, err)
29022903

29032904
defer func() {
2904-
_ = models.InitKubernetesLabeller("", "")
2905+
_ = labeller.InitKubernetesLabeller("", "", "")
29052906
}()
29062907

29072908
project := mlp.Project{
@@ -3390,11 +3391,11 @@ func TestCreateInferenceServiceSpecWithLogger(t *testing.T) {
33903391
}
33913392

33923393
func TestCreateInferenceServiceSpecWithTopologySpreadConstraints(t *testing.T) {
3393-
err := models.InitKubernetesLabeller("gojek.com/", testEnvironmentName)
3394+
err := labeller.InitKubernetesLabeller("gojek.com/", "caraml.dev/", testEnvironmentName)
33943395
assert.NoError(t, err)
33953396

33963397
defer func() {
3397-
_ = models.InitKubernetesLabeller("", "")
3398+
_ = labeller.InitKubernetesLabeller("", "", "")
33983399
}()
33993400

34003401
project := mlp.Project{

api/cmd/api/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"syscall"
2727
"time"
2828

29+
"github.com/caraml-dev/merlin/cluster/labeller"
2930
mlflowDelete "github.com/caraml-dev/mlp/api/pkg/client/mlflow"
3031
"github.com/caraml-dev/mlp/api/pkg/instrumentation/sentry"
3132
webhookManager "github.com/caraml-dev/mlp/api/pkg/webhooks"
@@ -41,7 +42,6 @@ import (
4142
"github.com/caraml-dev/merlin/database"
4243
"github.com/caraml-dev/merlin/log"
4344
mlflow "github.com/caraml-dev/merlin/mlflow"
44-
"github.com/caraml-dev/merlin/models"
4545
"github.com/caraml-dev/merlin/pkg/gitlab"
4646
"github.com/caraml-dev/merlin/pkg/observability/event"
4747
"github.com/caraml-dev/merlin/queue"
@@ -264,7 +264,7 @@ func buildDependencies(ctx context.Context, cfg *config.Config, db *gorm.DB, dis
264264
mlpAPIClient := initMLPAPIClient(ctx, cfg.MlpAPIConfig)
265265
coreClient := initFeastCoreClient(cfg.StandardTransformerConfig.FeastCoreURL, cfg.StandardTransformerConfig.FeastCoreAuthAudience, cfg.StandardTransformerConfig.EnableAuth)
266266

267-
if err := models.InitKubernetesLabeller(cfg.DeploymentLabelPrefix, cfg.Environment); err != nil {
267+
if err := labeller.InitKubernetesLabeller(cfg.DeploymentLabelPrefix, cfg.NamespaceLabelPrefix, cfg.Environment); err != nil {
268268
log.Panicf("invalid deployment label prefix (%s): %s", cfg.DeploymentLabelPrefix, err)
269269
}
270270

api/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type Config struct {
5454
NumOfQueueWorkers int `validate:"required" default:"2"`
5555
SwaggerPath string `validate:"required" default:"./swagger.yaml"`
5656

57+
NamespaceLabelPrefix string `validate:"required" default:"caraml.dev/"`
5758
DeploymentLabelPrefix string `validate:"required" default:"gojek.com/"`
5859
PyfuncGRPCOptions string `validate:"required" default:"{}"`
5960

0 commit comments

Comments
 (0)