Skip to content

Commit

Permalink
feat: add support for Conditions in CustomModelStatus and ModelStatus…
Browse files Browse the repository at this point in the history
… to track the observed state of the cluster
  • Loading branch information
ozeliurs committed Nov 21, 2024
1 parent 1c0805c commit c9aff62
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 18 deletions.
1 change: 1 addition & 0 deletions api/v1/custommodel_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type CustomModelSpec struct {
type CustomModelStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
9 changes: 8 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions config/crd/bases/ollama.ollama.startupnation_custommodels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,75 @@ spec:
type: string
modelName:
type: string
ollamaUrl:
type: string
required:
- modelFile
- modelName
- ollamaUrl
type: object
status:
description: CustomModelStatus defines the observed state of CustomModel
properties:
conditions:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
type: object
type: object
served: true
Expand Down
72 changes: 55 additions & 17 deletions internal/controller/custommodel_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

"github.com/StartUpNationLabs/simple-ollama-operator/internal/ollama_client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -52,37 +53,37 @@ type CustomModelReconciler struct {
func (r *CustomModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)

// Fetch the Model instance
CustomModel := &ollamav1.CustomModel{}
err := r.Get(ctx, req.NamespacedName, CustomModel)
// Fetch the CustomModel instance
customModel := &ollamav1.CustomModel{}
err := r.Get(ctx, req.NamespacedName, customModel)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// create a new Ollama Client
ollamaUrl := CustomModel.Spec.OllamaUrl
ollamaUrl := customModel.Spec.OllamaUrl
ollamaClient, err := ollama_client.NewClientWithResponses(ollamaUrl)
if err != nil {
logger.Error(err, "unable to create Ollama Client")
return ctrl.Result{}, err
}
logger.Info("Ollama Client created")
// If the CustomModel is being deleted, delete it from the Ollama Client
CustomModelFinalizer := "CustomModel.finalizer.ollama.ollama.startupnation"
customModelFinalizer := "custommodel.finalizer.ollama.ollama.startupnation"

modelName := unifyModelName(CustomModel.Spec.ModelName)
if CustomModel.ObjectMeta.DeletionTimestamp.IsZero() {
modelName := unifyModelName(customModel.Spec.ModelName)
if customModel.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object.
if !containsString(CustomModel.ObjectMeta.Finalizers, CustomModelFinalizer) {
CustomModel.ObjectMeta.Finalizers = append(CustomModel.ObjectMeta.Finalizers, CustomModelFinalizer)
if err := r.Update(context.Background(), CustomModel); err != nil {
if !containsString(customModel.ObjectMeta.Finalizers, customModelFinalizer) {
customModel.ObjectMeta.Finalizers = append(customModel.ObjectMeta.Finalizers, customModelFinalizer)
if err := r.Update(context.Background(), customModel); err != nil {
return reconcile.Result{}, err
}
}
} else {
// The object is being deleted
if containsString(CustomModel.ObjectMeta.Finalizers, CustomModelFinalizer) {
if containsString(customModel.ObjectMeta.Finalizers, customModelFinalizer) {
// our finalizer is present, so lets handle our external dependency
// first, we delete the external dependency
logger.Info("Deleting CustomModel", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
Expand All @@ -94,8 +95,8 @@ func (r *CustomModelReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}

// remove our finalizer from the list and update it.
CustomModel.ObjectMeta.Finalizers = removeString(CustomModel.ObjectMeta.Finalizers, CustomModelFinalizer)
if err := r.Update(context.Background(), CustomModel); err != nil {
customModel.ObjectMeta.Finalizers = removeString(customModel.ObjectMeta.Finalizers, customModelFinalizer)
if err := r.Update(context.Background(), customModel); err != nil {
return reconcile.Result{}, err
}
}
Expand All @@ -105,6 +106,11 @@ func (r *CustomModelReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
// if the CustomModel is not being deleted, start reconciliation

// Update status to indicate reconciliation has started
if err := r.updateStatus(ctx, customModel, "ReconciliationStarted", metav1.ConditionTrue, "Reconciling", "Reconciliation process has started"); err != nil {
return ctrl.Result{}, err
}

// get the CustomModel from the Ollama Client
logger.Info("Checking if CustomModel exists", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
res, err := ollamaClient.PostApiShowWithResponse(ctx, ollama_client.PostApiShowJSONRequestBody{
Expand All @@ -115,8 +121,12 @@ func (r *CustomModelReconciler) Reconcile(ctx context.Context, req ctrl.Request)
logger.Info("CustomModel exists", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
if res.JSON200 != nil {
logger.Info("Checking if the ModelFile is the same", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
if *res.JSON200.Parameters == CustomModel.Spec.ModelFile {
if *res.JSON200.Parameters == customModel.Spec.ModelFile {
logger.Info("ModelFile is the same", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
// Update status to indicate model exists
if err := r.updateStatus(ctx, customModel, "CustomModelExists", metav1.ConditionTrue, "ModelFound", "CustomModel exists in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
logger.Info("ModelFile is not the same", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
Expand All @@ -133,33 +143,61 @@ func (r *CustomModelReconciler) Reconcile(ctx context.Context, req ctrl.Request)
stream := false
_, err = ollamaClient.PostApiCreate(ctx, ollama_client.PostApiCreateJSONRequestBody{
Name: &modelName,
Modelfile: &CustomModel.Spec.ModelFile,
Modelfile: &customModel.Spec.ModelFile,
Stream: &stream,
})
if err != nil {
logger.Error(err, "unable to create CustomModel")
// Update status to indicate model creation failed
if err := r.updateStatus(ctx, customModel, "CustomModelCreationFailed", metav1.ConditionTrue, "CreationFailed", "Failed to create CustomModel in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, err
}
}
// Update status to indicate model creation succeeded
if err := r.updateStatus(ctx, customModel, "CustomModelCreated", metav1.ConditionTrue, "CreationSucceeded", "CustomModel created successfully in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, err
}
// if the CustomModel does not exist, create it
logger.Info("CustomModel does not exist, creating CustomModel", "CustomModel Name", modelName, "Ollama URL", ollamaUrl, "ModelFile", CustomModel.Spec.ModelFile)
logger.Info("CustomModel does not exist, creating CustomModel", "CustomModel Name", modelName, "Ollama URL", ollamaUrl, "ModelFile", customModel.Spec.ModelFile)
stream := false
_, err = ollamaClient.PostApiCreate(ctx, ollama_client.PostApiCreateJSONRequestBody{
Name: &modelName,
Modelfile: &CustomModel.Spec.ModelFile,
Modelfile: &customModel.Spec.ModelFile,
Stream: &stream,
})
if err != nil || res.StatusCode() != 200 {
logger.Error(err, "unable to create CustomModel")
// Update status to indicate model creation failed
if err := r.updateStatus(ctx, customModel, "CustomModelCreationFailed", metav1.ConditionTrue, "CreationFailed", "Failed to create CustomModel in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, err
}
logger.Info("CustomModel created", "CustomModel Name", modelName, "Ollama URL", ollamaUrl)
// Update status to indicate model creation succeeded
if err := r.updateStatus(ctx, customModel, "CustomModelCreated", metav1.ConditionTrue, "CreationSucceeded", "CustomModel created successfully in Ollama"); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// updateStatus is a helper function to update the status conditions of the CustomModel
func (r *CustomModelReconciler) updateStatus(ctx context.Context, customModel *ollamav1.CustomModel, conditionType string, status metav1.ConditionStatus, reason string, message string) error {
customModel.Status.Conditions = append(customModel.Status.Conditions, metav1.Condition{
Type: conditionType,
Status: status,
LastTransitionTime: metav1.Now(),
Reason: reason,
Message: message,
})
return r.Status().Update(ctx, customModel)
}

// SetupWithManager sets up the controller with the Manager.
func (r *CustomModelReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
30 changes: 30 additions & 0 deletions internal/controller/model_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

"github.com/StartUpNationLabs/simple-ollama-operator/internal/ollama_client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -106,6 +107,11 @@ func (r *ModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}
// if the Model is not being deleted, start reconciliation

// Update status to indicate reconciliation has started
if err := r.updateStatus(ctx, model, "ReconciliationStarted", metav1.ConditionTrue, "Reconciling", "Reconciliation process has started"); err != nil {
return ctrl.Result{}, err
}

// get the model from the Ollama Client
logger.Info("Checking if Model exists", "Model Name", modelName, "Ollama URL", ollamaUrl)
res, err := ollamaClient.PostApiShowWithResponse(ctx, ollama_client.PostApiShowJSONRequestBody{
Expand All @@ -116,6 +122,10 @@ func (r *ModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
logger.Info("Model exists", "Model Name", modelName, "Ollama URL", ollamaUrl)
if res.JSON200 != nil {
logger.Info("Model exists", "Model Params", res.JSON200.Parameters, "Ollama URL", ollamaUrl)
// Update status to indicate model exists
if err := r.updateStatus(ctx, model, "ModelExists", metav1.ConditionTrue, "ModelFound", "Model exists in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
Expand All @@ -129,13 +139,33 @@ func (r *ModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
})
if err != nil {
logger.Error(err, "unable to create Model")
// Update status to indicate model creation failed
if err := r.updateStatus(ctx, model, "ModelCreationFailed", metav1.ConditionTrue, "CreationFailed", "Failed to create model in Ollama"); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, err
}
logger.Info("Model created", "Model Name", modelName, "Ollama URL", ollamaUrl)
// Update status to indicate model creation succeeded
if err := r.updateStatus(ctx, model, "ModelCreated", metav1.ConditionTrue, "CreationSucceeded", "Model created successfully in Ollama"); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// updateStatus is a helper function to update the status conditions of the Model
func (r *ModelReconciler) updateStatus(ctx context.Context, model *ollamav1.Model, conditionType string, status metav1.ConditionStatus, reason string, message string) error {
model.Status.Conditions = append(model.Status.Conditions, metav1.Condition{
Type: conditionType,
Status: status,
LastTransitionTime: metav1.Now(),
Reason: reason,
Message: message,
})
return r.Status().Update(ctx, model)
}

// SetupWithManager sets up the controller with the Manager.
func (r *ModelReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down

0 comments on commit c9aff62

Please sign in to comment.