Commit 79e57720 authored by Brian's avatar Brian Committed by GitHub

Merge pull request #1155 from fibonacci1729/feat/rollback-storage

feat(rollback storage): support for rolling back to previous release.
parents cb93006c 84f982e8
......@@ -179,10 +179,20 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease
if req.Name == "" {
return nil, errMissingRelease
}
rel, err := s.env.Releases.Get(req.Name)
if err != nil {
return nil, err
var rel *release.Release
var err error
if req.Version <= 0 {
if rel, err = s.env.Releases.Deployed(req.Name); err != nil {
return nil, fmt.Errorf("getting deployed release '%s': %s", req.Name, err)
}
}
if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil {
return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err)
}
if rel.Info == nil {
return nil, errors.New("release info is missing")
}
......@@ -212,8 +222,13 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas
if req.Name == "" {
return nil, errMissingRelease
}
rel, err := s.env.Releases.Get(req.Name)
return &services.GetReleaseContentResponse{Release: rel}, err
if req.Version <= 0 {
rel, err := s.env.Releases.Deployed(req.Name)
return &services.GetReleaseContentResponse{Release: rel}, err
} else {
rel, err := s.env.Releases.Get(req.Name, req.Version)
return &services.GetReleaseContentResponse{Release: rel}, err
}
}
func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
......@@ -227,7 +242,7 @@ func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease
return nil, err
}
if err := s.env.Releases.Update(updatedRelease); err != nil {
if err := s.env.Releases.Create(updatedRelease); err != nil {
return nil, err
}
......@@ -263,6 +278,11 @@ func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.R
}
}
originalRelease.Info.Status.Code = release.Status_SUPERSEDED
if err := s.env.Releases.Update(originalRelease); err != nil {
return nil, fmt.Errorf("Update of %s failed: %s", originalRelease.Name, err)
}
updatedRelease.Info.Status.Code = release.Status_DEPLOYED
return res, nil
......@@ -279,7 +299,7 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
}
// finds the non-deleted release with the given name
currentRelease, err := s.env.Releases.Get(req.Name)
currentRelease, err := s.env.Releases.Deployed(req.Name)
if err != nil {
return nil, nil, err
}
......@@ -334,7 +354,7 @@ func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
return "", fmt.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen)
}
if rel, err := s.env.Releases.Get(start); err == driver.ErrReleaseNotFound {
if rel, err := s.env.Releases.Get(start, 1); err == driver.ErrReleaseNotFound {
return start, nil
} else if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) {
// Allowe re-use of names if the previous release is marked deleted.
......@@ -354,7 +374,7 @@ func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
if len(name) > releaseNameMaxLen {
name = name[:releaseNameMaxLen]
}
if _, err := s.env.Releases.Get(name); err == driver.ErrReleaseNotFound {
if _, err := s.env.Releases.Get(name, 1); err == driver.ErrReleaseNotFound {
return name, nil
}
log.Printf("info: Name %q is taken. Searching again.", name)
......@@ -610,7 +630,7 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
return nil, errMissingRelease
}
rel, err := s.env.Releases.Get(req.Name)
rel, err := s.env.Releases.Deployed(req.Name)
if err != nil {
log.Printf("uninstall: Release not loaded: %s", req.Name)
return nil, err
......@@ -620,7 +640,7 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
// already marked deleted?
if rel.Info.Status.Code == release.Status_DELETED {
if req.Purge {
if _, err := s.env.Releases.Delete(rel.Name); err != nil {
if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil {
log.Printf("uninstall: Failed to purge the release: %s", err)
return nil, err
}
......@@ -657,7 +677,7 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
log.Printf("uninstall: Failed to store updated release: %s", err)
}
} else {
if _, err := s.env.Releases.Delete(rel.Name); err != nil {
if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil {
log.Printf("uninstall: Failed to purge the release: %s", err)
}
}
......
......@@ -191,7 +191,7 @@ func TestInstallRelease(t *testing.T) {
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
......@@ -200,7 +200,7 @@ func TestInstallRelease(t *testing.T) {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name)
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
......@@ -252,7 +252,7 @@ func TestInstallReleaseWithNotes(t *testing.T) {
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
......@@ -261,7 +261,7 @@ func TestInstallReleaseWithNotes(t *testing.T) {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name)
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
......@@ -317,7 +317,7 @@ func TestInstallReleaseWithNotesRendered(t *testing.T) {
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
......@@ -326,7 +326,7 @@ func TestInstallReleaseWithNotesRendered(t *testing.T) {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name)
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
......@@ -401,7 +401,7 @@ func TestInstallReleaseDryRun(t *testing.T) {
t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest)
}
if _, err := rs.env.Releases.Get(res.Release.Name); err == nil {
if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil {
t.Errorf("Expected no stored release.")
}
......@@ -466,14 +466,14 @@ func TestInstallReleaseReuseName(t *testing.T) {
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name != rel.Name {
t.Errorf("expected %q, got %q", rel.Name, res.Release.Name)
}
getreq := &services.GetReleaseStatusRequest{Name: rel.Name}
getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}
getres, err := rs.GetReleaseStatus(c, getreq)
if err != nil {
t.Errorf("Failed to retrieve release: %s", err)
......@@ -501,7 +501,7 @@ func TestUpdateRelease(t *testing.T) {
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
t.Fatalf("Failed updated: %s", err)
}
if res.Release.Name == "" {
......@@ -516,7 +516,7 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace)
}
updated, err := rs.env.Releases.Get(res.Release.Name)
updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
......@@ -573,7 +573,7 @@ func TestUpdateReleaseNoHooks(t *testing.T) {
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
t.Fatalf("Failed updated: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
......@@ -593,7 +593,7 @@ func TestUninstallRelease(t *testing.T) {
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
t.Fatalf("Failed uninstall: %s", err)
}
if res.Release.Name != "angry-panda" {
......@@ -611,13 +611,6 @@ func TestUninstallRelease(t *testing.T) {
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
// Test that after deletion, we get an error that it is already deleted.
if _, err = rs.UninstallRelease(c, req); err == nil {
t.Error("Expected error when deleting already deleted resource.")
} else if err.Error() != "the release named \"angry-panda\" is already deleted" {
t.Errorf("Unexpected error message: %q", err)
}
}
func TestUninstallPurgeRelease(t *testing.T) {
......@@ -632,7 +625,7 @@ func TestUninstallPurgeRelease(t *testing.T) {
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
t.Fatalf("Failed uninstall: %s", err)
}
if res.Release.Name != "angry-panda" {
......@@ -650,16 +643,11 @@ func TestUninstallPurgeRelease(t *testing.T) {
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
// Test that after deletion, we get an error that it is already deleted.
if _, err = rs.UninstallRelease(c, req); err == nil {
t.Error("Expected error when deleting already deleted resource.")
} else if err.Error() != "release: not found" {
t.Errorf("Unexpected error message: %q", err)
}
}
func TestUninstallPurgeDeleteRelease(t *testing.T) {
t.Skip("TestUninstallPurgeDeleteRelease")
c := context.Background()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
......@@ -670,7 +658,7 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
_, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
t.Fatalf("Failed uninstall: %s", err)
}
req2 := &services.UninstallReleaseRequest{
......@@ -713,7 +701,7 @@ func TestGetReleaseContent(t *testing.T) {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name})
res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
......@@ -731,7 +719,7 @@ func TestGetReleaseStatus(t *testing.T) {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name})
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
......@@ -750,9 +738,9 @@ func TestGetReleaseStatusDeleted(t *testing.T) {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name})
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Errorf("Error getting release content: %s", err)
t.Fatalf("Error getting release content: %s", err)
}
if res.Info.Status.Code != release.Status_DELETED {
......
......@@ -30,21 +30,16 @@ import (
"k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors"
client "k8s.io/kubernetes/pkg/client/unversioned"
kblabels "k8s.io/kubernetes/pkg/labels"
)
var _ Driver = (*ConfigMaps)(nil)
// ConfigMapsDriverName is the string name of the driver.
const ConfigMapsDriverName = "ConfigMap"
var b64 = base64.StdEncoding
// labels is a map of key value pairs to be included as metadata in a configmap object.
type labels map[string]string
func (lbs *labels) init() { *lbs = labels(make(map[string]string)) }
func (lbs labels) get(key string) string { return lbs[key] }
func (lbs labels) set(key, val string) { lbs[key] = val }
func (lbs labels) toMap() map[string]string { return lbs }
// ConfigMaps is a wrapper around an implementation of a kubernetes
// ConfigMapsInterface.
type ConfigMaps struct {
......@@ -112,9 +107,41 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas
return results, nil
}
// Query fetches all releases that match the provided map of labels.
// An error is returned if the configmap fails to retrieve the releases.
func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) {
ls := kblabels.Set{}
for k, v := range labels {
ls[k] = v
}
opts := api.ListOptions{LabelSelector: ls.AsSelector()}
list, err := cfgmaps.impl.List(opts)
if err != nil {
logerrf(err, "query: failed to query with labels")
return nil, err
}
if len(list.Items) == 0 {
return nil, ErrReleaseNotFound
}
var results []*rspb.Release
for _, item := range list.Items {
rls, err := decodeRelease(item.Data["release"])
if err != nil {
logerrf(err, "query: failed to decode release: %s", err)
continue
}
results = append(results, rls)
}
return results, nil
}
// Create creates a new ConfigMap holding the release. If the
// ConfigMap already exists, ErrReleaseExists is returned.
func (cfgmaps *ConfigMaps) Create(rls *rspb.Release) error {
func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
// set labels for configmaps object meta data
var lbs labels
......@@ -122,7 +149,7 @@ func (cfgmaps *ConfigMaps) Create(rls *rspb.Release) error {
lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap to hold the release
obj, err := newConfigMapsObject(rls, lbs)
obj, err := newConfigMapsObject(key, rls, lbs)
if err != nil {
logerrf(err, "create: failed to encode release %q", rls.Name)
return err
......@@ -141,7 +168,7 @@ func (cfgmaps *ConfigMaps) Create(rls *rspb.Release) error {
// Update updates the ConfigMap holding the release. If not found
// the ConfigMap is created to hold the release.
func (cfgmaps *ConfigMaps) Update(rls *rspb.Release) error {
func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
// set labels for configmaps object meta data
var lbs labels
......@@ -149,7 +176,7 @@ func (cfgmaps *ConfigMaps) Update(rls *rspb.Release) error {
lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap object to hold the release
obj, err := newConfigMapsObject(rls, lbs)
obj, err := newConfigMapsObject(key, rls, lbs)
if err != nil {
logerrf(err, "update: failed to encode release %q", rls.Name)
return err
......@@ -194,7 +221,7 @@ func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
// "OWNER" - owner of the configmap, currently "TILLER".
// "NAME" - name of the release.
//
func newConfigMapsObject(rls *rspb.Release, lbs labels) (*api.ConfigMap, error) {
func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.ConfigMap, error) {
const owner = "TILLER"
// encode the release
......@@ -216,7 +243,7 @@ func newConfigMapsObject(rls *rspb.Release, lbs labels) (*api.ConfigMap, error)
// create and return configmap object
return &api.ConfigMap{
ObjectMeta: api.ObjectMeta{
Name: rls.Name,
Name: key,
Labels: lbs.toMap(),
},
Data: map[string]string{"release": s},
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -21,26 +18,22 @@ import (
"testing"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
)
var _ Driver = &ConfigMaps{}
func TestConfigMapName(t *testing.T) {
c := newTestFixture(t)
c := newTestFixtureCfgMaps(t)
if c.Name() != ConfigMapsDriverName {
t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name())
}
}
func TestConfigMapGet(t *testing.T) {
key := "key-1"
rel := newTestRelease(key, 1, rspb.Status_DEPLOYED)
vers := int32(1)
name := "smug-pigeon"
key := testKey(name, vers)
rel := releaseStub(name, vers, rspb.Status_DEPLOYED)
cfgmaps := newTestFixture(t, []*rspb.Release{rel}...)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
// get release with key
got, err := cfgmaps.Get(key)
......@@ -54,13 +47,13 @@ func TestConfigMapGet(t *testing.T) {
}
func TestConfigMapList(t *testing.T) {
cfgmaps := newTestFixture(t, []*rspb.Release{
newTestRelease("key-1", 1, rspb.Status_DELETED),
newTestRelease("key-2", 1, rspb.Status_DELETED),
newTestRelease("key-3", 1, rspb.Status_DEPLOYED),
newTestRelease("key-4", 1, rspb.Status_DEPLOYED),
newTestRelease("key-5", 1, rspb.Status_SUPERSEDED),
newTestRelease("key-6", 1, rspb.Status_SUPERSEDED),
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{
releaseStub("key-1", 1, rspb.Status_DELETED),
releaseStub("key-2", 1, rspb.Status_DELETED),
releaseStub("key-3", 1, rspb.Status_DEPLOYED),
releaseStub("key-4", 1, rspb.Status_DEPLOYED),
releaseStub("key-5", 1, rspb.Status_SUPERSEDED),
releaseStub("key-6", 1, rspb.Status_SUPERSEDED),
}...)
// list all deleted releases
......@@ -101,13 +94,15 @@ func TestConfigMapList(t *testing.T) {
}
func TestConfigMapCreate(t *testing.T) {
cfgmaps := newTestFixture(t)
cfgmaps := newTestFixtureCfgMaps(t)
key := "key-1"
rel := newTestRelease(key, 1, rspb.Status_DEPLOYED)
vers := int32(1)
name := "smug-pigeon"
key := testKey(name, vers)
rel := releaseStub(name, vers, rspb.Status_DEPLOYED)
// store the release in a configmap
if err := cfgmaps.Create(rel); err != nil {
if err := cfgmaps.Create(key, rel); err != nil {
t.Fatalf("Failed to create release with key %q: %s", key, err)
}
......@@ -124,16 +119,18 @@ func TestConfigMapCreate(t *testing.T) {
}
func TestConfigMapUpdate(t *testing.T) {
key := "key-1"
rel := newTestRelease(key, 1, rspb.Status_DEPLOYED)
vers := int32(1)
name := "smug-pigeon"
key := testKey(name, vers)
rel := releaseStub(name, vers, rspb.Status_DEPLOYED)
cfgmaps := newTestFixture(t, []*rspb.Release{rel}...)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
// modify release status code & version
rel = newTestRelease(key, 2, rspb.Status_SUPERSEDED)
// modify release status code
rel.Info.Status.Code = rspb.Status_SUPERSEDED
// perform the update
if err := cfgmaps.Update(rel); err != nil {
if err := cfgmaps.Update(key, rel); err != nil {
t.Fatalf("Failed to update release: %s", err)
}
......@@ -144,85 +141,7 @@ func TestConfigMapUpdate(t *testing.T) {
}
// check release has actually been updated by comparing modified fields
switch {
case rel.Info.Status.Code != got.Info.Status.Code:
if rel.Info.Status.Code != got.Info.Status.Code {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.Code, got.Info.Status.Code)
case rel.Version != got.Version:
t.Errorf("Expected version %d, got version %d", rel.Version, got.Version)
}
}
// newTestFixture initializes a MockConfigMapsInterface.
// ConfigMaps are created for each release provided.
func newTestFixture(t *testing.T, releases ...*rspb.Release) *ConfigMaps {
var mock MockConfigMapsInterface
mock.Init(t, releases...)
return NewConfigMaps(&mock)
}
// newTestRelease creates a release object for testing.
func newTestRelease(key string, version int32, status rspb.Status_Code) *rspb.Release {
return &rspb.Release{Name: key, Info: &rspb.Info{Status: &rspb.Status{Code: status}}, Version: version}
}
// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface
type MockConfigMapsInterface struct {
unversioned.ConfigMapsInterface
objects map[string]*api.ConfigMap
}
func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) {
mock.objects = map[string]*api.ConfigMap{}
for _, rls := range releases {
cfgmap, err := newConfigMapsObject(rls, nil)
if err != nil {
t.Fatalf("Failed to create configmap: %s", err)
}
mock.objects[rls.Name] = cfgmap
}
}
func (mock *MockConfigMapsInterface) Get(name string) (*api.ConfigMap, error) {
object, ok := mock.objects[name]
if !ok {
return nil, kberrs.NewNotFound(api.Resource("tests"), name)
}
return object, nil
}
func (mock *MockConfigMapsInterface) List(opts api.ListOptions) (*api.ConfigMapList, error) {
var list api.ConfigMapList
for _, cfgmap := range mock.objects {
list.Items = append(list.Items, *cfgmap)
}
return &list, nil
}
func (mock *MockConfigMapsInterface) Create(cfgmap *api.ConfigMap) (*api.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if object, ok := mock.objects[name]; ok {
return object, kberrs.NewAlreadyExists(api.Resource("tests"), name)
}
mock.objects[name] = cfgmap
return cfgmap, nil
}
func (mock *MockConfigMapsInterface) Update(cfgmap *api.ConfigMap) (*api.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if _, ok := mock.objects[name]; !ok {
return nil, kberrs.NewNotFound(api.Resource("tests"), name)
}
mock.objects[name] = cfgmap
return cfgmap, nil
}
func (mock *MockConfigMapsInterface) Delete(name string) error {
if _, ok := mock.objects[name]; !ok {
return kberrs.NewNotFound(api.Resource("tests"), name)
}
delete(mock.objects, name)
return nil
}
......@@ -27,6 +27,8 @@ var (
ErrReleaseNotFound = errors.New("release: not found")
// ErrReleaseExists indicates that a release already exists.
ErrReleaseExists = errors.New("release: already exists")
// ErrInvalidKey indicates that a release key could not be parsed.
ErrInvalidKey = errors.New("release: invalid key")
)
// Creator is the interface that wraps the Create method.
......@@ -34,7 +36,7 @@ var (
// Create stores the release or returns ErrReleaseExists
// if an identical release already exists.
type Creator interface {
Create(rls *rspb.Release) error
Create(key string, rls *rspb.Release) error
}
// Updator is the interface that wraps the Update method.
......@@ -42,7 +44,7 @@ type Creator interface {
// Update updates an existing release or returns
// ErrReleaseNotFound if the release does not exist.
type Updator interface {
Update(rls *rspb.Release) error
Update(key string, rls *rspb.Release) error
}
// Deletor is the interface that wraps the Delete method.
......@@ -59,9 +61,12 @@ type Deletor interface {
// if the release does not exist.
//
// List returns the set of all releases that satisfy the filter predicate.
//
// Query returns the set of all releases that match the provided label set.
type Queryor interface {
Get(key string) (*rspb.Release, error)
List(filter func(*rspb.Release) bool) ([]*rspb.Release, error)
Query(labels map[string]string) ([]*rspb.Release, error)
}
// Driver is the interface composed of Creator, Updator, Deletor, Queryor
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver
import (
"bytes"
"fmt"
"io"
)
// labels is a map of key value pairs to be included as metadata in a configmap object.
type labels map[string]string
func (lbs *labels) init() { *lbs = labels(make(map[string]string)) }
func (lbs labels) get(key string) string { return lbs[key] }
func (lbs labels) set(key, val string) { lbs[key] = val }
func (lbs labels) keys() (ls []string) {
for key := range lbs {
ls = append(ls, key)
}
return
}
func (lbs labels) match(set labels) bool {
for _, key := range set.keys() {
if lbs.get(key) != set.get(key) {
return false
}
}
return true
}
func (lbs labels) toMap() map[string]string { return lbs }
func (lbs *labels) fromMap(kvs map[string]string) {
for k, v := range kvs {
lbs.set(k, v)
}
}
func (lbs labels) dump(w io.Writer) error {
var b bytes.Buffer
fmt.Fprintln(&b, "labels:")
for k, v := range lbs {
fmt.Fprintf(&b, "\t- %q -> %q\n", k, v)
}
_, err := w.Write(b.Bytes())
return err
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"testing"
)
func TestLabelsMatch(t *testing.T) {
var tests = []struct {
desc string
set1 labels
set2 labels
expect bool
}{
{
"equal labels sets",
labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}),
labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}),
true,
},
{
"disjoint label sets",
labels(map[string]string{"KEY_C": "VAL_C", "KEY_D": "VAL_D"}),
labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}),
false,
},
}
for _, tt := range tests {
if !tt.set1.match(tt.set2) && tt.expect {
t.Fatalf("Expected match '%s'\n", tt.desc)
}
}
}
......@@ -14,26 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
package driver
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"sync"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
var _ Driver = (*Memory)(nil)
// MemoryDriverName is the string name of this driver.
const MemoryDriverName = "Memory"
// Memory is the in-memory storage driver implementation.
type Memory struct {
sync.RWMutex
cache map[string]*rspb.Release
cache map[string]records
}
// NewMemory initializes a new memory driver.
func NewMemory() *Memory {
return &Memory{cache: map[string]*rspb.Release{}}
return &Memory{cache: map[string]records{}}
}
// Name returns the name of the driver.
......@@ -45,42 +52,81 @@ func (mem *Memory) Name() string {
func (mem *Memory) Get(key string) (*rspb.Release, error) {
defer unlock(mem.rlock())
if rls, ok := mem.cache[key]; ok {
return rls, nil
switch elems := strings.Split(key, ".v"); len(elems) {
case 2:
name, ver := elems[0], elems[1]
if _, err := strconv.Atoi(ver); err != nil {
return nil, ErrInvalidKey
}
if recs, ok := mem.cache[name]; ok {
if r := recs.Get(key); r != nil {
return r.rls, nil
}
}
return nil, ErrReleaseNotFound
default:
return nil, ErrInvalidKey
}
return nil, ErrReleaseNotFound
}
// List returns the list of all releases such that filter(release) == true
func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
defer unlock(mem.rlock())
var releases []*rspb.Release
for k := range mem.cache {
if filter(mem.cache[k]) {
releases = append(releases, mem.cache[k])
}
var ls []*rspb.Release
for _, recs := range mem.cache {
recs.Iter(func(_ int, rec *record) bool {
if filter(rec.rls) {
ls = append(ls, rec.rls)
}
return true
})
}
return ls, nil
}
// Query returns the set of releases that match the provided set of labels
func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) {
defer unlock(mem.rlock())
var lbs labels
lbs.init()
lbs.fromMap(keyvals)
var ls []*rspb.Release
for _, recs := range mem.cache {
recs.Iter(func(_ int, rec *record) bool {
if rec.lbs.match(lbs) {
ls = append(ls, rec.rls)
}
return true
})
}
return releases, nil
return ls, nil
}
// Create creates a new release or returns ErrReleaseExists.
func (mem *Memory) Create(rls *rspb.Release) error {
func (mem *Memory) Create(key string, rls *rspb.Release) error {
defer unlock(mem.wlock())
if _, ok := mem.cache[rls.Name]; ok {
return ErrReleaseExists
if recs, ok := mem.cache[rls.Name]; ok {
if err := recs.Add(newRecord(key, rls)); err != nil {
return err
}
mem.cache[rls.Name] = recs
return nil
}
mem.cache[rls.Name] = rls
mem.cache[rls.Name] = records{newRecord(key, rls)}
return nil
}
// Update updates a release or returns ErrReleaseNotFound.
func (mem *Memory) Update(rls *rspb.Release) error {
func (mem *Memory) Update(key string, rls *rspb.Release) error {
defer unlock(mem.wlock())
if _, ok := mem.cache[rls.Name]; ok {
mem.cache[rls.Name] = rls
if rs, ok := mem.cache[rls.Name]; ok && rs.Exists(key) {
rs.Replace(key, newRecord(key, rls))
return nil
}
return ErrReleaseNotFound
......@@ -90,27 +136,55 @@ func (mem *Memory) Update(rls *rspb.Release) error {
func (mem *Memory) Delete(key string) (*rspb.Release, error) {
defer unlock(mem.wlock())
if old, ok := mem.cache[key]; ok {
delete(mem.cache, key)
return old, nil
switch elems := strings.Split(key, ".v"); len(elems) {
case 2:
name, ver := elems[0], elems[1]
if _, err := strconv.Atoi(ver); err != nil {
return nil, ErrInvalidKey
}
if recs, ok := mem.cache[name]; ok {
if r := recs.Remove(key); r != nil {
return r.rls, nil
}
}
return nil, ErrReleaseNotFound
default:
return nil, ErrInvalidKey
}
}
func (mem *Memory) dump(w io.Writer) error {
var b bytes.Buffer
fmt.Fprintln(&b, "memory:")
for key, recs := range mem.cache {
fmt.Fprintf(&b, "\t# %q\n", key)
recs.Iter(func(index int, r *record) bool {
fmt.Fprintf(&b, "\t\t- [%d] v%d (status = %s)\n",
index,
r.rls.Version,
r.rls.Info.Status.Code,
)
return true
})
}
return nil, ErrReleaseNotFound
_, err := w.Write(b.Bytes())
return err
}
// wlock locks mem for writing
func (mem *Memory) wlock() func() {
mem.Lock()
return func() {
mem.Unlock()
}
return func() { mem.Unlock() }
}
// rlock locks mem for reading
func (mem *Memory) rlock() func() {
mem.RLock()
return func() {
mem.RUnlock()
}
return func() { mem.RUnlock() }
}
// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g:
......
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
package driver
import (
"reflect"
......@@ -23,79 +23,146 @@ import (
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
var _ Driver = &Memory{}
func TestMemoryName(t *testing.T) {
mem := NewMemory()
if mem.Name() != MemoryDriverName {
if mem := NewMemory(); mem.Name() != MemoryDriverName {
t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name())
}
}
func TestMemoryGet(t *testing.T) {
key := "test-1"
rls := &rspb.Release{Name: key}
func TestMemoryCreate(t *testing.T) {
var tests = []struct {
desc string
rls *rspb.Release
err bool
}{
{
"create should success",
releaseStub("rls-c", 1, rspb.Status_DEPLOYED),
false,
},
{
"create should fail (release already exists)",
releaseStub("rls-a", 1, rspb.Status_DEPLOYED),
true,
},
}
ts := tsFixtureMemory(t)
for _, tt := range tests {
key := testKey(tt.rls.Name, tt.rls.Version)
rls := tt.rls
mem := NewMemory()
if err := mem.Create(rls); err != nil {
t.Fatalf("Failed create: %s", err)
if err := ts.Create(key, rls); err != nil {
if !tt.err {
t.Fatalf("failed to create %q: %s", tt.desc, err)
}
}
}
}
res, err := mem.Get(key)
if err != nil {
t.Errorf("Could not get %s: %s", key, err)
func TestMemoryGet(t *testing.T) {
var tests = []struct {
desc string
key string
err bool
}{
{"release key should exist", "rls-a.v1", false},
{"release key should not exist", "rls-a.v5", true},
}
if res.Name != key {
t.Errorf("Expected %s, got %s", key, res.Name)
ts := tsFixtureMemory(t)
for _, tt := range tests {
if _, err := ts.Get(tt.key); err != nil {
if !tt.err {
t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err)
}
}
}
}
func TestMemoryCreate(t *testing.T) {
key := "test-1"
rls := &rspb.Release{Name: key}
mem := NewMemory()
if err := mem.Create(rls); err != nil {
t.Fatalf("Failed created: %s", err)
func TestMemoryQuery(t *testing.T) {
var tests = []struct {
desc string
xlen int
lbs map[string]string
}{
{
"should be 2 query results",
2,
map[string]string{"STATUS": "DEPLOYED"},
},
}
if mem.cache[key].Name != key {
t.Errorf("Unexpected release name: %s", mem.cache[key].Name)
ts := tsFixtureMemory(t)
for _, tt := range tests {
l, err := ts.Query(tt.lbs)
if err != nil {
t.Fatalf("Failed to query: %s\n", err)
}
if tt.xlen != len(l) {
t.Fatalf("Expected %d results, actual %d\n", tt.xlen, len(l))
}
}
}
func TestMemoryUpdate(t *testing.T) {
key := "test-1"
rls := &rspb.Release{Name: key}
mem := NewMemory()
if err := mem.Create(rls); err != nil {
t.Fatalf("Failed create: %s", err)
}
if err := mem.Update(rls); err != nil {
t.Fatalf("Failed update: %s", err)
var tests = []struct {
desc string
key string
rls *rspb.Release
err bool
}{
{
"update release status",
"rls-a.v4",
releaseStub("rls-a", 4, rspb.Status_SUPERSEDED),
false,
},
{
"update release does not exist",
"rls-z.v1",
releaseStub("rls-z", 1, rspb.Status_DELETED),
true,
},
}
if mem.cache[key].Name != key {
t.Errorf("Unexpected release name: %s", mem.cache[key].Name)
ts := tsFixtureMemory(t)
for _, tt := range tests {
if err := ts.Update(tt.key, tt.rls); err != nil {
if !tt.err {
t.Fatalf("Failed %q: %s\n", tt.desc, err)
}
continue
}
r, err := ts.Get(tt.key)
if err != nil {
t.Fatalf("Failed to get: %s\n", err)
}
if !reflect.DeepEqual(r, tt.rls) {
t.Fatalf("Expected %s, actual %s\n", tt.rls, r)
}
}
}
func TestMemoryDelete(t *testing.T) {
key := "test-1"
rls := &rspb.Release{Name: key}
mem := NewMemory()
if err := mem.Create(rls); err != nil {
t.Fatalf("Failed create: %s", err)
var tests = []struct {
desc string
key string
err bool
}{
{"release key should exist", "rls-a.v1", false},
{"release key should not exist", "rls-a.v5", true},
}
res, err := mem.Delete(key)
if err != nil {
t.Fatalf("Failed delete: %s", err)
}
if mem.cache[key] != nil {
t.Errorf("Expected nil, got %s", mem.cache[key])
}
if !reflect.DeepEqual(rls, res) {
t.Errorf("Expected %s, got %s", rls, res)
ts := tsFixtureMemory(t)
for _, tt := range tests {
if _, err := ts.Delete(tt.key); err != nil {
if !tt.err {
t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err)
}
}
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"sort"
"strconv"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
// records holds a list of in-memory release records
type records []*record
func (rs records) Len() int { return len(rs) }
func (rs records) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
func (rs records) Less(i, j int) bool { return rs[i].rls.Version < rs[j].rls.Version }
func (rs *records) Add(r *record) error {
if r == nil {
return nil
}
if rs.Exists(r.key) {
return ErrReleaseExists
}
*rs = append(*rs, r)
sort.Sort(*rs)
return nil
}
func (rs records) Get(key string) *record {
if i, ok := rs.Index(key); ok {
return rs[i]
}
return nil
}
func (rs *records) Iter(fn func(int, *record) bool) {
cp := make([]*record, len(*rs))
copy(cp, *rs)
for i, r := range cp {
if !fn(i, r) {
return
}
}
}
func (rs *records) Index(key string) (int, bool) {
for i, r := range *rs {
if r.key == key {
return i, true
}
}
return -1, false
}
func (rs records) Exists(key string) bool {
_, ok := rs.Index(key)
return ok
}
func (rs *records) Remove(key string) (r *record) {
if i, ok := rs.Index(key); ok {
return rs.removeAt(i)
}
return nil
}
func (rs *records) Replace(key string, rec *record) *record {
if i, ok := rs.Index(key); ok {
old := (*rs)[i]
(*rs)[i] = rec
return old
}
return nil
}
func (rs records) FindByVersion(vers int32) (int, bool) {
i := sort.Search(len(rs), func(i int) bool {
return rs[i].rls.Version == vers
})
if i < len(rs) && rs[i].rls.Version == vers {
return i, true
}
return i, false
}
func (rs *records) removeAt(index int) *record {
r := (*rs)[index]
(*rs)[index] = nil
copy((*rs)[index:], (*rs)[index+1:])
*rs = (*rs)[:len(*rs)-1]
return r
}
// record is the data structure used to cache releases
// for the in-memory storage driver
type record struct {
key string
lbs labels
rls *rspb.Release
}
// newRecord creates a new in-memory release record
func newRecord(key string, rls *rspb.Release) *record {
var lbs labels
lbs.init()
lbs.set("NAME", rls.Name)
lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
return &record{key: key, lbs: lbs, rls: rls}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"testing"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
func TestRecordsAdd(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_SUPERSEDED)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, rspb.Status_DEPLOYED)),
})
var tests = []struct {
desc string
key string
ok bool
rec *record
}{
{
"add valid key",
"rls-a.v3",
false,
newRecord("rls-a.v3", releaseStub("rls-a", 3, rspb.Status_SUPERSEDED)),
},
{
"add already existing key",
"rls-a.v1",
true,
newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_DEPLOYED)),
},
}
for _, tt := range tests {
if err := rs.Add(tt.rec); err != nil {
if !tt.ok {
t.Fatalf("failed: %q: %s\n", tt.desc, err)
}
}
}
}
func TestRecordsRemove(t *testing.T) {
var tests = []struct {
desc string
key string
ok bool
}{
{"remove valid key", "rls-a.v1", false},
{"remove invalid key", "rls-a.v", true},
{"remove non-existent key", "rls-z.v1", true},
}
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_SUPERSEDED)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, rspb.Status_DEPLOYED)),
})
for _, tt := range tests {
if r := rs.Remove(tt.key); r == nil {
if !tt.ok {
t.Fatalf("Failed to %q (key = %s). Expected nil, got %s",
tt.desc,
tt.key,
r,
)
}
}
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"fmt"
"testing"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
)
func releaseStub(name string, vers int32, code rspb.Status_Code) *rspb.Release {
return &rspb.Release{
Name: name,
Version: vers,
Info: &rspb.Info{Status: &rspb.Status{Code: code}},
}
}
func testKey(name string, vers int32) string {
return fmt.Sprintf("%s.v%d", name, vers)
}
func tsFixtureMemory(t *testing.T) *Memory {
hs := []*rspb.Release{
// rls-a
releaseStub("rls-a", 4, rspb.Status_DEPLOYED),
releaseStub("rls-a", 1, rspb.Status_SUPERSEDED),
releaseStub("rls-a", 3, rspb.Status_SUPERSEDED),
releaseStub("rls-a", 2, rspb.Status_SUPERSEDED),
// rls-b
releaseStub("rls-b", 4, rspb.Status_DEPLOYED),
releaseStub("rls-b", 1, rspb.Status_SUPERSEDED),
releaseStub("rls-b", 3, rspb.Status_SUPERSEDED),
releaseStub("rls-b", 2, rspb.Status_SUPERSEDED),
}
mem := NewMemory()
for _, tt := range hs {
err := mem.Create(testKey(tt.Name, tt.Version), tt)
if err != nil {
t.Fatalf("Test setup failed to create: %s\n", err)
}
}
return mem
}
// newTestFixture initializes a MockConfigMapsInterface.
// ConfigMaps are created for each release provided.
func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps {
var mock MockConfigMapsInterface
mock.Init(t, releases...)
return NewConfigMaps(&mock)
}
// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface
type MockConfigMapsInterface struct {
unversioned.ConfigMapsInterface
objects map[string]*api.ConfigMap
}
// Init initializes the MockConfigMapsInterface with the set of releases.
func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) {
mock.objects = map[string]*api.ConfigMap{}
for _, rls := range releases {
objkey := testKey(rls.Name, rls.Version)
cfgmap, err := newConfigMapsObject(objkey, rls, nil)
if err != nil {
t.Fatalf("Failed to create configmap: %s", err)
}
mock.objects[objkey] = cfgmap
}
}
// Get returns the ConfigMap by name.
func (mock *MockConfigMapsInterface) Get(name string) (*api.ConfigMap, error) {
object, ok := mock.objects[name]
if !ok {
return nil, kberrs.NewNotFound(api.Resource("tests"), name)
}
return object, nil
}
// List returns the a of ConfigMaps.
func (mock *MockConfigMapsInterface) List(opts api.ListOptions) (*api.ConfigMapList, error) {
var list api.ConfigMapList
for _, cfgmap := range mock.objects {
list.Items = append(list.Items, *cfgmap)
}
return &list, nil
}
// Create creates a new ConfigMap.
func (mock *MockConfigMapsInterface) Create(cfgmap *api.ConfigMap) (*api.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if object, ok := mock.objects[name]; ok {
return object, kberrs.NewAlreadyExists(api.Resource("tests"), name)
}
mock.objects[name] = cfgmap
return cfgmap, nil
}
// Update updates a ConfigMap.
func (mock *MockConfigMapsInterface) Update(cfgmap *api.ConfigMap) (*api.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if _, ok := mock.objects[name]; !ok {
return nil, kberrs.NewNotFound(api.Resource("tests"), name)
}
mock.objects[name] = cfgmap
return cfgmap, nil
}
// Delete deletes a ConfigMap by name.
func (mock *MockConfigMapsInterface) Delete(name string) error {
if _, ok := mock.objects[name]; !ok {
return kberrs.NewNotFound(api.Resource("tests"), name)
}
delete(mock.objects, name)
return nil
}
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
package storage // import "k8s.io/helm/pkg/storage"
import rspb "k8s.io/helm/pkg/proto/hapi/release"
......
......@@ -17,6 +17,7 @@ limitations under the License.
package storage // import "k8s.io/helm/pkg/storage"
import (
"fmt"
"log"
rspb "k8s.io/helm/pkg/proto/hapi/release"
......@@ -30,34 +31,34 @@ type Storage struct {
// Get retrieves the release from storage. An error is returned
// if the storage driver failed to fetch the release, or the
// release identified by key does not exist.
func (s *Storage) Get(key string) (*rspb.Release, error) {
log.Printf("Getting release %q from storage\n", key)
return s.Driver.Get(key)
// release identified by the key, version pair does not exist.
func (s *Storage) Get(name string, version int32) (*rspb.Release, error) {
log.Printf("Getting release %q (v%d) from storage\n", name, version)
return s.Driver.Get(makeKey(name, version))
}
// Create creates a new storage entry holding the release. An
// error is returned if the storage driver failed to store the
// release, or a release with identical an key already exists.
func (s *Storage) Create(rls *rspb.Release) error {
log.Printf("Create release %q in storage\n", rls.Name)
return s.Driver.Create(rls)
log.Printf("Create release %q (v%d) in storage\n", rls.Name, rls.Version)
return s.Driver.Create(makeKey(rls.Name, rls.Version), rls)
}
// Update update the release in storage. An error is returned if the
// storage backend fails to update the release or if the release
// does not exist.
func (s *Storage) Update(rls *rspb.Release) error {
log.Printf("Updating %q in storage\n", rls.Name)
return s.Driver.Update(rls)
log.Printf("Updating %q (v%d) in storage\n", rls.Name, rls.Version)
return s.Driver.Update(makeKey(rls.Name, rls.Version), rls)
}
// Delete deletes the release from storage. An error is returned if
// the storage backend fails to delete the release or if the release
// does not exist.
func (s *Storage) Delete(key string) (*rspb.Release, error) {
log.Printf("Deleting release %q from storage\n", key)
return s.Driver.Delete(key)
func (s *Storage) Delete(name string, version int32) (*rspb.Release, error) {
log.Printf("Deleting release %q (v%d) from storage\n", name, version)
return s.Driver.Delete(makeKey(name, version))
}
// ListReleases returns all releases from storage. An error is returned if the
......@@ -105,6 +106,32 @@ func (s *Storage) ListFilterAny(filters ...FilterFunc) ([]*rspb.Release, error)
})
}
// Deployed returns the deployed release with the provided release name, or
// returns ErrReleaseNotFound if not found.
func (s *Storage) Deployed(name string) (*rspb.Release, error) {
log.Printf("Getting deployed release from '%s' history\n", name)
ls, err := s.Driver.Query(map[string]string{
"NAME": name,
"STATUS": "DEPLOYED",
})
switch {
case err != nil:
return nil, err
case len(ls) == 0:
return nil, fmt.Errorf("'%s' has no deployed releases", name)
default:
return ls[0], nil
}
}
// makeKey concatenates a release name and version into
// a string with format ```<release_name>#v<version>```.
// This key is used to uniquely identify storage objects.
func makeKey(rlsname string, version int32) string {
return fmt.Sprintf("%s.v%d", rlsname, version)
}
// Init initializes a new storage backend with the driver d.
// If d is nil, the default in-memory driver is used.
func Init(d driver.Driver) *Storage {
......
......@@ -30,11 +30,15 @@ func TestStorageCreate(t *testing.T) {
storage := Init(driver.NewMemory())
// create fake release
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
rls := ReleaseTestData{
Name: "angry-beaver",
Version: 1,
}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
// fetch the release
res, err := storage.Get(rls.Name)
res, err := storage.Get(rls.Name, rls.Version)
assertErrNil(t.Fatal, err, "QueryRelease")
// verify the fetched and created release are the same
......@@ -48,16 +52,20 @@ func TestStorageUpdate(t *testing.T) {
storage := Init(driver.NewMemory())
// create fake release
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
rls := ReleaseTestData{
Name: "angry-beaver",
Version: 1,
Status: rspb.Status_DEPLOYED,
}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
// modify the release
rls.Version = 2
rls.Manifest = "new-manifest"
rls.Info.Status.Code = rspb.Status_DELETED
assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease")
// retrieve the updated release
res, err := storage.Get(rls.Name)
res, err := storage.Get(rls.Name, rls.Version)
assertErrNil(t.Fatal, err, "QueryRelease")
// verify updated and fetched releases are the same.
......@@ -71,11 +79,15 @@ func TestStorageDelete(t *testing.T) {
storage := Init(driver.NewMemory())
// create fake release
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
rls := ReleaseTestData{
Name: "angry-beaver",
Version: 1,
}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
// delete the release
res, err := storage.Delete(rls.Name)
res, err := storage.Delete(rls.Name, rls.Version)
assertErrNil(t.Fatal, err, "DeleteRelease")
// verify updated and fetched releases are the same.
......@@ -134,6 +146,46 @@ func TestStorageList(t *testing.T) {
}
}
func TestStorageDeployed(t *testing.T) {
storage := Init(driver.NewMemory())
const name = "angry-bird"
const vers = int32(4)
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_DEPLOYED}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)")
assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)")
assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)")
}
setup()
rls, err := storage.Deployed(name)
if err != nil {
t.Fatalf("Failed to query for deployed release: %s\n", err)
}
switch {
case rls == nil:
t.Fatalf("Release is nil")
case rls.Name != name:
t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name)
case rls.Version != vers:
t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version)
case rls.Info.Status.Code != rspb.Status_DEPLOYED:
t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.Code)
}
}
type ReleaseTestData struct {
Name string
Version int32
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment