Skip to content

Commit

Permalink
feat: introduces conditional control plane installation (#10)
Browse files Browse the repository at this point in the history
* chore: extracts smcp GVR to a var

and reuse it in smcp-related funcs

* fix: returns err on oauth creation failure

instead of nil by mistake

* feat: adds ability to enable feature based on predicates

in certain cases this information can only be determined at runtime, for example based on user-defined values of the plugin spec.

* feat: introduces control plane installation

coupled with wait condition

* fix: makes feature enabled by default

* chore: reworks smcp creation

It relies on owner reference so can be cleaned up as other resources

* fix: applies cleanups only if feature is enabled

Additionally moves OssmResourceTracker creation to .Apply func

* feat: reworks plugin integration to use Apply and Delete funcs of KfApp

This way we can cleanup the resources in case deploying service mesh manifests because creating those in Generate phase leaves them hanging due to failing Delete hook

* fix(reconcile): corrects KfDef status in case of any error

Additionally propagates the error through reconcile to keep trying, as it was ignored.

* fix: reworks SMCP component readiness check

* chore: improves KfApp func docs

* chore: reworks logging in verifiers

* chore: simplifies CRD existence check

* chore: adds tests for feature enablement

* fix: sets interval for SMCP polling to 5s

* fix: handles types which are derived for built-in ones

for example `type InstallationMode string` was previously failing when
converting the default value defined in a custom tag. Now there's a
check performed and if value can be convereted to a given type it will.

In all other cases it will return an error.

* fix: sets default control plane installation mode rely on existing
installation

Proposed installation modes are:

- minimal
- pre-installed
  • Loading branch information
bartoszmajsak committed Sep 6, 2023
1 parent 1f4f101 commit 2b92731
Show file tree
Hide file tree
Showing 17 changed files with 415 additions and 134 deletions.
22 changes: 19 additions & 3 deletions apis/ossm.plugins.kubeflow.org/v1alpha1/ossm_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,27 @@ type OssmPluginSpec struct {
Auth AuthSpec `json:"auth,omitempty"`
}

// InstallationMode defines how the plugin should handle OpenShift Service Mesh installation.
// If not specified `use-existing` is assumed.
type InstallationMode string

var (
// PreInstalled indicates that KfDef plugin for Openshift Service Mesh will use existing
// installation and patch Service Mesh Control Plane.
PreInstalled InstallationMode = "pre-installed"

// Minimal results in installing Openshift Service Mesh Control Plane
// in defined namespace with minimal required configuration.
Minimal InstallationMode = "minimal"
)

// MeshSpec holds information on how Service Mesh should be configured.
type MeshSpec struct {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Certificate CertSpec `json:"certificate,omitempty"`
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
// +kubebuilder:validation:Enum=minimal;pre-installed
InstallationMode InstallationMode `json:"installationMode,omitempty"`
Certificate CertSpec `json:"certificate,omitempty"`
}

type CertSpec struct {
Expand Down
8 changes: 8 additions & 0 deletions config/crd/bases/ossm.plugins.kubeflow.org_ossmplugins.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ spec:
name:
type: string
type: object
installationMode:
description: InstallationMode defines how the plugin should handle
OpenShift Service Mesh installation. If not specified `use-existing`
is assumed.
enum:
- minimal
- pre-installed
type: string
name:
type: string
namespace:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ spec:
name:
type: string
type: object
installationMode:
description: InstallationMode defines how the plugin should handle
OpenShift Service Mesh installation. If not specified `use-existing`
is assumed.
type: string
name:
type: string
namespace:
Expand Down
7 changes: 3 additions & 4 deletions controllers/kfdef.apps.kubeflow.org/kfdef_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,11 @@ func (r *KfDefReconciler) Reconcile(ctx context.Context, request ctrl.Request) (
}

// set status of the KfDef resource
if err := r.reconcileStatus(instance); err != nil {
return ctrl.Result{}, err
if reconcileError := r.reconcileStatus(instance); reconcileError != nil {
return ctrl.Result{}, reconcileError
}

// If deployment created successfully - don't requeue
return ctrl.Result{}, nil
return ctrl.Result{}, err
}

// SetupWithManager sets up the controller with the Manager.
Expand Down
6 changes: 5 additions & 1 deletion controllers/kfdef.apps.kubeflow.org/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ func (r *KfDefReconciler) reconcileStatus(cr *kfdefv1.KfDef) error {
func getReconcileStatus(cr *kfdefv1.KfDef, err error) error {
conditions := []kfdefv1.KfDefCondition{}

availabilityStatus := corev1.ConditionTrue

if err != nil {
availabilityStatus = corev1.ConditionFalse

conditions = append(conditions, kfdefv1.KfDefCondition{
LastUpdateTime: cr.CreationTimestamp,
Status: corev1.ConditionTrue,
Expand All @@ -51,7 +55,7 @@ func getReconcileStatus(cr *kfdefv1.KfDef, err error) error {

conditions = append(conditions, kfdefv1.KfDefCondition{
LastUpdateTime: cr.CreationTimestamp,
Status: corev1.ConditionTrue,
Status: availabilityStatus,
Reason: DeploymentCompleted,
Type: kfdefv1.KfAvailable,
})
Expand Down
25 changes: 22 additions & 3 deletions pkg/kfapp/coordinator/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,22 @@ func (kfapp *coordinator) Apply(resources kftypesv3.ResourceEnum) error {
}
}

serviceMeshConfig := func() error {
if kfapp.KfDef.Spec.Platform != kftypesv3.OSSM {
return nil
}

if p, ok := kfapp.Platforms[kfapp.KfDef.Spec.Platform]; !ok {
return &kfapis.KfError{
Code: int(kfapis.INTERNAL_ERROR),
Message: "Platform OSSM specified but not loaded.",
}
} else {
ossmInstaller := p.(*ossm.OssmInstaller)
return ossmInstaller.Apply(kftypesv3.K8S)
}
}

if err := kfapp.KfDef.SyncCache(); err != nil {
return &kfapis.KfError{
Code: int(kfapis.INTERNAL_ERROR),
Expand All @@ -423,6 +439,9 @@ func (kfapp *coordinator) Apply(resources kftypesv3.ResourceEnum) error {
case kftypesv3.PLATFORM:
return platform()
case kftypesv3.K8S:
if err := serviceMeshConfig(); err != nil {
return err
}
if err := k8s(); err != nil {
return err
}
Expand Down Expand Up @@ -471,7 +490,7 @@ func (kfapp *coordinator) Delete(resources kftypesv3.ResourceEnum) error {
return nil
}

ossmCleanup := func() error {
serviceMeshCleanup := func() error {
if kfapp.KfDef.Spec.Platform != kftypesv3.OSSM {
return nil
}
Expand All @@ -483,7 +502,7 @@ func (kfapp *coordinator) Delete(resources kftypesv3.ResourceEnum) error {
}
} else {
ossmInstaller := p.(*ossm.OssmInstaller)
return ossmInstaller.CleanupResources()
return ossmInstaller.Delete(kftypesv3.K8S)
}
}

Expand Down Expand Up @@ -515,7 +534,7 @@ func (kfapp *coordinator) Delete(resources kftypesv3.ResourceEnum) error {
if err := k8s(); err != nil {
return err
}
return ossmCleanup()
return serviceMeshCleanup()
}
return nil
}
Expand Down
34 changes: 31 additions & 3 deletions pkg/kfapp/ossm/feature/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package feature
import (
"github.com/opendatahub-io/opendatahub-operator/pkg/kfconfig/ossmplugin"
"github.com/pkg/errors"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -53,6 +54,10 @@ func (fb *featureBuilder) UsingConfig(config *rest.Config) *featureBuilder {
return errors.WithStack(err)
}

if err := apiextv1.AddToScheme(f.client.Scheme()); err != nil {
return err
}

return nil
})

Expand Down Expand Up @@ -99,6 +104,16 @@ func (fb *featureBuilder) Preconditions(preconditions ...action) *featureBuilder
return fb
}

func (fb *featureBuilder) Postconditions(postconditions ...action) *featureBuilder {
fb.builders = append(fb.builders, func(f *Feature) error {
f.postconditions = append(f.postconditions, postconditions...)

return nil
})

return fb
}

func (fb *featureBuilder) OnDelete(cleanups ...action) *featureBuilder {
fb.builders = append(fb.builders, func(f *Feature) error {
f.addCleanup(cleanups...)
Expand All @@ -119,9 +134,20 @@ func (fb *featureBuilder) WithResources(resources ...action) *featureBuilder {
return fb
}

func (fb *featureBuilder) EnabledIf(enabled func(f *Feature) bool) *featureBuilder {
fb.builders = append(fb.builders, func(f *Feature) error {
f.Enabled = enabled(f)

return nil

})
return fb
}

func (fb *featureBuilder) Load() (*Feature, error) {
feature := &Feature{
Name: fb.name,
Name: fb.name,
Enabled: true,
}

for i := range fb.builders {
Expand All @@ -130,8 +156,10 @@ func (fb *featureBuilder) Load() (*Feature, error) {
}
}

if err := feature.createResourceTracker(); err != nil {
return nil, err
if feature.Enabled {
if err := feature.createResourceTracker(); err != nil {
return feature, err
}
}

return feature, nil
Expand Down
36 changes: 14 additions & 22 deletions pkg/kfapp/ossm/feature/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)

var smcpGVR = schema.GroupVersionResource{
Group: "maistra.io",
Version: "v2",
Resource: "servicemeshcontrolplanes",
}

func RemoveTokenVolumes(feature *Feature) error {
tokenVolume := fmt.Sprintf("%s-oauth2-tokens", feature.Spec.AppNamespace)

gvr := schema.GroupVersionResource{
Group: "maistra.io",
Version: "v2",
Resource: "servicemeshcontrolplanes",
}

meshNs := feature.Spec.Mesh.Namespace
meshName := feature.Spec.Mesh.Name

smcp, err := feature.dynamicClient.Resource(gvr).Namespace(meshNs).Get(context.Background(), meshName, metav1.GetOptions{})
smcp, err := feature.dynamicClient.Resource(smcpGVR).Namespace(meshNs).Get(context.Background(), meshName, metav1.GetOptions{})
if err != nil {
return err
}
Expand All @@ -37,7 +37,7 @@ func RemoveTokenVolumes(feature *Feature) error {
for i, v := range volumes {
volume, ok := v.(map[string]interface{})
if !ok {
fmt.Println("Unexpected type for volume")
log.Info("unexpected type for volume", "type", fmt.Sprintf("%T", volume))
continue
}

Expand All @@ -46,7 +46,7 @@ func RemoveTokenVolumes(feature *Feature) error {
return err
}
if !found {
fmt.Println("No volumeMount found in the volume")
log.Info("no volumeMount found in the volume")
continue
}

Expand All @@ -60,12 +60,9 @@ func RemoveTokenVolumes(feature *Feature) error {
}
}

_, err = feature.dynamicClient.Resource(gvr).Namespace(meshNs).Update(context.Background(), smcp, metav1.UpdateOptions{})
if err != nil {
return err
}
_, err = feature.dynamicClient.Resource(smcpGVR).Namespace(meshNs).Update(context.Background(), smcp, metav1.UpdateOptions{})

return nil
return err
}

func RemoveOAuthClient(feature *Feature) error {
Expand All @@ -86,6 +83,7 @@ func RemoveOAuthClient(feature *Feature) error {

if err := feature.dynamicClient.Resource(gvr).Delete(context.Background(), oauthClientName, metav1.DeleteOptions{}); err != nil {
log.Error(err, "failed deleting OAuthClient", "name", oauthClientName)

return err
}

Expand All @@ -95,15 +93,9 @@ func RemoveOAuthClient(feature *Feature) error {
func RemoveExtensionProvider(feature *Feature) error {
ossmAuthzProvider := fmt.Sprintf("%s-odh-auth-provider", feature.Spec.AppNamespace)

gvr := schema.GroupVersionResource{
Group: "maistra.io",
Version: "v2",
Resource: "servicemeshcontrolplanes",
}

mesh := feature.Spec.Mesh

smcp, err := feature.dynamicClient.Resource(gvr).
smcp, err := feature.dynamicClient.Resource(smcpGVR).
Namespace(mesh.Namespace).
Get(context.Background(), mesh.Name, metav1.GetOptions{})
if err != nil {
Expand Down Expand Up @@ -136,7 +128,7 @@ func RemoveExtensionProvider(feature *Feature) error {
}
}

_, err = feature.dynamicClient.Resource(gvr).
_, err = feature.dynamicClient.Resource(smcpGVR).
Namespace(mesh.Namespace).
Update(context.Background(), smcp, metav1.UpdateOptions{})

Expand Down
26 changes: 23 additions & 3 deletions pkg/kfapp/ossm/feature/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import (
var log = ctrlLog.Log.WithName("ossm-features")

type Feature struct {
Name string
Spec *Spec
Name string
Spec *Spec
Enabled bool

clientset *kubernetes.Clientset
dynamicClient dynamic.Interface
Expand All @@ -42,6 +43,13 @@ type Feature struct {
type action func(feature *Feature) error

func (f *Feature) Apply() error {

if !f.Enabled {
log.Info("feature is disabled, skipping.", "name", f.Name)

return nil
}

// Verify all precondition and collect errors
var multiErr *multierror.Error
for _, precondition := range f.preconditions {
Expand Down Expand Up @@ -80,12 +88,24 @@ func (f *Feature) Apply() error {
return err
}

// TODO postconditions
for _, postcondition := range f.postconditions {
multiErr = multierror.Append(multiErr, postcondition(f))
}

if multiErr.ErrorOrNil() != nil {
return multiErr.ErrorOrNil()
}

return nil
}

func (f *Feature) Cleanup() error {
if !f.Enabled {
log.Info("feature is disabled, skipping.", "name", f.Name)

return nil
}

var cleanupErrors *multierror.Error
for _, cleanupFunc := range f.cleanups {
cleanupErrors = multierror.Append(cleanupErrors, cleanupFunc(f))
Expand Down
7 changes: 4 additions & 3 deletions pkg/kfapp/ossm/feature/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,16 @@ func ServiceMeshEnabledInDashboard(feature *Feature) error {
dashboardConfig := spec["dashboardConfig"].(map[string]interface{})
dashboardConfig["disableServiceMesh"] = false

_, err = feature.dynamicClient.Resource(gvr).
if _, err := feature.dynamicClient.Resource(gvr).
Namespace(feature.Spec.AppNamespace).
Update(context.Background(), &config, metav1.UpdateOptions{})
if err != nil {
Update(context.Background(), &config, metav1.UpdateOptions{}); err != nil {
log.Error(err, "Failed to update odhdashboardconfig")

return err
}

log.Info("Successfully patched odhdashboardconfig")

return nil
}

Expand Down
Loading

0 comments on commit 2b92731

Please sign in to comment.