Skip to content

Commit

Permalink
docs: added updated component integration guide (#1488)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlassak authored Jan 15, 2025
1 parent ad20d38 commit 1960209
Showing 1 changed file with 324 additions and 0 deletions.
324 changes: 324 additions & 0 deletions docs/COMPONENT_INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
# Component Integration

Since the ODH operator is the integration point to deploy ODH component manifests, it is essential to have common processes to integrate new components.

Currently, each component is expected to have its own dedicated internal API/CRD and dedicated reconciler.
To understand the current operator architecture and its inner workings, please refer to [the design document](https://github.com/opendatahub-io/opendatahub-operator/blob/main/docs/DESIGN.md).

The list of the currently integrated ODH components is provided [at the end of this document](#integrated-components).

## Integrating a new component

To ensure a new component is integrated seamlessly in the operator, please follow the steps listed below.

### 1. Update API specs

The first step is to define the internal API spec for the new component and introduce it to the existing DataScienceCluster (DSC) API. Please proceed as follows:

#### Define internal API spec for the new component

1. Create a dedicated `<example_component_name>_types.go` file within `apis/components/v1alpha1` directory.

2. Define the internal API spec for the new component according to the expected definitions.
You can use the following pseudo-implementation for reference:

```go
package v1alpha1

import (
"github.com/opendatahub-io/opendatahub-operator/v2/apis/common"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// example new component name
ExampleComponentName = "examplecomponent"

// ExampleComponentInstanceName is the name of the new component instance singleton
// value should match what is set in the kubebuilder markers for XValidation defined below
ExampleComponentInstanceName = "default-examplecomponent"

// kubernetes kind of the new component
ExampleComponentKind = "ExampleComponent"
)

type ExampleComponentCommonSpec struct {
// new component spec exposed to DSC api
common.DevFlagsSpec `json:",inline"`

// new component spec shared with DSC api
// ( refer/define here if applicable to the new component )
}

// ExampleComponentSpec defines the desired state of ExampleComponent
type ExampleComponentSpec struct {
// new component spec exposed to DSC api
ExampleComponentCommonSpec `json:",inline"`

// new component spec exposed only to internal api
// ( refer/define here if applicable to the new component )
}

// ExampleComponentCommonStatus defines the shared observed state of ExampleComponent
type ExampleComponentCommonStatus struct {
// add fields/attributes if needed
}

// ExampleComponentStatus defines the observed state of ExampleComponent
type ExampleComponentStatus struct {
common.Status `json:",inline"`
ExampleComponentCommonStatus `json:",inline"`
}

// default kubebuilder markers for the new component
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default-examplecomponent'",message="ExampleComponent name must be default-examplecomponent"
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`,description="Ready"
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`,description="Reason"

// ExampleComponent is the Schema for the new component API
type ExampleComponent struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ExampleComponentSpec `json:"spec,omitempty"`
Status ExampleComponentStatus `json:"status,omitempty"`
}

// getter for devFlags
func (c *ExampleComponent) GetDevFlags() *common.DevFlags {
return c.Spec.DevFlags
}

// status getter
func (c *ExampleComponent) GetStatus() *common.Status {
return &c.Status.Status
}

// +kubebuilder:object:root=true

// ExampleComponentList contains a list of ExampleComponent
type ExampleComponentList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ExampleComponent `json:"items"`
}

// register the defined schemas
func init() {
SchemeBuilder.Register(&ExampleComponent{}, &ExampleComponentList{})
}

// DSCExampleComponent contains all the configuration exposed in DSC instance for ExampleComponent component
// ( utilize DSC prefix here for naming consistency with the other integrated components )
type DSCExampleComponent struct {
// configuration fields common across components
common.ManagementSpec `json:",inline"`

// new component-specific fields
ExampleComponentCommonSpec `json:",inline"`
}

// DSCExampleComponentStatus struct holds the status for the ExampleComponent component exposed in the DSC
type DSCExampleComponentStatus struct {
common.ManagementSpec `json:",inline"`
*ExampleComponentCommonStatus `json:",inline"`
}
```

Alternatively, you can refer to the existing integrated component APIs located within `apis/components/v1alpha1` directory.

#### Add Component to DataScienceCluster API spec

DataScienceCluster (DSC) CRD is responsible for enabling individual components and exposing them to end users.
To introduce the newly defined component API, extend the `Components` struct within the DataScienceCluster API spec (located within `apis/datasciencecluster/v1`) to include the new API.

```diff
type Components struct {
// Dashboard component configuration.
Dashboard componentApi.DSCDashboard `json:"dashboard,omitempty"`

// Workbenches component configuration.
Workbenches componentApi.DSCWorkbenches `json:"workbenches,omitempty"`

// ... other currently integrated components ...

// add the new component as follows
+ ExampleComponent componentApi.DSCExampleComponent `json:"examplecomponent,omitempty"`
}
```

Additionally, extend the `ComponentsStatus` struct within the same file to include the new component status to be exposed in the DSC.

```diff
// ComponentsStatus defines the custom status of DataScienceCluster components.
type ComponentsStatus struct {
// Dashboard component status.
Dashboard componentApi.DSCDashboardStatus `json:"dashboard,omitempty"`

// Workbenches component status.
Workbenches componentApi.DSCWorkbenchesStatus `json:"workbenches,omitempty"`

// ... other currently integrated component statuses ...

// add the new component status as follows
+ ExampleComponent componentApi.DSCExampleComponentStatus `json:"examplecomponent,omitempty"`
}
```

#### Update kubebuilder_rbac.go

Add kubebuilder RBAC permissions intended for the new component into `controllers/datasciencecluster/kubebuilder_rbac.go`.

#### Update the dependent files

To fully reflect the API changes brought by the addition of the new component, run the following command:
```make
make generate manifests api-docs bundle
```
This command will (re-)generate the necessary kubebuilder functions, and update both the API documentation and the operator bundle manifests.

### 2. Create a module for the new component reconciliation logic

To add new component-specific reconciler logic, create a dedicated `<example_component_name>` module, located in the `controllers/components` directory.
For reference, the `controllers/components` directory contains reconciler implementations for the currently integrated components.

#### Implement the component handler interface

Each component that is intended to be managed by the operator is expected to be included in the components registry.
The components registry (currently implemented in `pkg/componentsregistry`) defines a component handler interface which is required to be implemented for the new component.
To do so, create a dedicated `<example_component_name>.go` file within the newly created component module and provide the interface implementation:

```go
type componentHandler struct{}

func init() { //nolint:gochecknoinits
cr.Add(&componentHandler{})
}

func (s *componentHandler) GetName() string

func (s *componentHandler) GetManagementState(dsc *dscv1.DataScienceCluster) operatorv1.ManagementState

func (s *componentHandler) NewCRObject(dsc *dscv1.DataScienceCluster) common.PlatformObject

func (s *componentHandler) Init(platform cluster.Platform) error

func (s *componentHandler) UpdateDSCStatus(dsc *dscv1.DataScienceCluster, obj client.Object) error
```

Please refer the existing component implementations in the `controllers/components` directory for further details.

#### Implement new component reconciler

Create a dedicated `<example_component_name>_controller.go` file and implement the expected `NewComponentReconciler` function there.
This function will be responsible for creating the reconciler for the previously introduced `<ExampleComponent>` API.

`NewControllerReconciler` utilizes a generic builder pattern, that supports defining various types of relationships and functionality:
- resource ownership - using `.Owns()`
- watching a resource - using `.Watches()`
- reconciler actions - using `.WithAction()`
- this includes pre-implemented actions used commonly across components (e.g. manifest rendering), as well as custom, component-specific actions
- more details on actions are provided [below](#actions)

The example pseudo-implementation should look like as follows:
```go
func (s *componentHandler) NewComponentReconciler(ctx context.Context, mgr ctrl.Manager) error {
_, err := reconciler.ReconcilerFor(mgr, &componentApi.ExampleComponent{}).
Owns(...).
// ... add other necessary resource ownerships
Watches(...).
// ... add other necessary resource watches
WithAction(...).
// ... add custom actions if needed
// ... add mandatory common actions (e.g. manifest rendering, deployment, garbage collection)
Build(ctx)

if err != nil {
return err
}

return nil
}
```

##### Actions

Actions are functions that define pieces of component reconciliation logic. Any action is expected to conform to the following signature:

```go
func exampleAction(ctx context.Context, rr *odhtypes.ReconciliationRequest) error
```

Such actions can be then introduced to the reconciler builder using `.WithAction()` calls.
As seen in the existing component reconciler implementations, it would be recommended to include the action implementations in a separate file within the module, such as `<example_component_name>_controller_actions.go`.

"Generic"/commonly-implemented actions for each of the currently integrated components include:
- `initialize()` - to register paths to the component manifests
- `devFlags()` - to override the component manifest paths according to the Dev Flags configuration

In addition, proper generic actions, intended to be used across the components, are provided as part of the operator implementation (located in `pkg/controller/actions`).
These support:
- (if necessary) creating pod security role binding
- manifest rendering
- can additionally utilize caching
- manifest deployment
- can additionally utilize caching
- status updating
- garbage collection
- **additional requirement - garbage collection action must always be called as the last action before the final `.Build()` call**

If the new component requires additional custom logic, custom actions can also be added to the builder via the respective `.WithAction()` calls.

For practical examples of all the above-mentioned functionality, please refer to the implementations within `controllers/components` directory.

#### Update upgrade.go

Update the `CreateDefaultDSC()` function in `pkg/upgrade/upgrade.go` to include the newly added component.

#### Update main.go

Add an import for the the newly added component:

```diff
package main

import (
// ... existing imports ...

// ... component imports for the integrated components ...
+ _ "github.com/opendatahub-io/opendatahub-operator/v2/controllers/components/<example_component>"
)
```

### 3. Add unit and e2e tests

Please add `unit` tests for any component-specific functions added to the codebase.

Please also add [e2e tests](https://github.com/opendatahub-io/opendatahub-operator/tree/main/tests/e2e) to
the e2e test suite to capture deployments introduced by the new component.
Existing e2e test suites for the integrated components can be also found there.

Lastly, please update the following files to fully integrate new component tests into the overall test suite:
- update `setupDSCInstance()` function in `tests/e2e/helper_test.go` to include the newly added component
- update `newDSC()` function in `controllers/webhook/webhook_suite_test.go` to include the newly added component
- update `componentsTestSuites` map in `tests/e2e/controller_test.go` to include the reference for the new component e2e test suite

## Integrated components

Currently integrated components are:
- [Dashboard](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/dashboard)
- [Codeflare](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/codeflare)
- [Ray](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/ray)
- [Data Science Pipelines](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/datasciencepipelines)
- [KServe](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/kserve)
- [ModelMesh Serving](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/modelmeshserving)
- [Workbenches](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/workbenches)
- [TrustyAI](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/trustyai)
- [ModelRegistry](https://github.com/opendatahub-io/opendatahub-operator/tree/main/components/modelregistry)
- [Kueue](https://github.com/opendatahub-io/kueue)
- [Model Controller](https://github.com/opendatahub-io/odh-model-controller)

The particular controller implementations for the listed components are located in the `controllers/components` directory and the corresponding internal component APIs are located in `apis/components/v1alpha1`.

0 comments on commit 1960209

Please sign in to comment.