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

Validation for MultiClusterService #597

Merged
merged 4 commits into from
Nov 8, 2024
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
26 changes: 24 additions & 2 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ func SetupIndexers(ctx context.Context, mgr ctrl.Manager) error {
return err
}

if err := SetupMultiClusterServiceServicesIndexer(ctx, mgr); err != nil {
return err
}

if err := SetupClusterTemplateChainIndexer(ctx, mgr); err != nil {
return err
}
Expand Down Expand Up @@ -131,10 +135,10 @@ func ExtractReleaseTemplates(rawObj client.Object) []string {
const ServicesTemplateKey = ".spec.services[].Template"

func SetupManagedClusterServicesIndexer(ctx context.Context, mgr ctrl.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, ServicesTemplateKey, ExtractServiceTemplateName)
return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, ServicesTemplateKey, ExtractServiceTemplateFromManagedCluster)
}

func ExtractServiceTemplateName(rawObj client.Object) []string {
func ExtractServiceTemplateFromManagedCluster(rawObj client.Object) []string {
cluster, ok := rawObj.(*ManagedCluster)
if !ok {
return nil
Expand All @@ -148,6 +152,24 @@ func ExtractServiceTemplateName(rawObj client.Object) []string {
return templates
}

func SetupMultiClusterServiceServicesIndexer(ctx context.Context, mgr ctrl.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, &MultiClusterService{}, ServicesTemplateKey, ExtractServiceTemplateFromMultiClusterService)
}

func ExtractServiceTemplateFromMultiClusterService(rawObj client.Object) []string {
cluster, ok := rawObj.(*MultiClusterService)
if !ok {
return nil
}

templates := []string{}
for _, s := range cluster.Spec.Services {
templates = append(templates, s.Template)
}

return templates
}

const SupportedTemplateKey = ".spec.supportedTemplates[].Name"

func SetupClusterTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error {
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/multiclusterservice_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type ServiceStatus struct {
type MultiClusterServiceStatus struct {
// Services contains details for the state of services.
Services []ServiceStatus `json:"services,omitempty"`
// Conditions contains details for the current state of the ManagedCluster
// Conditions contains details for the current state of the MultiClusterService.
Conditions []metav1.Condition `json:"conditions,omitempty"`
// ObservedGeneration is the last observed generation.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
Expand Down
9 changes: 7 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ func main() {
}

if err = (&controller.MultiClusterServiceReconciler{
Client: mgr.GetClient(),
Client: mgr.GetClient(),
SystemNamespace: currentNamespace,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MultiClusterService")
os.Exit(1)
Expand Down Expand Up @@ -331,6 +332,10 @@ func setupWebhooks(mgr ctrl.Manager, currentNamespace string) error {
setupLog.Error(err, "unable to create webhook", "webhook", "ManagedCluster")
return err
}
if err := (&hmcwebhook.MultiClusterServiceValidator{SystemNamespace: currentNamespace}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "MultiClusterService")
return err
}
if err := (&hmcwebhook.ManagementValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Management")
return err
Expand All @@ -351,7 +356,7 @@ func setupWebhooks(mgr ctrl.Manager, currentNamespace string) error {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplate")
return err
}
if err := (&hmcwebhook.ServiceTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
if err := (&hmcwebhook.ServiceTemplateValidator{SystemNamespace: currentNamespace}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ServiceTemplate")
return err
}
Expand Down
44 changes: 41 additions & 3 deletions internal/controller/managedcluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

hmc "github.com/Mirantis/hmc/api/v1alpha1"
Expand All @@ -38,8 +39,9 @@ var _ = Describe("ManagedCluster Controller", func() {
managedClusterName = "test-managed-cluster"
managedClusterNamespace = "test"

templateName = "test-template"
credentialName = "test-credential"
templateName = "test-template"
svcTemplateName = "test-svc-template"
credentialName = "test-credential"
)

ctx := context.Background()
Expand All @@ -50,6 +52,7 @@ var _ = Describe("ManagedCluster Controller", func() {
}
managedCluster := &hmc.ManagedCluster{}
template := &hmc.ClusterTemplate{}
svcTemplate := &hmc.ServiceTemplate{}
management := &hmc.Management{}
credential := &hmc.Credential{}
namespace := &corev1.Namespace{}
Expand All @@ -66,7 +69,7 @@ var _ = Describe("ManagedCluster Controller", func() {
Expect(k8sClient.Create(ctx, namespace)).To(Succeed())
}

By("creating the custom resource for the Kind Template")
By("creating the custom resource for the Kind ClusterTemplate")
err = k8sClient.Get(ctx, typeNamespacedName, template)
if err != nil && errors.IsNotFound(err) {
template = &hmc.ClusterTemplate{
Expand Down Expand Up @@ -99,6 +102,35 @@ var _ = Describe("ManagedCluster Controller", func() {
Expect(k8sClient.Status().Update(ctx, template)).To(Succeed())
}

By("creating the custom resource for the Kind ServiceTemplate")
err = k8sClient.Get(ctx, client.ObjectKey{Namespace: managedClusterNamespace, Name: svcTemplateName}, svcTemplate)
if err != nil && errors.IsNotFound(err) {
svcTemplate = &hmc.ServiceTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: svcTemplateName,
Namespace: managedClusterNamespace,
},
Spec: hmc.ServiceTemplateSpec{
Helm: hmc.HelmSpec{
ChartRef: &hcv2.CrossNamespaceSourceReference{
Kind: "HelmChart",
Name: "ref-test",
Namespace: "default",
},
},
},
}
Expect(k8sClient.Create(ctx, svcTemplate)).To(Succeed())
svcTemplate.Status = hmc.ServiceTemplateStatus{
TemplateStatusCommon: hmc.TemplateStatusCommon{
TemplateValidationStatus: hmc.TemplateValidationStatus{
Valid: true,
},
},
}
Expect(k8sClient.Status().Update(ctx, svcTemplate)).To(Succeed())
}

By("creating the custom resource for the Kind Management")
err = k8sClient.Get(ctx, typeNamespacedName, management)
if err != nil && errors.IsNotFound(err) {
Expand Down Expand Up @@ -148,6 +180,12 @@ var _ = Describe("ManagedCluster Controller", func() {
Spec: hmc.ManagedClusterSpec{
Template: templateName,
Credential: credentialName,
Services: []hmc.ServiceSpec{
{
Template: svcTemplateName,
Name: "test-svc-name",
},
},
},
}
Expect(k8sClient.Create(ctx, managedCluster)).To(Succeed())
Expand Down
11 changes: 6 additions & 5 deletions internal/controller/multiclusterservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
// MultiClusterServiceReconciler reconciles a MultiClusterService object
type MultiClusterServiceReconciler struct {
client.Client
SystemNamespace string
}

// Reconcile reconciles a MultiClusterService object.
Expand Down Expand Up @@ -114,9 +115,9 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs
return ctrl.Result{Requeue: true}, nil
}

// By using DefaultSystemNamespace we are enforcing that MultiClusterService
// may only use ServiceTemplates that are present in the hmc-system namespace.
opts, err := helmChartOpts(ctx, r.Client, utils.DefaultSystemNamespace, mcs.Spec.Services)
// We are enforcing that MultiClusterService may only use
// ServiceTemplates that are present in the system namespace.
opts, err := helmChartOpts(ctx, r.Client, r.SystemNamespace, mcs.Spec.Services)
if err != nil {
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -179,7 +180,7 @@ func helmChartOpts(ctx context.Context, c client.Client, namespace string, servi
// Here we can use the same namespace for all services
// because if the services slice is part of:
// 1. ManagedCluster: Then the referred template must be in its own namespace.
// 2. MultiClusterService: Then the referred template must be in hmc-system namespace.
// 2. MultiClusterService: Then the referred template must be in system namespace.
tmplRef := client.ObjectKey{Name: svc.Template, Namespace: namespace}
if err := c.Get(ctx, tmplRef, tmpl); err != nil {
return nil, fmt.Errorf("failed to get ServiceTemplate %s: %w", tmplRef.String(), err)
Expand Down Expand Up @@ -356,7 +357,7 @@ func requeueSveltosProfileForClusterSummary(ctx context.Context, obj client.Obje

cs, ok := obj.(*sveltosv1beta1.ClusterSummary)
if !ok {
l.Error(errors.New("request is not for a ClusterSummary object"), msg, "ClusterSummary.Name", obj.GetName(), "ClusterSummary.Namespace", obj.GetNamespace())
l.Error(errors.New("request is not for a ClusterSummary object"), msg, "Requested.Name", obj.GetName(), "Requested.Namespace", obj.GetNamespace())
return []ctrl.Request{}
}

Expand Down
53 changes: 28 additions & 25 deletions internal/controller/multiclusterservice_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

hmc "github.com/Mirantis/hmc/api/v1alpha1"
"github.com/Mirantis/hmc/internal/utils"
)

var _ = Describe("MultiClusterService Controller", func() {
Context("When reconciling a resource", func() {
const (
testNamespace = utils.DefaultSystemNamespace
serviceTemplateName = "test-service-0-1-0"
helmRepoName = "test-helmrepo"
helmChartName = "test-helmchart"
Expand Down Expand Up @@ -66,31 +64,31 @@ var _ = Describe("MultiClusterService Controller", func() {
multiClusterService := &hmc.MultiClusterService{}
clusterProfile := &sveltosv1beta1.ClusterProfile{}

helmRepositoryRef := types.NamespacedName{Namespace: testNamespace, Name: helmRepoName}
helmChartRef := types.NamespacedName{Namespace: testNamespace, Name: helmChartName}
serviceTemplateRef := types.NamespacedName{Namespace: testNamespace, Name: serviceTemplateName}
helmRepositoryRef := types.NamespacedName{Namespace: testSystemNamespace, Name: helmRepoName}
helmChartRef := types.NamespacedName{Namespace: testSystemNamespace, Name: helmChartName}
serviceTemplateRef := types.NamespacedName{Namespace: testSystemNamespace, Name: serviceTemplateName}
multiClusterServiceRef := types.NamespacedName{Name: multiClusterServiceName}
clusterProfileRef := types.NamespacedName{Name: multiClusterServiceName}

BeforeEach(func() {
By("creating Namespace")
err := k8sClient.Get(ctx, types.NamespacedName{Name: testNamespace}, namespace)
err := k8sClient.Get(ctx, types.NamespacedName{Name: testSystemNamespace}, namespace)
if err != nil && apierrors.IsNotFound(err) {
namespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: testNamespace,
Name: testSystemNamespace,
},
}
Expect(k8sClient.Create(ctx, namespace)).To(Succeed())
}

By("creating HelmRepository")
err = k8sClient.Get(ctx, types.NamespacedName{Name: helmRepoName, Namespace: testNamespace}, helmRepo)
err = k8sClient.Get(ctx, types.NamespacedName{Name: helmRepoName, Namespace: testSystemNamespace}, helmRepo)
if err != nil && apierrors.IsNotFound(err) {
helmRepo = &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: helmRepoName,
Namespace: testNamespace,
Namespace: testSystemNamespace,
},
Spec: sourcev1.HelmRepositorySpec{
URL: "oci://test/helmrepo",
Expand All @@ -100,12 +98,12 @@ var _ = Describe("MultiClusterService Controller", func() {
}

By("creating HelmChart")
err = k8sClient.Get(ctx, types.NamespacedName{Name: helmChartName, Namespace: testNamespace}, helmChart)
err = k8sClient.Get(ctx, types.NamespacedName{Name: helmChartName, Namespace: testSystemNamespace}, helmChart)
if err != nil && apierrors.IsNotFound(err) {
helmChart = &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: helmChartName,
Namespace: testNamespace,
Namespace: testSystemNamespace,
},
Spec: sourcev1.HelmChartSpec{
SourceRef: sourcev1.LocalHelmChartSourceReference{
Expand All @@ -131,7 +129,7 @@ var _ = Describe("MultiClusterService Controller", func() {
serviceTemplate = &hmc.ServiceTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: serviceTemplateName,
Namespace: testNamespace,
Namespace: testSystemNamespace,
Labels: map[string]string{
hmc.HMCManagedLabelKey: "true",
},
Expand All @@ -142,14 +140,28 @@ var _ = Describe("MultiClusterService Controller", func() {
ChartRef: &helmcontrollerv2.CrossNamespaceSourceReference{
Kind: "HelmChart",
Name: helmChartName,
Namespace: testNamespace,
Namespace: testSystemNamespace,
},
},
},
}
}
Expect(k8sClient.Create(ctx, serviceTemplate)).To(Succeed())

By("reconciling ServiceTemplate used by MultiClusterService")
templateReconciler := TemplateReconciler{
Client: k8sClient,
downloadHelmChartFunc: fakeDownloadHelmChartFunc,
}
serviceTemplateReconciler := &ServiceTemplateReconciler{TemplateReconciler: templateReconciler}
_, err = serviceTemplateReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: serviceTemplateRef})
Expect(err).NotTo(HaveOccurred())

By("having the valid service template status")
Expect(k8sClient.Get(ctx, serviceTemplateRef, serviceTemplate)).To(Succeed())
Expect(serviceTemplate.Status.Valid).To(BeTrue())
Expect(serviceTemplate.Status.ValidationError).To(BeEmpty())

By("creating MultiClusterService")
err = k8sClient.Get(ctx, multiClusterServiceRef, multiClusterService)
if err != nil && apierrors.IsNotFound(err) {
Expand Down Expand Up @@ -181,7 +193,7 @@ var _ = Describe("MultiClusterService Controller", func() {
multiClusterServiceResource := &hmc.MultiClusterService{}
Expect(k8sClient.Get(ctx, multiClusterServiceRef, multiClusterServiceResource)).NotTo(HaveOccurred())

reconciler := &MultiClusterServiceReconciler{Client: k8sClient}
reconciler := &MultiClusterServiceReconciler{Client: k8sClient, SystemNamespace: testSystemNamespace}
Expect(k8sClient.Delete(ctx, multiClusterService)).To(Succeed())
// Running reconcile to remove the finalizer and delete the MultiClusterService
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: multiClusterServiceRef})
Expand All @@ -204,19 +216,10 @@ var _ = Describe("MultiClusterService Controller", func() {
})

It("should successfully reconcile the resource", func() {
By("reconciling ServiceTemplate used by MultiClusterService")
templateReconciler := TemplateReconciler{
Client: k8sClient,
downloadHelmChartFunc: fakeDownloadHelmChartFunc,
}
serviceTemplateReconciler := &ServiceTemplateReconciler{TemplateReconciler: templateReconciler}
_, err := serviceTemplateReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: serviceTemplateRef})
Expect(err).NotTo(HaveOccurred())

By("reconciling MultiClusterService")
multiClusterServiceReconciler := &MultiClusterServiceReconciler{Client: k8sClient}
multiClusterServiceReconciler := &MultiClusterServiceReconciler{Client: k8sClient, SystemNamespace: testSystemNamespace}

_, err = multiClusterServiceReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: multiClusterServiceRef})
_, err := multiClusterServiceReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: multiClusterServiceRef})
Expect(err).NotTo(HaveOccurred())

Eventually(k8sClient.Get, 1*time.Minute, 5*time.Second).WithArguments(ctx, clusterProfileRef, clusterProfile).ShouldNot(HaveOccurred())
Expand Down
6 changes: 5 additions & 1 deletion internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
const (
mutatingWebhookKind = "MutatingWebhookConfiguration"
validatingWebhookKind = "ValidatingWebhookConfiguration"
testSystemNamespace = "test-system-namespace"
)

var (
Expand Down Expand Up @@ -150,6 +151,9 @@ var _ = BeforeSuite(func() {
err = (&hmcwebhook.ManagedClusterValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.MultiClusterServiceValidator{SystemNamespace: testSystemNamespace}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ManagementValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

Expand All @@ -165,7 +169,7 @@ var _ = BeforeSuite(func() {
err = (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ServiceTemplateValidator{}).SetupWebhookWithManager(mgr)
err = (&hmcwebhook.ServiceTemplateValidator{SystemNamespace: testSystemNamespace}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ProviderTemplateValidator{}).SetupWebhookWithManager(mgr)
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/template_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ var _ = Describe("Template Controller", func() {

By("Having the valid cluster template status")
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterTemplate), clusterTemplate)).To(Succeed())
Expect(clusterTemplate.Status.Valid && clusterTemplate.Status.ValidationError == "").To(BeTrue())
Expect(clusterTemplate.Status.Valid).To(BeTrue())
Expect(clusterTemplate.Status.ValidationError).To(BeEmpty())
Expect(clusterTemplate.Status.Providers).To(HaveLen(2))
Expect(clusterTemplate.Status.ProviderContracts).To(HaveLen(2))
Expect(clusterTemplate.Status.Providers[0]).To(Equal(someProviderName))
Expand Down
Loading