Skip to content

Commit

Permalink
show available versions in the status of the spicedbcluster
Browse files Browse the repository at this point in the history
  • Loading branch information
ecordell committed Nov 15, 2022
1 parent 0bdc0e7 commit e3ad798
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 25 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,11 @@ jobs:
file: "spicedb/Dockerfile"
tags: "spicedb:dev,spicedb:updated"
outputs: "type=docker,dest=/tmp/image.tar"
- name: "Tag Test v1.13.0 image"
run: |
docker pull ghcr.io/authzed/spicedb:v1.13.0
docker tag ghcr.io/authzed/spicedb:v1.13.0 spicedb:v1.13.0
- name: "Run Ginkgo Tests"
run: "go run github.com/onsi/ginkgo/v2/ginkgo --skip-package ./spicedb --tags=e2e -r --procs=2 -v --randomize-all --randomize-suites --fail-on-pending --fail-fast --no-color --race --trace --json-report=report.json -- -v=4"
env:
PROVISION: "true"
ARCHIVES: "/tmp/image.tar"
IMAGES: "spicedb:v1.13.0"
- uses: "docker/build-push-action@v2"
with:
context: "./"
Expand Down
2 changes: 2 additions & 0 deletions e2e/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,8 @@ var _ = Describe("SpiceDBClusters", func() {
fetched, err := typed.UnstructuredObjToTypedObj[*v1alpha1.SpiceDBCluster](clusterUnst)
g.Expect(err).To(Succeed())
g.Expect(len(fetched.Status.Conditions)).To(BeZero())
GinkgoWriter.Println(fetched.Status)
g.Expect(len(fetched.Status.AvailableVersions)).ToNot(BeZero(), "status should show available updates")
}).Should(Succeed())

// once the cluster is running at the initial version, update the target version
Expand Down
45 changes: 45 additions & 0 deletions pkg/apis/authzed/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1alpha1
import (
"encoding/json"

"golang.org/x/exp/slices"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -120,12 +121,56 @@ type ClusterStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

func (s ClusterStatus) Equals(other ClusterStatus) bool {
if s.ObservedGeneration != other.ObservedGeneration {
return false
}
if s.TargetMigrationHash != other.TargetMigrationHash {
return false
}
if s.CurrentMigrationHash != other.CurrentMigrationHash {
return false
}
if s.SecretHash != other.SecretHash {
return false
}
if s.Image != other.Image {
return false
}
if s.Migration != other.Migration {
return false
}
if s.Phase != other.Phase {
return false
}
if !s.CurrentVersion.Equals(other.CurrentVersion) {
return false
}
if !slices.Equal(s.AvailableVersions, other.AvailableVersions) {
return false
}
if !slices.Equal(s.Conditions, other.Conditions) {
return false
}
return true
}

type SpiceDBVersion struct {
Name string `json:"name"`
Channel string `json:"channel"`
Description string `json:"description,omitempty"`
}

func (v *SpiceDBVersion) Equals(other *SpiceDBVersion) bool {
if v == other {
return true
}
if v != nil && other != nil && v.Name == other.Name && v.Channel == other.Channel {
return true
}
return false
}

// SpiceDBClusterList is a list of SpiceDBCluster resources
//
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
122 changes: 102 additions & 20 deletions pkg/controller/validate_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"

Expand All @@ -13,6 +15,7 @@ import (

"github.com/authzed/spicedb-operator/pkg/apis/authzed/v1alpha1"
"github.com/authzed/spicedb-operator/pkg/config"
"github.com/authzed/spicedb-operator/pkg/updates"
)

const EventInvalidSpiceDBConfig = "InvalidSpiceDBConfig"
Expand Down Expand Up @@ -73,27 +76,31 @@ func (c *ValidateConfigHandler) Handle(ctx context.Context) {
}
ctx = CtxMigrationHash.WithValue(ctx, migrationHash)

computedStatus := v1alpha1.ClusterStatus{
ObservedGeneration: currentStatus.GetGeneration(),
TargetMigrationHash: migrationHash,
CurrentMigrationHash: currentStatus.Status.CurrentMigrationHash,
SecretHash: currentStatus.Status.SecretHash,
Image: validatedConfig.TargetSpiceDBImage,
Migration: validatedConfig.TargetMigration,
Phase: validatedConfig.TargetPhase,
CurrentVersion: validatedConfig.SpiceDBVersion,
Conditions: currentStatus.GetStatusConditions(),
}
if validatedConfig.SpiceDBVersion != nil {
computedStatus.AvailableVersions = c.getAvailableVersions(ctx, operatorConfig.UpdateGraph, *validatedConfig.SpiceDBVersion, validatedConfig.DatastoreEngine)
}
meta.RemoveStatusCondition(&computedStatus.Conditions, v1alpha1.ConditionValidatingFailed)
meta.RemoveStatusCondition(&computedStatus.Conditions, v1alpha1.ConditionTypeValidating)
if warningCondition != nil {
meta.SetStatusCondition(&computedStatus.Conditions, *warningCondition)
} else {
meta.RemoveStatusCondition(&computedStatus.Conditions, v1alpha1.ConditionTypeConfigWarnings)
}

// Remove invalid config status and set image and hash
if currentStatus.IsStatusConditionTrue(v1alpha1.ConditionValidatingFailed) ||
currentStatus.IsStatusConditionTrue(v1alpha1.ConditionTypeValidating) ||
currentStatus.Status.Image != validatedConfig.TargetSpiceDBImage ||
currentStatus.Status.TargetMigrationHash != migrationHash ||
currentStatus.IsStatusConditionChanged(v1alpha1.ConditionTypeConfigWarnings, warningCondition) ||
// TODO: this should deref and check the values if they're not nil
currentStatus.Status.CurrentVersion != validatedConfig.SpiceDBVersion {
currentStatus.RemoveStatusCondition(v1alpha1.ConditionValidatingFailed)
currentStatus.Status.CurrentVersion = validatedConfig.SpiceDBVersion
currentStatus.Status.Image = validatedConfig.TargetSpiceDBImage
currentStatus.Status.TargetMigrationHash = migrationHash
currentStatus.Status.ObservedGeneration = currentStatus.GetGeneration()
currentStatus.Status.Phase = validatedConfig.TargetPhase
currentStatus.Status.Migration = validatedConfig.TargetMigration
if warningCondition != nil {
currentStatus.SetStatusCondition(*warningCondition)
} else {
currentStatus.RemoveStatusCondition(v1alpha1.ConditionTypeConfigWarnings)
}
currentStatus.RemoveStatusCondition(v1alpha1.ConditionTypeValidating)
if !currentStatus.Status.Equals(computedStatus) {
currentStatus.Status = computedStatus
if err := c.patchStatus(ctx, currentStatus); err != nil {
QueueOps.RequeueAPIErr(ctx, err)
return
Expand All @@ -104,3 +111,78 @@ func (c *ValidateConfigHandler) Handle(ctx context.Context) {
ctx = CtxClusterStatus.WithValue(ctx, currentStatus)
c.next.Handle(ctx)
}

func (c *ValidateConfigHandler) getAvailableVersions(ctx context.Context, graph updates.UpdateGraph, version v1alpha1.SpiceDBVersion, datastore string) []v1alpha1.SpiceDBVersion {
logger := logr.FromContextOrDiscard(ctx)
source, err := graph.SourceForChannel(version.Channel)
if err != nil {
logger.V(4).Error(err, "no source found for channel %q, can't compute available versions", version.Channel)
}

availableVersions := make([]v1alpha1.SpiceDBVersion, 0)
nextDirect := source.NextDirect(version.Name)
next := source.Next(version.Name)
latest := source.Latest(version.Name)
if len(nextDirect) > 0 {
nextDirectVersion := v1alpha1.SpiceDBVersion{
Name: nextDirect,
Channel: version.Channel,
Description: "direct update with no migrations",
}
if nextDirect == latest {
nextDirectVersion.Description += ", head of channel"
}
availableVersions = append(availableVersions, nextDirectVersion)
}
if len(next) > 0 && next != nextDirect {
nextVersion := v1alpha1.SpiceDBVersion{
Name: next,
Channel: version.Channel,
Description: "update will run a migration",
}
if next == latest {
nextVersion.Description += ", head of channel"
}
availableVersions = append(availableVersions, nextVersion)
}
if len(latest) > 0 && next != latest && nextDirect != latest {
availableVersions = append(availableVersions, v1alpha1.SpiceDBVersion{
Name: latest,
Channel: version.Channel,
Description: "head of the channel, multiple updates will run in sequence",
})
}

// check for options in other channels (only show the safest update for
// each available channel)
for _, c := range graph.Channels {
if c.Name == version.Channel {
continue
}
if c.Metadata["datastore"] != datastore {
continue
}
source, err := graph.SourceForChannel(c.Name)
if err != nil {
logger.V(4).Error(err, "no source found for channel %q, can't compute available versions", c.Name)
continue
}
if next := source.NextDirect(version.Name); len(next) > 0 {
availableVersions = append(availableVersions, v1alpha1.SpiceDBVersion{
Name: next,
Channel: c.Name,
Description: "direct update with no migrations, different channel",
})
continue
}
if next := source.Next(version.Name); len(next) > 0 {
availableVersions = append(availableVersions, v1alpha1.SpiceDBVersion{
Name: next,
Channel: c.Name,
Description: "update will run a migration, different channel",
})
}
}

return availableVersions
}
2 changes: 2 additions & 0 deletions pkg/controller/validate_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ func TestValidateConfigHandler(t *testing.T) {
name: "valid config, no changes, no warnings",
currentStatus: &v1alpha1.SpiceDBCluster{Status: v1alpha1.ClusterStatus{
Image: "image:v1",
Migration: "head",
TargetMigrationHash: "ndchdch68dh69h566h56fhb9h5dq",
CurrentMigrationHash: "ndchdch68dh69h566h56fhb9h5dq",
CurrentVersion: &v1alpha1.SpiceDBVersion{
Name: "v1",
Channel: "cockroachdb",
},
AvailableVersions: []v1alpha1.SpiceDBVersion{},
}},
rawConfig: json.RawMessage(`{
"datastoreEngine": "cockroachdb",
Expand Down

0 comments on commit e3ad798

Please sign in to comment.