mirror of https://github.com/helm/helm.git
update pkg/storage to support rollback
This commit is contained in:
parent
36606cf152
commit
383a9c186a
|
|
@ -17,168 +17,195 @@ limitations under the License.
|
|||
package driver // import "k8s.io/helm/pkg/storage/driver"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
kberrs "k8s.io/kubernetes/pkg/api/errors"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"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 {
|
||||
impl client.ConfigMapsInterface
|
||||
impl client.ConfigMapsInterface
|
||||
}
|
||||
|
||||
// NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of
|
||||
// the kubernetes ConfigMapsInterface.
|
||||
func NewConfigMaps(impl client.ConfigMapsInterface) *ConfigMaps {
|
||||
return &ConfigMaps{impl: impl}
|
||||
return &ConfigMaps{impl: impl}
|
||||
}
|
||||
|
||||
// Name returns the name of the driver.
|
||||
func (cfgmaps *ConfigMaps) Name() string {
|
||||
return ConfigMapsDriverName
|
||||
return ConfigMapsDriverName
|
||||
}
|
||||
|
||||
// Get fetches the release named by key. The corresponding release is returned
|
||||
// or error if not found.
|
||||
func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
|
||||
// fetch the configmap holding the release named by key
|
||||
obj, err := cfgmaps.impl.Get(key)
|
||||
if err != nil {
|
||||
if kberrs.IsNotFound(err) {
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
// fetch the configmap holding the release named by key
|
||||
obj, err := cfgmaps.impl.Get(key)
|
||||
if err != nil {
|
||||
if kberrs.IsNotFound(err) {
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
logerrf(err, "get: failed to get %q", key)
|
||||
return nil, err
|
||||
}
|
||||
// found the configmap, decode the base64 data string
|
||||
r, err := decodeRelease(obj.Data["release"])
|
||||
if err != nil {
|
||||
logerrf(err, "get: failed to decode data %q", key)
|
||||
return nil, err
|
||||
}
|
||||
// return the release object
|
||||
return r, nil
|
||||
logerrf(err, "get: failed to get %q", key)
|
||||
return nil, err
|
||||
}
|
||||
// found the configmap, decode the base64 data string
|
||||
r, err := decodeRelease(obj.Data["release"])
|
||||
if err != nil {
|
||||
logerrf(err, "get: failed to decode data %q", key)
|
||||
return nil, err
|
||||
}
|
||||
// return the release object
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// List fetches all releases and returns the list releases such
|
||||
// that filter(release) == true. An error is returned if the
|
||||
// configmap fails to retrieve the releases.
|
||||
func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
|
||||
list, err := cfgmaps.impl.List(api.ListOptions{})
|
||||
if err != nil {
|
||||
logerrf(err, "list: failed to list")
|
||||
return nil, err
|
||||
}
|
||||
list, err := cfgmaps.impl.List(api.ListOptions{})
|
||||
if err != nil {
|
||||
logerrf(err, "list: failed to list")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []*rspb.Release
|
||||
var results []*rspb.Release
|
||||
|
||||
// iterate over the configmaps object list
|
||||
// and decode each release
|
||||
for _, item := range list.Items {
|
||||
rls, err := decodeRelease(item.Data["release"])
|
||||
if err != nil {
|
||||
logerrf(err, "list: failed to decode release: %s", rls)
|
||||
continue
|
||||
}
|
||||
if filter(rls) {
|
||||
results = append(results, rls)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
// iterate over the configmaps object list
|
||||
// and decode each release
|
||||
for _, item := range list.Items {
|
||||
rls, err := decodeRelease(item.Data["release"])
|
||||
if err != nil {
|
||||
logerrf(err, "list: failed to decode release: %s", rls)
|
||||
continue
|
||||
}
|
||||
if filter(rls) {
|
||||
results = append(results, rls)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// set labels for configmaps object meta data
|
||||
var lbs labels
|
||||
func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
|
||||
// set labels for configmaps object meta data
|
||||
var lbs labels
|
||||
|
||||
lbs.init()
|
||||
lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
|
||||
lbs.init()
|
||||
lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
|
||||
|
||||
// create a new configmap to hold the release
|
||||
obj, err := newConfigMapsObject(rls, lbs)
|
||||
if err != nil {
|
||||
logerrf(err, "create: failed to encode release %q", rls.Name)
|
||||
return err
|
||||
}
|
||||
// push the configmap object out into the kubiverse
|
||||
if _, err := cfgmaps.impl.Create(obj); err != nil {
|
||||
if kberrs.IsAlreadyExists(err) {
|
||||
return ErrReleaseExists
|
||||
}
|
||||
// create a new configmap to hold the release
|
||||
obj, err := newConfigMapsObject(key, rls, lbs)
|
||||
if err != nil {
|
||||
logerrf(err, "create: failed to encode release %q", rls.Name)
|
||||
return err
|
||||
}
|
||||
// push the configmap object out into the kubiverse
|
||||
if _, err := cfgmaps.impl.Create(obj); err != nil {
|
||||
if kberrs.IsAlreadyExists(err) {
|
||||
return ErrReleaseExists
|
||||
}
|
||||
|
||||
logerrf(err, "create: failed to create")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
logerrf(err, "create: failed to create")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// set labels for configmaps object meta data
|
||||
var lbs labels
|
||||
func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
|
||||
// set labels for configmaps object meta data
|
||||
var lbs labels
|
||||
|
||||
lbs.init()
|
||||
lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
|
||||
lbs.init()
|
||||
lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
|
||||
|
||||
// create a new configmap object to hold the release
|
||||
obj, err := newConfigMapsObject(rls, lbs)
|
||||
if err != nil {
|
||||
logerrf(err, "update: failed to encode release %q", rls.Name)
|
||||
return err
|
||||
}
|
||||
// push the configmap object out into the kubiverse
|
||||
_, err = cfgmaps.impl.Update(obj)
|
||||
if err != nil {
|
||||
logerrf(err, "update: failed to update")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// create a new configmap object to hold the release
|
||||
obj, err := newConfigMapsObject(key, rls, lbs)
|
||||
if err != nil {
|
||||
logerrf(err, "update: failed to encode release %q", rls.Name)
|
||||
return err
|
||||
}
|
||||
// push the configmap object out into the kubiverse
|
||||
_, err = cfgmaps.impl.Update(obj)
|
||||
if err != nil {
|
||||
logerrf(err, "update: failed to update")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the ConfigMap holding the release named by key.
|
||||
func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
|
||||
// fetch the release to check existence
|
||||
if rls, err = cfgmaps.Get(key); err != nil {
|
||||
if kberrs.IsNotFound(err) {
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
// fetch the release to check existence
|
||||
if rls, err = cfgmaps.Get(key); err != nil {
|
||||
if kberrs.IsNotFound(err) {
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
logerrf(err, "delete: failed to get release %q", rls.Name)
|
||||
return nil, err
|
||||
}
|
||||
// delete the release
|
||||
if err = cfgmaps.impl.Delete(key); err != nil {
|
||||
return rls, err
|
||||
}
|
||||
return rls, nil
|
||||
logerrf(err, "delete: failed to get release %q", rls.Name)
|
||||
return nil, err
|
||||
}
|
||||
// delete the release
|
||||
if err = cfgmaps.impl.Delete(key); err != nil {
|
||||
return rls, err
|
||||
}
|
||||
return rls, nil
|
||||
}
|
||||
|
||||
// newConfigMapsObject constructs a kubernetes ConfigMap object
|
||||
|
|
@ -194,43 +221,43 @@ 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) {
|
||||
const owner = "TILLER"
|
||||
func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.ConfigMap, error) {
|
||||
const owner = "TILLER"
|
||||
|
||||
// encode the release
|
||||
s, err := encodeRelease(rls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// encode the release
|
||||
s, err := encodeRelease(rls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lbs == nil {
|
||||
lbs.init()
|
||||
}
|
||||
if lbs == nil {
|
||||
lbs.init()
|
||||
}
|
||||
|
||||
// apply labels
|
||||
lbs.set("NAME", rls.Name)
|
||||
lbs.set("OWNER", owner)
|
||||
lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
|
||||
lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
|
||||
// apply labels
|
||||
lbs.set("NAME", rls.Name)
|
||||
lbs.set("OWNER", owner)
|
||||
lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
|
||||
lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
|
||||
|
||||
// create and return configmap object
|
||||
return &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: rls.Name,
|
||||
Labels: lbs.toMap(),
|
||||
},
|
||||
Data: map[string]string{"release": s},
|
||||
}, nil
|
||||
// create and return configmap object
|
||||
return &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: key,
|
||||
Labels: lbs.toMap(),
|
||||
},
|
||||
Data: map[string]string{"release": s},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodeRelease encodes a release returning a base64 encoded
|
||||
// binary protobuf encoding representation, or error.
|
||||
func encodeRelease(rls *rspb.Release) (string, error) {
|
||||
b, err := proto.Marshal(rls)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b64.EncodeToString(b), nil
|
||||
b, err := proto.Marshal(rls)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b64.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// decodeRelease decodes the bytes in data into a release
|
||||
|
|
@ -238,21 +265,21 @@ func encodeRelease(rls *rspb.Release) (string, error) {
|
|||
// valid protobuf encoding of a release, otherwise
|
||||
// an error is returned.
|
||||
func decodeRelease(data string) (*rspb.Release, error) {
|
||||
// base64 decode string
|
||||
b, err := b64.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// base64 decode string
|
||||
b, err := b64.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rls rspb.Release
|
||||
// unmarshal protobuf bytes
|
||||
if err := proto.Unmarshal(b, &rls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rls, nil
|
||||
var rls rspb.Release
|
||||
// unmarshal protobuf bytes
|
||||
if err := proto.Unmarshal(b, &rls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rls, nil
|
||||
}
|
||||
|
||||
// logerrf wraps an error with the a formatted string (used for debugging)
|
||||
func logerrf(err error, format string, args ...interface{}) {
|
||||
log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err)
|
||||
log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
/*
|
||||
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.
|
||||
|
|
@ -17,212 +14,134 @@ limitations under the License.
|
|||
package driver
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"reflect"
|
||||
"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"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
var _ Driver = &ConfigMaps{}
|
||||
|
||||
func TestConfigMapName(t *testing.T) {
|
||||
c := newTestFixture(t)
|
||||
if c.Name() != ConfigMapsDriverName {
|
||||
t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name())
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release: %s", err)
|
||||
}
|
||||
// compare fetched release with original
|
||||
if !reflect.DeepEqual(rel, got) {
|
||||
t.Errorf("Expected {%q}, got {%q}", rel, got)
|
||||
}
|
||||
// get release with key
|
||||
got, err := cfgmaps.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release: %s", err)
|
||||
}
|
||||
// compare fetched release with original
|
||||
if !reflect.DeepEqual(rel, got) {
|
||||
t.Errorf("Expected {%q}, got {%q}", rel, got)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
del, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_DELETED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deleted: %s", err)
|
||||
}
|
||||
if len(del) != 2 {
|
||||
t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del)
|
||||
}
|
||||
// list all deleted releases
|
||||
del, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_DELETED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deleted: %s", err)
|
||||
}
|
||||
if len(del) != 2 {
|
||||
t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del)
|
||||
}
|
||||
|
||||
// list all deployed releases
|
||||
dpl, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_DEPLOYED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deployed: %s", err)
|
||||
}
|
||||
if len(dpl) != 2 {
|
||||
t.Errorf("Expected 2 deployed, got %d", len(dpl))
|
||||
}
|
||||
// list all deployed releases
|
||||
dpl, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_DEPLOYED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deployed: %s", err)
|
||||
}
|
||||
if len(dpl) != 2 {
|
||||
t.Errorf("Expected 2 deployed, got %d", len(dpl))
|
||||
}
|
||||
|
||||
// list all superseded releases
|
||||
ssd, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_SUPERSEDED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list superseded: %s", err)
|
||||
}
|
||||
if len(ssd) != 2 {
|
||||
t.Errorf("Expected 2 superseded, got %d", len(ssd))
|
||||
}
|
||||
// list all superseded releases
|
||||
ssd, err := cfgmaps.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status.Code == rspb.Status_SUPERSEDED
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list superseded: %s", err)
|
||||
}
|
||||
if len(ssd) != 2 {
|
||||
t.Errorf("Expected 2 superseded, got %d", len(ssd))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatalf("Failed to create release with key %q: %s", key, err)
|
||||
}
|
||||
// store the release in a configmap
|
||||
if err := cfgmaps.Create(key, rel); err != nil {
|
||||
t.Fatalf("Failed to create release with key %q: %s", key, err)
|
||||
}
|
||||
|
||||
// get the release back
|
||||
got, err := cfgmaps.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release with key %q: %s", key, err)
|
||||
}
|
||||
// get the release back
|
||||
got, err := cfgmaps.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release with key %q: %s", key, err)
|
||||
}
|
||||
|
||||
// compare created release with original
|
||||
if !reflect.DeepEqual(rel, got) {
|
||||
t.Errorf("Expected {%q}, got {%q}", rel, got)
|
||||
}
|
||||
// compare created release with original
|
||||
if !reflect.DeepEqual(rel, got) {
|
||||
t.Errorf("Expected {%q}, got {%q}", rel, got)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatalf("Failed to update release: %s", err)
|
||||
}
|
||||
// perform the update
|
||||
if err := cfgmaps.Update(key, rel); err != nil {
|
||||
t.Fatalf("Failed to update release: %s", err)
|
||||
}
|
||||
|
||||
// fetch the updated release
|
||||
got, err := cfgmaps.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release with key %q: %s", key, err)
|
||||
}
|
||||
// fetch the updated release
|
||||
got, err := cfgmaps.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release with key %q: %s", key, err)
|
||||
}
|
||||
|
||||
// check release has actually been updated by comparing modified fields
|
||||
switch {
|
||||
case 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
|
||||
}
|
||||
// check release has actually been updated by comparing modified fields
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,16 +17,18 @@ limitations under the License.
|
|||
package driver // import "k8s.io/helm/pkg/storage/driver"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"errors"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrReleaseNotFound indicates that a release is not found.
|
||||
ErrReleaseNotFound = errors.New("release: not found")
|
||||
// ErrReleaseExists indicates that a release already exists.
|
||||
ErrReleaseExists = errors.New("release: already exists")
|
||||
// ErrReleaseNotFound indicates that a release is not found.
|
||||
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.
|
||||
|
|
@ -50,7 +52,7 @@ type Updator interface {
|
|||
// Delete deletes the release named by key or returns
|
||||
// ErrReleaseNotFound if the release does not exist.
|
||||
type Deletor interface {
|
||||
Delete(key string) (*rspb.Release, error)
|
||||
Delete(key string) (*rspb.Release, error)
|
||||
}
|
||||
|
||||
// Queryor is the interface that wraps the Get and List methods.
|
||||
|
|
@ -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)
|
||||
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
|
||||
|
|
@ -69,9 +74,9 @@ type Queryor interface {
|
|||
// and retrieving tiller releases from some underlying storage mechanism,
|
||||
// e.g. memory, configmaps.
|
||||
type Driver interface {
|
||||
Creator
|
||||
Updator
|
||||
Deletor
|
||||
Queryor
|
||||
Name() string
|
||||
Creator
|
||||
Updator
|
||||
Deletor
|
||||
Queryor
|
||||
Name() string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package 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,103 +14,178 @@ 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 (
|
||||
"sync"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
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
|
||||
sync.RWMutex
|
||||
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.
|
||||
func (mem *Memory) Name() string {
|
||||
return MemoryDriverName
|
||||
return MemoryDriverName
|
||||
}
|
||||
|
||||
// Get returns the release named by key or returns ErrReleaseNotFound.
|
||||
func (mem *Memory) Get(key string) (*rspb.Release, error) {
|
||||
defer unlock(mem.rlock())
|
||||
defer unlock(mem.rlock())
|
||||
|
||||
if rls, ok := mem.cache[key]; ok {
|
||||
return rls, nil
|
||||
}
|
||||
return nil, ErrReleaseNotFound
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
defer unlock(mem.rlock())
|
||||
|
||||
var releases []*rspb.Release
|
||||
for k := range mem.cache {
|
||||
if filter(mem.cache[k]) {
|
||||
releases = append(releases, mem.cache[k])
|
||||
}
|
||||
}
|
||||
return releases, nil
|
||||
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 ls, nil
|
||||
}
|
||||
|
||||
// Create creates a new release or returns ErrReleaseExists.
|
||||
func (mem *Memory) Create(rls *rspb.Release) error {
|
||||
defer unlock(mem.wlock())
|
||||
func (mem *Memory) Create(key string, rls *rspb.Release) error {
|
||||
defer unlock(mem.wlock())
|
||||
|
||||
if _, ok := mem.cache[rls.Name]; ok {
|
||||
return ErrReleaseExists
|
||||
}
|
||||
mem.cache[rls.Name] = rls
|
||||
return nil
|
||||
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] = records{newRecord(key, rls)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates a release or returns ErrReleaseNotFound.
|
||||
func (mem *Memory) Update(rls *rspb.Release) error {
|
||||
defer unlock(mem.wlock())
|
||||
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
|
||||
return nil
|
||||
}
|
||||
return ErrReleaseNotFound
|
||||
if rs, ok := mem.cache[rls.Name]; ok && rs.Exists(key) {
|
||||
rs.Replace(key, newRecord(key, rls))
|
||||
return nil
|
||||
}
|
||||
return ErrReleaseNotFound
|
||||
}
|
||||
|
||||
// Delete deletes a release or returns ErrReleaseNotFound.
|
||||
func (mem *Memory) Delete(key string) (*rspb.Release, error) {
|
||||
defer unlock(mem.wlock())
|
||||
defer unlock(mem.wlock())
|
||||
|
||||
if old, ok := mem.cache[key]; ok {
|
||||
delete(mem.cache, key)
|
||||
return old, nil
|
||||
}
|
||||
return nil, ErrReleaseNotFound
|
||||
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
|
||||
}
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
_, err := w.Write(b.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// wlock locks mem for writing
|
||||
func (mem *Memory) wlock() func() {
|
||||
mem.Lock()
|
||||
return func() {
|
||||
mem.Unlock()
|
||||
}
|
||||
mem.Lock()
|
||||
return func() { mem.Unlock() }
|
||||
}
|
||||
|
||||
// rlock locks mem for reading
|
||||
func (mem *Memory) rlock() func() {
|
||||
mem.RLock()
|
||||
return func() {
|
||||
mem.RUnlock()
|
||||
}
|
||||
mem.RLock()
|
||||
return func() { mem.RUnlock() }
|
||||
}
|
||||
|
||||
// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g:
|
||||
|
|
|
|||
|
|
@ -14,88 +14,155 @@ 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"
|
||||
"testing"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
var _ Driver = &Memory{}
|
||||
|
||||
func TestMemoryName(t *testing.T) {
|
||||
mem := NewMemory()
|
||||
if 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}
|
||||
|
||||
mem := NewMemory()
|
||||
if err := mem.Create(rls); err != nil {
|
||||
t.Fatalf("Failed create: %s", err)
|
||||
}
|
||||
|
||||
res, err := mem.Get(key)
|
||||
if err != nil {
|
||||
t.Errorf("Could not get %s: %s", key, err)
|
||||
}
|
||||
if res.Name != key {
|
||||
t.Errorf("Expected %s, got %s", key, res.Name)
|
||||
}
|
||||
if mem := NewMemory(); mem.Name() != MemoryDriverName {
|
||||
t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryCreate(t *testing.T) {
|
||||
key := "test-1"
|
||||
rls := &rspb.Release{Name: key}
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
mem := NewMemory()
|
||||
if err := mem.Create(rls); err != nil {
|
||||
t.Fatalf("Failed created: %s", err)
|
||||
}
|
||||
if mem.cache[key].Name != key {
|
||||
t.Errorf("Unexpected release name: %s", mem.cache[key].Name)
|
||||
}
|
||||
ts := tsFixtureMemory(t)
|
||||
for _, tt := range tests {
|
||||
key := testKey(tt.rls.Name, tt.rls.Version)
|
||||
rls := tt.rls
|
||||
|
||||
if err := ts.Create(key, rls); err != nil {
|
||||
if !tt.err {
|
||||
t.Fatalf("failed to create %q: %s", tt.desc, 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},
|
||||
}
|
||||
|
||||
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 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"},
|
||||
},
|
||||
}
|
||||
|
||||
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}
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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}
|
||||
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},
|
||||
}
|
||||
|
||||
mem := NewMemory()
|
||||
if err := mem.Create(rls); err != nil {
|
||||
t.Fatalf("Failed create: %s", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
package 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}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package 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-existant 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
kberrs "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -17,100 +17,127 @@ limitations under the License.
|
|||
package storage // import "k8s.io/helm/pkg/storage"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/storage/driver"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/storage/driver"
|
||||
)
|
||||
|
||||
// Storage represents a storage engine for a Release.
|
||||
type Storage struct {
|
||||
driver.Driver
|
||||
driver.Driver
|
||||
}
|
||||
|
||||
// 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
|
||||
// storage backend fails to retrieve the releases.
|
||||
func (s *Storage) ListReleases() ([]*rspb.Release, error) {
|
||||
log.Println("Listing all releases in storage")
|
||||
return s.Driver.List(func(_ *rspb.Release) bool { return true })
|
||||
log.Println("Listing all releases in storage")
|
||||
return s.Driver.List(func(_ *rspb.Release) bool { return true })
|
||||
}
|
||||
|
||||
// ListDeleted returns all releases with Status == DELETED. An error is returned
|
||||
// if the storage backend fails to retrieve the releases.
|
||||
func (s *Storage) ListDeleted() ([]*rspb.Release, error) {
|
||||
log.Println("List deleted releases in storage")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return StatusFilter(rspb.Status_DELETED).Check(rls)
|
||||
})
|
||||
log.Println("List deleted releases in storage")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return StatusFilter(rspb.Status_DELETED).Check(rls)
|
||||
})
|
||||
}
|
||||
|
||||
// ListDeployed returns all releases with Status == DEPLOYED. An error is returned
|
||||
// if the storage backend fails to retrieve the releases.
|
||||
func (s *Storage) ListDeployed() ([]*rspb.Release, error) {
|
||||
log.Println("Listing all deployed releases in storage")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return StatusFilter(rspb.Status_DEPLOYED).Check(rls)
|
||||
})
|
||||
log.Println("Listing all deployed releases in storage")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return StatusFilter(rspb.Status_DEPLOYED).Check(rls)
|
||||
})
|
||||
}
|
||||
|
||||
// ListFilterAll returns the set of releases satisfying satisfying the predicate
|
||||
// (filter0 && filter1 && ... && filterN), i.e. a Release is included in the results
|
||||
// if and only if all filters return true.
|
||||
func (s *Storage) ListFilterAll(filters ...FilterFunc) ([]*rspb.Release, error) {
|
||||
log.Println("Listing all releases with filter")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return All(filters...).Check(rls)
|
||||
})
|
||||
log.Println("Listing all releases with filter")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return All(filters...).Check(rls)
|
||||
})
|
||||
}
|
||||
|
||||
// ListFilterAny returns the set of releases satisfying satisfying the predicate
|
||||
// (filter0 || filter1 || ... || filterN), i.e. a Release is included in the results
|
||||
// if at least one of the filters returns true.
|
||||
func (s *Storage) ListFilterAny(filters ...FilterFunc) ([]*rspb.Release, error) {
|
||||
log.Println("Listing any releases with filter")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return Any(filters...).Check(rls)
|
||||
})
|
||||
log.Println("Listing any releases with filter")
|
||||
return s.Driver.List(func(rls *rspb.Release) bool {
|
||||
return Any(filters...).Check(rls)
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// default driver is in memory
|
||||
if d == nil {
|
||||
d = driver.NewMemory()
|
||||
}
|
||||
return &Storage{Driver: d}
|
||||
// default driver is in memory
|
||||
if d == nil {
|
||||
d = driver.NewMemory()
|
||||
}
|
||||
return &Storage{Driver: d}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,143 +17,195 @@ limitations under the License.
|
|||
package storage // import "k8s.io/helm/pkg/storage"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/storage/driver"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/storage/driver"
|
||||
)
|
||||
|
||||
func TestStorageCreate(t *testing.T) {
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
// create fake release
|
||||
rls := ReleaseTestData{
|
||||
Name: "angry-beaver",
|
||||
Version: 1,
|
||||
}.ToRelease()
|
||||
|
||||
// fetch the release
|
||||
res, err := storage.Get(rls.Name)
|
||||
assertErrNil(t.Fatal, err, "QueryRelease")
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// verify the fetched and created release are the same
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
// fetch the release
|
||||
res, err := storage.Get(rls.Name, rls.Version)
|
||||
assertErrNil(t.Fatal, err, "QueryRelease")
|
||||
|
||||
// verify the fetched and created release are the same
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageUpdate(t *testing.T) {
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
// create fake release
|
||||
rls := ReleaseTestData{
|
||||
Name: "angry-beaver",
|
||||
Version: 1,
|
||||
Status: rspb.Status_DEPLOYED,
|
||||
}.ToRelease()
|
||||
|
||||
// modify the release
|
||||
rls.Version = 2
|
||||
rls.Manifest = "new-manifest"
|
||||
assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease")
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// retrieve the updated release
|
||||
res, err := storage.Get(rls.Name)
|
||||
assertErrNil(t.Fatal, err, "QueryRelease")
|
||||
// modify the release
|
||||
rls.Info.Status.Code = rspb.Status_DELETED
|
||||
assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease")
|
||||
|
||||
// verify updated and fetched releases are the same.
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
// retrieve the updated release
|
||||
res, err := storage.Get(rls.Name, rls.Version)
|
||||
assertErrNil(t.Fatal, err, "QueryRelease")
|
||||
|
||||
// verify updated and fetched releases are the same.
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageDelete(t *testing.T) {
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
// create fake release
|
||||
rls := ReleaseTestData{
|
||||
Name: "angry-beaver",
|
||||
Version: 1,
|
||||
}.ToRelease()
|
||||
|
||||
// delete the release
|
||||
res, err := storage.Delete(rls.Name)
|
||||
assertErrNil(t.Fatal, err, "DeleteRelease")
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// verify updated and fetched releases are the same.
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
// delete the release
|
||||
res, err := storage.Delete(rls.Name, rls.Version)
|
||||
assertErrNil(t.Fatal, err, "DeleteRelease")
|
||||
|
||||
// verify updated and fetched releases are the same.
|
||||
if !reflect.DeepEqual(rls, res) {
|
||||
t.Fatalf("Expected %q, got %q", rls, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageList(t *testing.T) {
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
// initialize storage
|
||||
storage := Init(driver.NewMemory())
|
||||
|
||||
// setup storage with test releases
|
||||
setup := func() {
|
||||
// release records
|
||||
rls0 := ReleaseTestData{Name: "happy-catdog", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.Status_DEPLOYED}.ToRelease()
|
||||
rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.Status_DEPLOYED}.ToRelease()
|
||||
rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.Status_DELETED}.ToRelease()
|
||||
rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.Status_DELETED}.ToRelease()
|
||||
// setup storage with test releases
|
||||
setup := func() {
|
||||
// release records
|
||||
rls0 := ReleaseTestData{Name: "happy-catdog", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.Status_SUPERSEDED}.ToRelease()
|
||||
rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.Status_DEPLOYED}.ToRelease()
|
||||
rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.Status_DEPLOYED}.ToRelease()
|
||||
rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.Status_DELETED}.ToRelease()
|
||||
rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.Status_DELETED}.ToRelease()
|
||||
|
||||
// create the release records in the storage
|
||||
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'rls1'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'rls2'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'rls3'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls4), "Storing release 'rls4'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'rls5'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls6), "Storing release 'rls6'")
|
||||
}
|
||||
// create the release records in the storage
|
||||
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'rls1'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'rls2'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'rls3'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls4), "Storing release 'rls4'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'rls5'")
|
||||
assertErrNil(t.Fatal, storage.Create(rls6), "Storing release 'rls6'")
|
||||
}
|
||||
|
||||
var listTests = []struct {
|
||||
Description string
|
||||
NumExpected int
|
||||
ListFunc func() ([]*rspb.Release, error)
|
||||
}{
|
||||
{"ListDeleted", 2, storage.ListDeleted},
|
||||
{"ListDeployed", 2, storage.ListDeployed},
|
||||
{"ListReleases", 7, storage.ListReleases},
|
||||
}
|
||||
var listTests = []struct {
|
||||
Description string
|
||||
NumExpected int
|
||||
ListFunc func() ([]*rspb.Release, error)
|
||||
}{
|
||||
{"ListDeleted", 2, storage.ListDeleted},
|
||||
{"ListDeployed", 2, storage.ListDeployed},
|
||||
{"ListReleases", 7, storage.ListReleases},
|
||||
}
|
||||
|
||||
setup()
|
||||
setup()
|
||||
|
||||
for _, tt := range listTests {
|
||||
list, err := tt.ListFunc()
|
||||
assertErrNil(t.Fatal, err, tt.Description)
|
||||
// verify the count of releases returned
|
||||
if len(list) != tt.NumExpected {
|
||||
t.Errorf("ListReleases(%s): expected %d, actual %d",
|
||||
tt.Description,
|
||||
tt.NumExpected,
|
||||
len(list))
|
||||
}
|
||||
}
|
||||
for _, tt := range listTests {
|
||||
list, err := tt.ListFunc()
|
||||
assertErrNil(t.Fatal, err, tt.Description)
|
||||
// verify the count of releases returned
|
||||
if len(list) != tt.NumExpected {
|
||||
t.Errorf("ListReleases(%s): expected %d, actual %d",
|
||||
tt.Description,
|
||||
tt.NumExpected,
|
||||
len(list))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Manifest string
|
||||
Namespace string
|
||||
Status rspb.Status_Code
|
||||
Name string
|
||||
Version int32
|
||||
Manifest string
|
||||
Namespace string
|
||||
Status rspb.Status_Code
|
||||
}
|
||||
|
||||
func (test ReleaseTestData) ToRelease() *rspb.Release {
|
||||
return &rspb.Release{
|
||||
Name: test.Name,
|
||||
Version: test.Version,
|
||||
Manifest: test.Manifest,
|
||||
Namespace: test.Namespace,
|
||||
Info: &rspb.Info{Status: &rspb.Status{Code: test.Status}},
|
||||
}
|
||||
return &rspb.Release{
|
||||
Name: test.Name,
|
||||
Version: test.Version,
|
||||
Manifest: test.Manifest,
|
||||
Namespace: test.Namespace,
|
||||
Info: &rspb.Info{Status: &rspb.Status{Code: test.Status}},
|
||||
}
|
||||
}
|
||||
|
||||
func assertErrNil(eh func(args ...interface{}), err error, message string) {
|
||||
if err != nil {
|
||||
eh(fmt.Sprintf("%s: %q", message, err))
|
||||
}
|
||||
if err != nil {
|
||||
eh(fmt.Sprintf("%s: %q", message, err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue