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