mirror of https://github.com/helm/helm
Merge pull request #1007 from fibonacci1729/feat/storage-memory
feat(storage): in-memory & configmaps driverpull/1040/head
commit
1b15275135
@ -0,0 +1,250 @@
|
||||
/*
|
||||
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 (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of
|
||||
// the kubernetes ConfigMapsInterface.
|
||||
func NewConfigMaps(impl client.ConfigMapsInterface) *ConfigMaps {
|
||||
return &ConfigMaps{impl: impl}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
// to store a release. Each configmap data entry is the base64
|
||||
// encoded string of a release's binary protobuf encoding.
|
||||
//
|
||||
// The following labels are used within each configmap:
|
||||
//
|
||||
// "MODIFIED_AT" - timestamp indicating when this configmap was last modified. (set in Update)
|
||||
// "CREATED_AT" - timestamp indicating when this configmap was created. (set in Create)
|
||||
// "VERSION" - version of the release.
|
||||
// "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants)
|
||||
// "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"
|
||||
|
||||
// encode the release
|
||||
s, err := encodeRelease(rls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
// create and return configmap object
|
||||
return &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: rls.Name,
|
||||
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
|
||||
}
|
||||
|
||||
// decodeRelease decodes the bytes in data into a release
|
||||
// type. Data must contain a base64 encoded string of a
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
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 (
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestConfigMapGet(t *testing.T) {
|
||||
key := "key-1"
|
||||
rel := newTestRelease(key, 1, rspb.Status_DEPLOYED)
|
||||
|
||||
cfgmaps := newTestFixture(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)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}...)
|
||||
|
||||
// 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 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)
|
||||
|
||||
key := "key-1"
|
||||
rel := newTestRelease(key, 1, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMapUpdate(t *testing.T) {
|
||||
key := "key-1"
|
||||
rel := newTestRelease(key, 1, rspb.Status_DEPLOYED)
|
||||
|
||||
cfgmaps := newTestFixture(t, []*rspb.Release{rel}...)
|
||||
|
||||
// modify release status code & version
|
||||
rel = newTestRelease(key, 2, rspb.Status_SUPERSEDED)
|
||||
|
||||
// perform the update
|
||||
if err := cfgmaps.Update(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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
// Creator is the interface that wraps the Create method.
|
||||
//
|
||||
// Create stores the release or returns ErrReleaseExists
|
||||
// if an identical release already exists.
|
||||
type Creator interface {
|
||||
Create(rls *rspb.Release) error
|
||||
}
|
||||
|
||||
// Updator is the interface that wraps the Update method.
|
||||
//
|
||||
// Update updates an existing release or returns
|
||||
// ErrReleaseNotFound if the release does not exist.
|
||||
type Updator interface {
|
||||
Update(rls *rspb.Release) error
|
||||
}
|
||||
|
||||
// Deletor is the interface that wraps the Delete method.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Queryor is the interface that wraps the Get and List methods.
|
||||
//
|
||||
// Get returns the release named by key or returns ErrReleaseNotFound
|
||||
// if the release does not exist.
|
||||
//
|
||||
// List returns the set of all releases that satisfy the filter predicate.
|
||||
type Queryor interface {
|
||||
Get(key string) (*rspb.Release, error)
|
||||
List(filter func(*rspb.Release) bool) ([]*rspb.Release, error)
|
||||
}
|
||||
|
||||
// Driver is the interface composed of Creator, Updator, Deletor, Queryor
|
||||
// interfaces. It defines the behavior for storing, updating, deleted,
|
||||
// and retrieving tiller releases from some underlying storage mechanism,
|
||||
// e.g. memory, configmaps.
|
||||
type Driver interface {
|
||||
Creator
|
||||
Updator
|
||||
Deletor
|
||||
Queryor
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 (
|
||||
"sync"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
// Memory is the in-memory storage driver implementation.
|
||||
type Memory struct {
|
||||
sync.RWMutex
|
||||
cache map[string]*rspb.Release
|
||||
}
|
||||
|
||||
// NewMemory initializes a new memory driver.
|
||||
func NewMemory() *Memory {
|
||||
return &Memory{cache: map[string]*rspb.Release{}}
|
||||
}
|
||||
|
||||
// Get returns the release named by key or returns ErrReleaseNotFound.
|
||||
func (mem *Memory) Get(key string) (*rspb.Release, error) {
|
||||
defer unlock(mem.rlock())
|
||||
|
||||
if rls, ok := mem.cache[key]; ok {
|
||||
return rls, nil
|
||||
}
|
||||
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])
|
||||
}
|
||||
}
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
// Create creates a new release or returns ErrReleaseExists.
|
||||
func (mem *Memory) Create(rls *rspb.Release) error {
|
||||
defer unlock(mem.wlock())
|
||||
|
||||
if _, ok := mem.cache[rls.Name]; ok {
|
||||
return ErrReleaseExists
|
||||
}
|
||||
mem.cache[rls.Name] = rls
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates a release or returns ErrReleaseNotFound.
|
||||
func (mem *Memory) Update(rls *rspb.Release) error {
|
||||
defer unlock(mem.wlock())
|
||||
|
||||
if _, ok := mem.cache[rls.Name]; ok {
|
||||
mem.cache[rls.Name] = 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())
|
||||
|
||||
if old, ok := mem.cache[key]; ok {
|
||||
delete(mem.cache, key)
|
||||
return old, nil
|
||||
}
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
// wlock locks mem for writing
|
||||
func (mem *Memory) wlock() func() {
|
||||
mem.Lock()
|
||||
return func() {
|
||||
mem.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// rlock locks mem for reading
|
||||
func (mem *Memory) rlock() func() {
|
||||
mem.RLock()
|
||||
return func() {
|
||||
mem.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g:
|
||||
// ```defer unlock(mem.rlock())```, locks mem for reading at the
|
||||
// call point of defer and unlocks upon exiting the block.
|
||||
func unlock(fn func()) { fn() }
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if mem.cache[key].Name != key {
|
||||
t.Errorf("Unexpected release name: %s", mem.cache[key].Name)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if mem.cache[key].Name != key {
|
||||
t.Errorf("Unexpected release name: %s", mem.cache[key].Name)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 storage
|
||||
|
||||
import rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
|
||||
// FilterFunc returns true if the release object satisfies
|
||||
// the predicate of the underlying func.
|
||||
type FilterFunc func(*rspb.Release) bool
|
||||
|
||||
// Check applies the FilterFunc to the release object.
|
||||
func (fn FilterFunc) Check(rls *rspb.Release) bool {
|
||||
if rls == nil {
|
||||
return false
|
||||
}
|
||||
return fn(rls)
|
||||
}
|
||||
|
||||
// Any returns a FilterFunc that filters a list of releases
|
||||
// determined by the predicate 'f0 || f1 || ... || fn'.
|
||||
func Any(filters ...FilterFunc) FilterFunc {
|
||||
return func(rls *rspb.Release) bool {
|
||||
for _, filter := range filters {
|
||||
if filter(rls) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All returns a FilterFunc that filters a list of releases
|
||||
// determined by the predicate 'f0 && f1 && ... && fn'.
|
||||
func All(filters ...FilterFunc) FilterFunc {
|
||||
return func(rls *rspb.Release) bool {
|
||||
for _, filter := range filters {
|
||||
if !filter(rls) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// StatusFilter filters a set of releases by status code.
|
||||
func StatusFilter(status rspb.Status_Code) FilterFunc {
|
||||
return FilterFunc(func(rls *rspb.Release) bool {
|
||||
if rls == nil {
|
||||
return true
|
||||
}
|
||||
return rls.GetInfo().GetStatus().Code == status
|
||||
})
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
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 storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
// Memory is an in-memory ReleaseStorage implementation.
|
||||
type Memory struct {
|
||||
sync.RWMutex
|
||||
releases map[string]*release.Release
|
||||
}
|
||||
|
||||
// NewMemory creates a new in-memory storage.
|
||||
func NewMemory() *Memory {
|
||||
return &Memory{
|
||||
releases: map[string]*release.Release{},
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNotFound indicates that a release is not found.
|
||||
var ErrNotFound = errors.New("release not found")
|
||||
|
||||
// Read returns the named Release.
|
||||
//
|
||||
// If the release is not found, an ErrNotFound error is returned.
|
||||
func (m *Memory) Read(k string) (*release.Release, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
v, ok := m.releases[k]
|
||||
if !ok {
|
||||
return v, ErrNotFound
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Create sets a release.
|
||||
func (m *Memory) Create(rel *release.Release) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.releases[rel.Name] = rel
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update sets a release.
|
||||
func (m *Memory) Update(rel *release.Release) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
if _, ok := m.releases[rel.Name]; !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// FIXME: When Release is done, we need to do this right by marking the old
|
||||
// release as superseded, and creating a new release.
|
||||
m.releases[rel.Name] = rel
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a release.
|
||||
func (m *Memory) Delete(name string) (*release.Release, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
rel, ok := m.releases[name]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
delete(m.releases, name)
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// List returns all releases whose status is not Status_DELETED.
|
||||
func (m *Memory) List() ([]*release.Release, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
buf := []*release.Release{}
|
||||
for _, v := range m.releases {
|
||||
if v.Info.Status.Code != release.Status_DELETED {
|
||||
buf = append(buf, v)
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Query searches all releases for matches.
|
||||
func (m *Memory) Query(labels map[string]string) ([]*release.Release, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return []*release.Release{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// History returns the history of this release, in the form of a series of releases.
|
||||
func (m *Memory) History(name string) ([]*release.Release, error) {
|
||||
// TODO: This _should_ return all of the releases with the given name, sorted
|
||||
// by LastDeployed, regardless of status.
|
||||
r, err := m.Read(name)
|
||||
if err != nil {
|
||||
return []*release.Release{}, err
|
||||
}
|
||||
return []*release.Release{r}, nil
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
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 storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
k := "test-1"
|
||||
r := &release.Release{Name: k}
|
||||
|
||||
ms := NewMemory()
|
||||
if err := ms.Create(r); err != nil {
|
||||
t.Fatalf("Failed create: %s", err)
|
||||
}
|
||||
|
||||
if ms.releases[k].Name != k {
|
||||
t.Errorf("Unexpected release name: %s", ms.releases[k].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
k := "test-1"
|
||||
r := &release.Release{Name: k}
|
||||
|
||||
ms := NewMemory()
|
||||
ms.Create(r)
|
||||
|
||||
if out, err := ms.Read(k); err != nil {
|
||||
t.Errorf("Could not get %s: %s", k, err)
|
||||
} else if out.Name != k {
|
||||
t.Errorf("Expected %s, got %s", k, out.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistory(t *testing.T) {
|
||||
k := "test-1"
|
||||
r := &release.Release{Name: k}
|
||||
|
||||
ms := NewMemory()
|
||||
ms.Create(r)
|
||||
|
||||
if out, err := ms.History(k); err != nil {
|
||||
t.Errorf("Could not get %s: %s", k, err)
|
||||
} else if len(out) != 1 {
|
||||
t.Fatalf("Expected 1 release, got %d", len(out))
|
||||
} else if out[0].Name != k {
|
||||
t.Errorf("Expected %s, got %s", k, out[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
k := "test-1"
|
||||
r := &release.Release{Name: k}
|
||||
|
||||
ms := NewMemory()
|
||||
if err := ms.Create(r); err != nil {
|
||||
t.Fatalf("Failed create: %s", err)
|
||||
}
|
||||
if err := ms.Update(r); err != nil {
|
||||
t.Fatalf("Failed update: %s", err)
|
||||
}
|
||||
|
||||
if ms.releases[k].Name != k {
|
||||
t.Errorf("Unexpected release name: %s", ms.releases[k].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
ms := NewMemory()
|
||||
rels := []string{"a", "b", "c"}
|
||||
|
||||
for _, k := range rels {
|
||||
ms.Create(&release.Release{
|
||||
Name: k,
|
||||
Info: &release.Info{
|
||||
Status: &release.Status{Code: release.Status_UNKNOWN},
|
||||
},
|
||||
})
|
||||
ms.Create(&release.Release{
|
||||
Name: "deleted-should-not-show-up",
|
||||
Info: &release.Info{
|
||||
Status: &release.Status{Code: release.Status_DELETED},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
l, err := ms.List()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(l) != 3 {
|
||||
t.Errorf("Expected 3, got %d", len(l))
|
||||
}
|
||||
|
||||
for _, n := range rels {
|
||||
foundN := false
|
||||
for _, rr := range l {
|
||||
if rr.Name == n {
|
||||
foundN = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundN {
|
||||
t.Errorf("Did not find %s in list.", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
t.Skip("Not Implemented")
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
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 storage // import "k8s.io/helm/pkg/storage"
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 })
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 storage // import "k8s.io/helm/pkg/storage"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
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())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// fetch the release
|
||||
res, err := storage.Get(rls.Name)
|
||||
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())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// modify the release
|
||||
rls.Version = 2
|
||||
rls.Manifest = "new-manifest"
|
||||
assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease")
|
||||
|
||||
// retrieve the updated release
|
||||
res, err := storage.Get(rls.Name)
|
||||
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())
|
||||
|
||||
// create fake release
|
||||
rls := ReleaseTestData{Name: "angry-beaver"}.ToRelease()
|
||||
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
|
||||
|
||||
// delete the release
|
||||
res, err := storage.Delete(rls.Name)
|
||||
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())
|
||||
|
||||
// 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'")
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ReleaseTestData struct {
|
||||
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}},
|
||||
}
|
||||
}
|
||||
|
||||
func assertErrNil(eh func(args ...interface{}), err error, message string) {
|
||||
if err != nil {
|
||||
eh(fmt.Sprintf("%s: %q", message, err))
|
||||
}
|
||||
}
|
Loading…
Reference in new issue