Skip to content

Commit

Permalink
#4 add persistence for effective blueprint and add status code for th…
Browse files Browse the repository at this point in the history
…e creation of the effective blueprint
  • Loading branch information
alexander-dammeier committed Dec 15, 2023
1 parent 6543b66 commit ecc424a
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 179 deletions.
58 changes: 58 additions & 0 deletions k8s/helm-crd/templates/k8s.cloudogu.com_blueprints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,64 @@ spec:
status:
description: Status defines the observed state of the Blueprint.
properties:
effectiveBlueprint:
description: EffectiveBlueprint is the blueprint after applying the blueprint mask.
properties:
components:
description: Packages contains a set of exact package versions which should be present or absent in the CES instance after which this blueprint was applied. The packages must correspond to the used operation system package manager. Optional.
items:
properties:
name:
description: Name defines the name of the component including its namespace, f. i. "official/nginx". Must not be empty.
type: string
targetState:
description: TargetState defines a state of installation of this component. Optional field, but defaults to "TargetStatePresent"
type: string
version:
description: Version defines the version of the dogu that is to be installed. Must not be empty if the targetState is "present"; otherwise it is optional and is not going to be interpreted.
type: string
required:
- name
- targetState
- version
type: object
type: array
dogus:
description: Dogus contains a set of exact dogu versions which should be present or absent in the CES instance after which this blueprint was applied. Optional.
items:
description: TargetDogu defines a Dogu, its version, and the installation state in which it is supposed to be after a blueprint was applied.
properties:
name:
description: Name defines the name of the dogu including its namespace, f. i. "official/nginx". Must not be empty.
type: string
targetState:
description: TargetState defines a state of installation of this dogu. Optional field, but defaults to "TargetStatePresent"
type: string
version:
description: Version defines the version of the dogu that is to be installed. Must not be empty if the targetState is "present"; otherwise it is optional and is not going to be interpreted.
type: string
required:
- name
- targetState
- version
type: object
type: array
registryConfig:
additionalProperties:
type: string
description: Used to configure registry globalRegistryEntries on blueprint upgrades
type: object
registryConfigAbsent:
description: Used to remove registry globalRegistryEntries on blueprint upgrades
items:
type: string
type: array
registryConfigEncrypted:
additionalProperties:
type: string
description: Used to configure encrypted registry globalRegistryEntries on blueprint upgrades
type: object
type: object
phase:
description: Phase represents the processing state of the blueprint
type: string
Expand Down
60 changes: 0 additions & 60 deletions pkg/adapter/kubernetes/blueprintRestClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"time"

v1 "github.com/cloudogu/k8s-blueprint-operator/pkg/api/v1"
"github.com/cloudogu/k8s-blueprint-operator/pkg/retry"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
Expand All @@ -24,21 +22,6 @@ type BlueprintInterface interface {
// UpdateStatus was generated because the type contains a Status member.
UpdateStatus(ctx context.Context, blueprint *v1.Blueprint, opts metav1.UpdateOptions) (*v1.Blueprint, error)

// UpdateStatusInProgress sets the status of the blueprint to "in progress".
UpdateStatusInProgress(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error)

// UpdateStatusCompleted sets the status of the blueprint to "completed".
UpdateStatusCompleted(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error)

// UpdateStatusFailed sets the status of the blueprint to "failed".
UpdateStatusFailed(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error)

// UpdateStatusRetrying sets the status of the blueprint to "retrying".
UpdateStatusRetrying(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error)

// UpdateStatusInvalid sets the status of the blueprint to "invalid".
UpdateStatusInvalid(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error)

// Delete takes name of the blueprint and deletes it. Returns an error if one occurs.
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error

Expand All @@ -63,49 +46,6 @@ type blueprintClient struct {
ns string
}

// UpdateStatusInProgress sets the status of the blueprint to "inProgress".
func (d *blueprintClient) UpdateStatusInProgress(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error) {
return d.updateStatusWithRetry(ctx, blueprint, v1.StatusPhaseInProgress)
}

// UpdateStatusCompleted sets the status of the blueprint to "completed".
func (d *blueprintClient) UpdateStatusCompleted(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error) {
return d.updateStatusWithRetry(ctx, blueprint, v1.StatusPhaseCompleted)
}

// UpdateStatusInvalid sets the status of the blueprint to "invalid".
func (d *blueprintClient) UpdateStatusInvalid(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error) {
return d.updateStatusWithRetry(ctx, blueprint, v1.StatusPhaseInvalid)
}

// UpdateStatusFailed sets the status of the blueprint to "failed".
func (d *blueprintClient) UpdateStatusFailed(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error) {
return d.updateStatusWithRetry(ctx, blueprint, v1.StatusPhaseFailed)
}

// UpdateStatusRetrying sets the status of the blueprint to "retrying".
func (d *blueprintClient) UpdateStatusRetrying(ctx context.Context, blueprint *v1.Blueprint) (*v1.Blueprint, error) {
return d.updateStatusWithRetry(ctx, blueprint, v1.StatusPhaseRetrying)
}

func (d *blueprintClient) updateStatusWithRetry(ctx context.Context, blueprint *v1.Blueprint, targetStatus v1.StatusPhase) (*v1.Blueprint, error) {
var resultBlueprint *v1.Blueprint
err := retry.OnConflict(func() error {
updatedBlueprint, err := d.Get(ctx, blueprint.GetName(), metav1.GetOptions{})
if err != nil {
return err
}

// do not overwrite the whole status, so we do not lose other values from the Status object
// esp. a potentially set requeue time
updatedBlueprint.Status.Phase = targetStatus
resultBlueprint, err = d.UpdateStatus(ctx, updatedBlueprint, metav1.UpdateOptions{})
return err
})

return resultBlueprint, err
}

// Get takes name of the blueprint, and returns the corresponding blueprint object, and an error if there is any.
func (d *blueprintClient) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Blueprint, err error) {
result = &v1.Blueprint{}
Expand Down
81 changes: 0 additions & 81 deletions pkg/adapter/kubernetes/blueprintRestClient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package kubernetes
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -315,82 +313,3 @@ func Test_blueprintClient_Patch(t *testing.T) {
require.NoError(t, err)
})
}

func Test_blueprintClient_UpdateStatusXXX(t *testing.T) {
for _, testCase := range []struct {
functionName string
expectedStatus k8sv1.StatusPhase
}{
{
functionName: "UpdateStatusInProgress",
expectedStatus: k8sv1.StatusPhase("inProgress"),
},
{
functionName: "UpdateStatusCompleted",
expectedStatus: k8sv1.StatusPhase("completed"),
},
{
functionName: "UpdateStatusInvalid",
expectedStatus: k8sv1.StatusPhase("invalid"),
},
{
functionName: "UpdateStatusFailed",
expectedStatus: k8sv1.StatusPhase("failed"),
},
{
functionName: "UpdateStatusRetrying",
expectedStatus: k8sv1.StatusPhase("retrying"),
},
} {
t.Run(fmt.Sprintf("%s success", testCase.functionName), func(t *testing.T) {
// given
blueprint := &k8sv1.Blueprint{ObjectMeta: v1.ObjectMeta{Name: "testblueprint", Namespace: "test"}}

server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
switch request.Method {
case http.MethodGet:
assert.Equal(t, "/apis/k8s.cloudogu.com/v1/namespaces/test/blueprints/testblueprint", request.URL.Path)
assert.Equal(t, http.NoBody, request.Body)

writer.Header().Add("content-type", "application/json")
blueprint := &k8sv1.Blueprint{ObjectMeta: v1.ObjectMeta{Name: "testblueprint", Namespace: "test"}}
blueprintBytes, err := json.Marshal(blueprint)
require.NoError(t, err)
_, err = writer.Write(blueprintBytes)
require.NoError(t, err)
writer.WriteHeader(200)
case http.MethodPut:
assert.Equal(t, "/apis/k8s.cloudogu.com/v1/namespaces/test/blueprints/testblueprint/status", request.URL.Path)
bytes, err := io.ReadAll(request.Body)
require.NoError(t, err)

createdBlueprint := &k8sv1.Blueprint{}
require.NoError(t, json.Unmarshal(bytes, createdBlueprint))
assert.Equal(t, "testblueprint", createdBlueprint.Name)
assert.Equal(t, testCase.expectedStatus, createdBlueprint.Status.Phase)

writer.Header().Add("content-type", "application/json")
_, err = writer.Write(bytes)
require.NoError(t, err)
writer.WriteHeader(200)
default:
assert.Fail(t, "method should be get or put")
}
}))

config := rest.Config{
Host: server.URL,
}
client, err := newForConfig(&config)
require.NoError(t, err)
dClient := client.Blueprints("test")

// when
returnValues := reflect.ValueOf(dClient).MethodByName(testCase.functionName).Call([]reflect.Value{reflect.ValueOf(testCtx), reflect.ValueOf(blueprint)})
err, _ = returnValues[1].Interface().(error)

// then
require.NoError(t, err)
})
}
}
17 changes: 14 additions & 3 deletions pkg/adapter/kubernetes/blueprintSpecCRRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/cloudogu/k8s-blueprint-operator/pkg/adapter/serializer"
"github.com/cloudogu/k8s-blueprint-operator/pkg/adapter/serializer/effectiveBlueprintV1"
v1 "github.com/cloudogu/k8s-blueprint-operator/pkg/api/v1"
"github.com/cloudogu/k8s-blueprint-operator/pkg/domain"
"github.com/cloudogu/k8s-blueprint-operator/pkg/domainservice"
Expand Down Expand Up @@ -60,16 +61,20 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string)

persistenceContext := make(map[string]interface{}, 1)
persistenceContext[resourceVersionKey] = resourceVersionValue{blueprintCR.GetResourceVersion()}
effectiveBlueprint, err := effectiveBlueprintV1.ConvertToEffectiveBlueprint(blueprintCR.Status.EffectiveBlueprint)
if err != nil {
return domain.BlueprintSpec{}, err
}
blueprintSpec := domain.BlueprintSpec{
Id: blueprintId,
EffectiveBlueprint: domain.EffectiveBlueprint{},
EffectiveBlueprint: effectiveBlueprint,
StateDiff: domain.StateDiff{},
BlueprintUpgradePlan: domain.BlueprintUpgradePlan{},
Config: domain.BlueprintConfiguration{
IgnoreDoguHealth: blueprintCR.Spec.IgnoreDoguHealth,
AllowDoguNamespaceSwitch: blueprintCR.Spec.AllowDoguNamespaceSwitch,
},
Status: domain.StatusPhase(blueprintCR.Status.Phase),
Status: blueprintCR.Status.Phase,
PersistenceContext: persistenceContext,
}

Expand All @@ -92,6 +97,11 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec domain.Blueprint
if err != nil {
return err
}
effectiveBlueprint, err := effectiveBlueprintV1.ConvertToEffectiveBlueprintV1(spec.EffectiveBlueprint)
if err != nil {
return err
}

updatedBlueprint := v1.Blueprint{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -100,7 +110,8 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec domain.Blueprint
CreationTimestamp: metav1.Time{},
},
Status: v1.BlueprintStatus{
Phase: v1.StatusPhase(spec.Status),
Phase: spec.Status,
EffectiveBlueprint: effectiveBlueprint,
},
}
logger.Info("update blueprint", "blueprint to save", updatedBlueprint)
Expand Down
6 changes: 3 additions & 3 deletions pkg/adapter/kubernetes/blueprintSpecCRRepository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) {
},
Spec: v1.BlueprintSpec{},
Status: v1.BlueprintStatus{
Phase: v1.StatusPhase(domain.StatusPhaseValidated),
Phase: domain.StatusPhaseValidated,
},
}
restClientMock.EXPECT().
Expand Down Expand Up @@ -221,7 +221,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) {
},
Spec: v1.BlueprintSpec{},
Status: v1.BlueprintStatus{
Phase: v1.StatusPhase(domain.StatusPhaseValidated),
Phase: domain.StatusPhaseValidated,
},
}
expectedError := k8sErrors.NewConflict(
Expand Down Expand Up @@ -266,7 +266,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) {
},
Spec: v1.BlueprintSpec{},
Status: v1.BlueprintStatus{
Phase: v1.StatusPhase(domain.StatusPhaseValidated),
Phase: domain.StatusPhaseValidated,
},
}
expectedError := fmt.Errorf("test-error")
Expand Down
Loading

0 comments on commit ecc424a

Please sign in to comment.