Commit 0ad4803a authored by Matt Butcher's avatar Matt Butcher

fix(storage): Use a CRUD interface

Fixes #23
parent 2d9563b4
...@@ -58,15 +58,46 @@ type Engine interface { ...@@ -58,15 +58,46 @@ type Engine interface {
// //
// Release storage must be concurrency safe. // Release storage must be concurrency safe.
type ReleaseStorage interface { type ReleaseStorage interface {
// Get takes a name and returns the accompanying release.
Get(key string) (*hapi.Release, error) // Create stores a release in the storage.
// Set saves the release with the given name. //
Set(key string, val *hapi.Release) error // If a release with the same name exists, this returns an error.
//
// It may return other errors in cases where it cannot write to storage.
Create(*hapi.Release) error
// Read takes a name and returns a release that has that name.
//
// It will only return releases that are not deleted and not superseded.
//
// It will return an error if no relevant release can be found, or if storage
// is not properly functioning.
Read(name string) (*hapi.Release, error)
// Update looks for a release with the same name and updates it with the
// present release contents.
//
// For immutable storage backends, this may result in a new release record
// being created, and the previous release being marked as superseded.
//
// It will return an error if a previous release is not found. It may also
// return an error if the storage backend encounters an error.
Update(*hapi.Release) error
// Delete marks a Release as deleted.
//
// It returns the deleted record. If the record is not found or if the
// underlying storage encounters an error, this will return an error.
Delete(name string) (*hapi.Release, error)
// List lists all active (non-deleted, non-superseded) releases. // List lists all active (non-deleted, non-superseded) releases.
// //
// To get deleted or superseded releases, use Query. // To get deleted or superseded releases, use Query.
List() ([]*hapi.Release, error) List() ([]*hapi.Release, error)
// Query takes a map of labels and returns any releases that match. // Query takes a map of labels and returns any releases that match.
//
// Query will search all releases, including deleted and superseded ones.
// The provided map will be used to filter results.
Query(map[string]string) ([]*hapi.Release, error) Query(map[string]string) ([]*hapi.Release, error)
} }
......
...@@ -18,15 +18,24 @@ type mockReleaseStorage struct { ...@@ -18,15 +18,24 @@ type mockReleaseStorage struct {
rel *hapi.Release rel *hapi.Release
} }
func (r *mockReleaseStorage) Get(k string) (*hapi.Release, error) { func (r *mockReleaseStorage) Create(v *hapi.Release) error {
r.rel = v
return nil
}
func (r *mockReleaseStorage) Read(k string) (*hapi.Release, error) {
return r.rel, nil return r.rel, nil
} }
func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { func (r *mockReleaseStorage) Update(v *hapi.Release) error {
r.rel = v r.rel = v
return nil return nil
} }
func (r *mockReleaseStorage) Delete(k string) (*hapi.Release, error) {
return r.rel, nil
}
func (r *mockReleaseStorage) List() ([]*hapi.Release, error) { func (r *mockReleaseStorage) List() ([]*hapi.Release, error) {
return []*hapi.Release{}, nil return []*hapi.Release{}, nil
} }
...@@ -68,15 +77,23 @@ func TestReleaseStorage(t *testing.T) { ...@@ -68,15 +77,23 @@ func TestReleaseStorage(t *testing.T) {
release := &hapi.Release{Name: "mariner"} release := &hapi.Release{Name: "mariner"}
if err := env.Releases.Set("albatross", release); err != nil { if err := env.Releases.Create(release); err != nil {
t.Fatalf("failed to store release: %s", err) t.Fatalf("failed to store release: %s", err)
} }
if v, err := env.Releases.Get("albatross"); err != nil { if err := env.Releases.Update(release); err != nil {
t.Fatalf("failed to update release: %s", err)
}
if v, err := env.Releases.Read("albatross"); err != nil {
t.Errorf("Error fetching release: %s", err) t.Errorf("Error fetching release: %s", err)
} else if v.Name != "mariner" { } else if v.Name != "mariner" {
t.Errorf("Expected mariner, got %q", v.Name) t.Errorf("Expected mariner, got %q", v.Name)
} }
if _, err := env.Releases.Delete("albatross"); err != nil {
t.Fatalf("failed to delete release: %s", err)
}
} }
func TestKubeClient(t *testing.T) { func TestKubeClient(t *testing.T) {
......
...@@ -19,10 +19,10 @@ func NewMemory() *Memory { ...@@ -19,10 +19,10 @@ func NewMemory() *Memory {
var ErrNotFound = errors.New("release not found") var ErrNotFound = errors.New("release not found")
// Get returns the named Release. // Read returns the named Release.
// //
// If the release is not found, an ErrNotFound error is returned. // If the release is not found, an ErrNotFound error is returned.
func (m *Memory) Get(k string) (*hapi.Release, error) { func (m *Memory) Read(k string) (*hapi.Release, error) {
v, ok := m.releases[k] v, ok := m.releases[k]
if !ok { if !ok {
return v, ErrNotFound return v, ErrNotFound
...@@ -30,14 +30,35 @@ func (m *Memory) Get(k string) (*hapi.Release, error) { ...@@ -30,14 +30,35 @@ func (m *Memory) Get(k string) (*hapi.Release, error) {
return v, nil return v, nil
} }
// Set sets a release. // Create sets a release.
// func (m *Memory) Create(rel *hapi.Release) error {
// TODO: Is there any reason why Set doesn't just use the release name? m.releases[rel.Name] = rel
func (m *Memory) Set(k string, rel *hapi.Release) error {
m.releases[k] = rel
return nil return nil
} }
var ErrNoRelease = errors.New("no release found")
// Update sets a release.
func (m *Memory) Update(rel *hapi.Release) error {
if _, ok := m.releases[rel.Name]; !ok {
return ErrNoRelease
}
// 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
}
func (m *Memory) Delete(name string) (*hapi.Release, error) {
rel, ok := m.releases[name]
if !ok {
return nil, ErrNoRelease
}
delete(m.releases, name)
return rel, nil
}
// List returns all releases // List returns all releases
func (m *Memory) List() ([]*hapi.Release, error) { func (m *Memory) List() ([]*hapi.Release, error) {
buf := make([]*hapi.Release, len(m.releases)) buf := make([]*hapi.Release, len(m.releases))
......
...@@ -6,13 +6,13 @@ import ( ...@@ -6,13 +6,13 @@ import (
"github.com/deis/tiller/pkg/hapi" "github.com/deis/tiller/pkg/hapi"
) )
func TestSet(t *testing.T) { func TestCreate(t *testing.T) {
k := "test-1" k := "test-1"
r := &hapi.Release{Name: k} r := &hapi.Release{Name: k}
ms := NewMemory() ms := NewMemory()
if err := ms.Set(k, r); err != nil { if err := ms.Create(r); err != nil {
t.Fatalf("Failed set: %s", err) t.Fatalf("Failed create: %s", err)
} }
if ms.releases[k].Name != k { if ms.releases[k].Name != k {
...@@ -20,26 +20,43 @@ func TestSet(t *testing.T) { ...@@ -20,26 +20,43 @@ func TestSet(t *testing.T) {
} }
} }
func TestGet(t *testing.T) { func TestRead(t *testing.T) {
k := "test-1" k := "test-1"
r := &hapi.Release{Name: k} r := &hapi.Release{Name: k}
ms := NewMemory() ms := NewMemory()
ms.Set(k, r) ms.Create(r)
if out, err := ms.Get(k); err != nil { if out, err := ms.Read(k); err != nil {
t.Errorf("Could not get %s: %s", k, err) t.Errorf("Could not get %s: %s", k, err)
} else if out.Name != k { } else if out.Name != k {
t.Errorf("Expected %s, got %s", k, out.Name) t.Errorf("Expected %s, got %s", k, out.Name)
} }
} }
func TestUpdate(t *testing.T) {
k := "test-1"
r := &hapi.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) { func TestList(t *testing.T) {
ms := NewMemory() ms := NewMemory()
rels := []string{"a", "b", "c"} rels := []string{"a", "b", "c"}
for _, k := range rels { for _, k := range rels {
ms.Set(k, &hapi.Release{Name: k}) ms.Create(&hapi.Release{Name: k})
} }
l, err := ms.List() l, err := ms.List()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment