Skip to content
Open
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
18 changes: 5 additions & 13 deletions pkg/bootstrap/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,20 +179,12 @@ func (c *KlusterletManifestsConfig) Generate(ctx context.Context,
var kcImagePullSecret corev1.ObjectReference
var appliedManifestWorkEvictionGracePeriod string

switch installMode {
case operatorv1.InstallModeHosted, operatorv1.InstallModeSingletonHosted:
// do nothing
case operatorv1.InstallModeDefault, operatorv1.InstallModeSingleton:
if c.klusterletConfig != nil {
kcRegistries = c.klusterletConfig.Spec.Registries
kcNodePlacement = c.klusterletConfig.Spec.NodePlacement
kcImagePullSecret = c.klusterletConfig.Spec.PullSecret
appliedManifestWorkEvictionGracePeriod = c.klusterletConfig.Spec.AppliedManifestWorkEvictionGracePeriod
}
default:
return nil, nil, nil, fmt.Errorf("invalid install mode: %s", installMode)
if c.klusterletConfig != nil {
kcRegistries = c.klusterletConfig.Spec.Registries
kcNodePlacement = c.klusterletConfig.Spec.NodePlacement
kcImagePullSecret = c.klusterletConfig.Spec.PullSecret
appliedManifestWorkEvictionGracePeriod = c.klusterletConfig.Spec.AppliedManifestWorkEvictionGracePeriod
}

c.chartConfig.NoOperator = installNoOperator(installMode, c.klusterletConfig)

var managedClusterAnnotations map[string]string
Expand Down
145 changes: 145 additions & 0 deletions pkg/bootstrap/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,151 @@ func TestKlusterletConfigGenerate(t *testing.T) {
// Should not be called for error cases
},
},
{
name: "hosted with nodePlacement from klusterletConfig",
clientObjs: []runtimeclient.Object{
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
},
},
defaultImagePullSecret: "test-image-pull-secret",
runtimeObjs: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-image-pull-secret",
},
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte("fake-token"),
},
Type: corev1.SecretTypeDockerConfigJson,
},
},
config: NewKlusterletManifestsConfig(
operatorv1.InstallModeHosted,
"test", // cluster name
[]byte("bootstrap kubeconfig"),
).WithoutImagePullSecretGenerate().WithKlusterletConfig(&klusterletconfigv1alpha1.KlusterletConfig{
Spec: klusterletconfigv1alpha1.KlusterletConfigSpec{
NodePlacement: &operatorv1.NodePlacement{
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
Tolerations: []corev1.Toleration{
{
Key: "node.kubernetes.io/hosted",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoExecute,
TolerationSeconds: &tolerationSeconds,
},
},
},
},
}),
validateFunc: func(t *testing.T, objects, crds []runtime.Object) {
testinghelpers.ValidateObjectCount(t, objects, 3)
testinghelpers.ValidateCRDs(t, crds, 0)
testinghelpers.ValidateKlusterlet(t, objects[2], operatorv1.InstallModeHosted,
"klusterlet-test", "test", "open-cluster-management-test")
klusterlet, _ := objects[2].(*operatorv1.Klusterlet)
if klusterlet.Spec.NodePlacement.NodeSelector["kubernetes.io/os"] != "linux" {
t.Errorf("the klusterlet node selector %s is not %s",
klusterlet.Spec.NodePlacement.NodeSelector["kubernetes.io/os"], "linux")
}
if klusterlet.Spec.NodePlacement.Tolerations[0].Key != "node.kubernetes.io/hosted" {
t.Errorf("the klusterlet tolerations %s is not %s",
klusterlet.Spec.NodePlacement.Tolerations[0].Key, "node.kubernetes.io/hosted")
}
},
},
{
name: "hosted with pullSecret and registries from klusterletConfig",
clientObjs: []runtimeclient.Object{
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
},
},
defaultImagePullSecret: "test-image-pull-secret",
runtimeObjs: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-pull-secret",
Namespace: "default",
},
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte("custom-fake-token"),
},
Type: corev1.SecretTypeDockerConfigJson,
},
},
config: NewKlusterletManifestsConfig(
operatorv1.InstallModeHosted,
"test", // cluster name
[]byte("bootstrap kubeconfig"),
).WithKlusterletConfig(&klusterletconfigv1alpha1.KlusterletConfig{
Spec: klusterletconfigv1alpha1.KlusterletConfigSpec{
PullSecret: corev1.ObjectReference{
Name: "custom-pull-secret",
Namespace: "default",
},
Registries: []klusterletconfigv1alpha1.Registries{
{
Source: "quay.io/open-cluster-management",
Mirror: "quay.io/rhacm2",
},
{
Source: "quay.io/stolostron",
Mirror: "quay.io/rhacm2",
},
},
},
}),
validateFunc: func(t *testing.T, objects, crds []runtime.Object) {
testinghelpers.ValidateObjectCount(t, objects, 4)
testinghelpers.ValidateCRDs(t, crds, 0)

// Find the klusterlet object
var klusterlet *operatorv1.Klusterlet
var imagePullSecretIdx int
for i, obj := range objects {
if k, ok := obj.(*operatorv1.Klusterlet); ok {
klusterlet = k
}
if s, ok := obj.(*corev1.Secret); ok {
if s.Type == corev1.SecretTypeDockerConfigJson {
imagePullSecretIdx = i
}
}
}

if klusterlet == nil {
t.Fatal("klusterlet not found in objects")
}

// Verify klusterlet properties
if klusterlet.Name != "klusterlet-test" {
t.Errorf("expected klusterlet name klusterlet-test, got %s", klusterlet.Name)
}
if klusterlet.Spec.ClusterName != "test" {
t.Errorf("expected cluster name test, got %s", klusterlet.Spec.ClusterName)
}

// Verify that custom registries are applied
if !strings.HasPrefix(klusterlet.Spec.RegistrationImagePullSpec, "quay.io/rhacm2/registration") {
t.Errorf("the klusterlet registration image pull spec %s does not use custom registry",
klusterlet.Spec.RegistrationImagePullSpec)
}
if !strings.HasPrefix(klusterlet.Spec.WorkImagePullSpec, "quay.io/rhacm2/work") {
t.Errorf("the klusterlet work image pull spec %s does not use custom registry",
klusterlet.Spec.WorkImagePullSpec)
}
// Verify that custom pull secret is applied
testinghelpers.ValidateImagePullSecret(t, objects[imagePullSecretIdx], "open-cluster-management-test", "custom-fake-token")
},
},
}

for _, testcase := range testcases {
Expand Down
83 changes: 83 additions & 0 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1272,3 +1272,86 @@ func assertClusterImportConfigSecret(managedClusterName string) {
}, 30*time.Second, 1*time.Second).Should(gomega.Succeed())
})
}

func assertHostedKlusterletNodePlacement(klusterletName string, nodeSelector map[string]string, tolerations []corev1.Toleration) {
ginkgo.By("Hosted klusterlet should have expected nodePlacement", func() {
gomega.Eventually(func() error {
name := fmt.Sprintf("%s-import", klusterletName)
// Get the managed cluster name from klusterlet name (format: klusterlet-<managedClusterName>)
managedClusterName := strings.TrimPrefix(klusterletName, "klusterlet-")
secret, err := hubKubeClient.CoreV1().Secrets(managedClusterName).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return err
}

var klusterlet *operatorv1.Klusterlet
for _, yaml := range helpers.SplitYamls(secret.Data[constants.ImportSecretImportYamlKey]) {
obj := helpers.MustCreateObject(yaml)
switch required := obj.(type) {
case *operatorv1.Klusterlet:
klusterlet = required
}
}
if klusterlet == nil {
return fmt.Errorf("klusterlet is not found in import.yaml")
}

if !equality.Semantic.DeepEqual(klusterlet.Spec.NodePlacement.NodeSelector, nodeSelector) {
return fmt.Errorf("klusterlet nodePlacement diff: %s", cmp.Diff(klusterlet.Spec.NodePlacement.NodeSelector, nodeSelector))
}

if !equality.Semantic.DeepEqual(klusterlet.Spec.NodePlacement.Tolerations, tolerations) {
return fmt.Errorf("klusterlet tolerations diff: %s", cmp.Diff(klusterlet.Spec.NodePlacement.Tolerations, tolerations))
}

return nil
}, 60*time.Second, 1*time.Second).Should(gomega.Succeed())
})
}

func assertHostedImagePullSecretAndRegistry(managedClusterName string) {
ginkgo.By("Hosted klusterlet should have image pull secret and customized registry", func() {
gomega.Eventually(func() error {
name := fmt.Sprintf("%s-import", managedClusterName)
secret, err := hubKubeClient.CoreV1().Secrets(managedClusterName).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return err
}

importYaml, ok := secret.Data["import.yaml"]
if !ok {
return fmt.Errorf("import.yaml not found in secret")
}

objs := util.ToImportResoruces(importYaml)

hasImagePullCredentials := false
hasCustomizedImage := false
for _, obj := range objs {
if obj.GetName() == "open-cluster-management-image-pull-credentials" && obj.GetKind() == "Secret" {
hasImagePullCredentials = true
}

if obj.GetName() == fmt.Sprintf("klusterlet-%s", managedClusterName) && obj.GetKind() == "Klusterlet" {
klusterlet := util.ToKlusterlet(obj)
if klusterlet == nil {
return fmt.Errorf("failed to convert to klusterlet")
}
if strings.HasPrefix(klusterlet.Spec.WorkImagePullSpec, "quay.io/rhacm2/work") &&
strings.HasPrefix(klusterlet.Spec.RegistrationImagePullSpec, "quay.io/rhacm2/registration") {
hasCustomizedImage = true
}
}
}

if !hasImagePullCredentials {
return fmt.Errorf("image pull credentials secret not found")
}
if !hasCustomizedImage {
return fmt.Errorf("customized image registry not found")
}

return nil
}, 60*time.Second, 1*time.Second).Should(gomega.Succeed())
})
}
Loading
Loading