diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go new file mode 100644 index 000000000..a83954479 --- /dev/null +++ b/pkg/storage/driver/cfgmaps.go @@ -0,0 +1,53 @@ +/* +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 ( + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +type ConfigMaps struct { + *Memory // simple cache +} + +func NewConfigMaps() *ConfigMaps { + return &ConfigMaps{Memory: NewMemory()} +} + +// Get retrieves the releases named by key from the ConfigMap +func (cfg *ConfigMaps) Get(key string) (*rspb.Release, error) { + return nil, ErrNotImplemented +} + +// All returns all releases whose status is not Status_DELETED. +func (cfg *ConfigMaps) All(key string, opts ...interface{}) ([]*rspb.Release, error) { + return nil, ErrNotImplemented +} + +// Create creates a new release or error. +func (cfg *ConfigMaps) Create(rls *rspb.Release) error { + return ErrNotImplemented +} + +// Update updates a release or error. +func (cfg *ConfigMaps) Update(rls *rspb.Release) error { + return ErrNotImplemented +} + +// Delete deletes a release or error. +func (cfg *ConfigMaps) Delete(key string) (*rspb.Release, error) { + return nil, ErrNotImplemented +} diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go new file mode 100644 index 000000000..dd8f39200 --- /dev/null +++ b/pkg/storage/driver/cfgmaps_test.go @@ -0,0 +1,40 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package driver // import "k8s.io/helm/pkg/storage/driver" + +import ( + "testing" +) + +func TestConfigMapsGet(t *testing.T) { + t.Skip("ConfigMapsGet") +} + +func TestConfigMapsAll(t *testing.T) { + t.Skip("ConfigMapsAll") +} + +func TestConfigMapsCreate(t *testing.T) { + t.Skip("ConfigMapsCreate") +} + +func TestConfigMapsUpdate(t *testing.T) { + t.Skip("ConfigMapsUpdate") +} + +func TestConfigMapsDelete(t *testing.T) { + t.Skip("ConfigMapsDelete") +} diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go new file mode 100644 index 000000000..c6670cb41 --- /dev/null +++ b/pkg/storage/driver/driver.go @@ -0,0 +1,53 @@ +/* +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") + // Temporary error while WIP. + ErrNotImplemented = errors.New("not implemented") +) + +type Creator interface { + Create(*rspb.Release) error +} + +type Updator interface { + Update(*rspb.Release) error +} + +type Deletor interface { + Delete(string) (*rspb.Release, error) +} + +type Queryor interface { + Get(string) (*rspb.Release, error) + + All(string, ...interface{}) ([]*rspb.Release, error) +} + +type Driver interface { + Creator + Updator + Deletor + Queryor +} diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go new file mode 100644 index 000000000..e5301f0de --- /dev/null +++ b/pkg/storage/driver/memory.go @@ -0,0 +1,106 @@ +/* +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 an in-memory storage driver implementation. +type Memory struct { + sync.RWMutex + cache map[string]*rspb.Release +} + +func NewMemory() *Memory { + return &Memory{cache: map[string]*rspb.Release{}} +} + +// Get returns the release named by key. +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 +} + +// All returns all releases whose status is not Status_DELETED. +func (mem *Memory) All(key string, opts ...interface{}) ([]*rspb.Release, error) { + defer unlock(mem.rlock()) + + var releases []*rspb.Release + for _, rls := range mem.cache { + if rls.Info.Status.Code != rspb.Status_DELETED { + releases = append(releases, rls) + } + } + return releases, nil +} + +// Create creates a new release or error. +func (mem *Memory) Create(rls *rspb.Release) error { + defer unlock(mem.wlock()) + + mem.cache[rls.Name] = rls + return nil +} + +// Update updates a release or error. +func (mem *Memory) Update(rls *rspb.Release) error { + defer unlock(mem.wlock()) + + if old, ok := mem.cache[rls.Name]; ok { + // FIXME: when release update is complete, old release should + // be marked as superseded, creating the new release. + _ = old + + mem.cache[rls.Name] = rls + return nil + } + return ErrReleaseNotFound +} + +// Delete deletes a release or error. +func (mem *Memory) Delete(key string) (*rspb.Release, error) { + defer unlock(mem.wlock()) + + if old, ok := mem.cache[key]; ok { + old.Info.Status.Code = rspb.Status_DELETED + delete(mem.cache, key) + return old, nil + } + return nil, ErrReleaseNotFound +} + +func (mem *Memory) wlock() func() { + mem.Lock() + return func() { + mem.Unlock() + } +} + +func (mem *Memory) rlock() func() { + mem.RLock() + return func() { + mem.RUnlock() + } +} + +func unlock(fn func()) { fn() } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go new file mode 100644 index 000000000..0edd77ff1 --- /dev/null +++ b/pkg/storage/driver/memory_test.go @@ -0,0 +1,97 @@ +/* +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 ( + rspb "k8s.io/helm/pkg/proto/hapi/release" + "testing" +) + +func TestMemoryGet(t *testing.T) { + key := "test-1" + rls := &rspb.Release{Name: key} + + mem := NewMemory() + mem.Create(rls) + + res, err := mem.Get(key) + switch { + case err != nil: + t.Errorf("Could not get %s: %s", key, err) + case res.Name != key: + t.Errorf("Expected %s, got %s", key, res.Name) + } +} + +func TestMemoryAll(t *testing.T) { + t.Skip("MemoryAll") +} + +func TestMemoryCreate(t *testing.T) { + key := "test-1" + rls := &rspb.Release{Name: key} + + mem := NewMemory() + err := mem.Create(rls) + + switch { + case err != nil: + t.Fatalf("Failed create: %s", err) + case 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, + Info: &rspb.Info{ + Status: &rspb.Status{Code: rspb.Status_DEPLOYED}, + }, + } + + mem := NewMemory() + if err := mem.Create(rls); err != nil { + t.Fatalf("Failed create: %s", err) + } + + res, err := mem.Delete(key) + switch { + case err != nil: + t.Fatalf("Failed delete: %s", err) + case mem.cache[key] != nil: + t.Errorf("Expected nil, got %s", mem.cache[key]) + case res.Info.Status.Code != rspb.Status_DELETED: + t.Errorf("Expected Status_DELETED, got %s", res.Info.Status.Code) + } +} diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go deleted file mode 100644 index 1ab8b3a69..000000000 --- a/pkg/storage/memory.go +++ /dev/null @@ -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 -} diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go deleted file mode 100644 index b83cd1a72..000000000 --- a/pkg/storage/memory_test.go +++ /dev/null @@ -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") -} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go new file mode 100644 index 000000000..6345d33d6 --- /dev/null +++ b/pkg/storage/storage.go @@ -0,0 +1,48 @@ +/* +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 ( + rspb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/storage/driver" +) + +type Storage struct { + drv driver.Driver +} + +func (st *Storage) StoreRelease(rls *rspb.Release) error { + return st.drv.Create(rls) +} + +func (st *Storage) UpdateRelease(rls *rspb.Release) error { + return st.drv.Update(rls) +} + +func (st *Storage) QueryRelease(key string) (*rspb.Release, error) { + return st.drv.Get(key) +} + +func (st *Storage) DeleteRelease(key string) (*rspb.Release, error) { + return st.drv.Delete(key) +} + +func Init(drv driver.Driver) *Storage { + if drv == nil { + drv = driver.NewMemory() + } + return &Storage{drv: drv} +} diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go new file mode 100644 index 000000000..d84e1cd71 --- /dev/null +++ b/pkg/storage/storage_test.go @@ -0,0 +1,141 @@ +/* +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" + "time" + + rspb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/storage/driver" + + tspb "github.com/golang/protobuf/ptypes" +) + +var storage = Init(driver.NewMemory()) + +func releaseData() *rspb.Release { + var manifest = `apiVersion: v1 + kind: ConfigMap + metadata: + name: configmap-storage-test + data: + count: "100" + limit: "200" + state: "new" + token: "abc" + ` + + tm, _ := tspb.TimestampProto(time.Now()) + return &rspb.Release{ + Name: "hungry-hippo", + Info: &rspb.Info{ + FirstDeployed: tm, + LastDeployed: tm, + Status: &rspb.Status{Code: rspb.Status_DEPLOYED}, + }, + Version: 2, + Manifest: manifest, + Namespace: "kube-system", + } +} + +func TestStoreRelease(t *testing.T) { + ckerr := func(err error, msg string) { + if err != nil { + t.Fatalf(fmt.Sprintf("Failed to %s: %q", msg, err)) + } + } + + rls := releaseData() + ckerr(storage.StoreRelease(rls), "StoreRelease") + + res, err := storage.QueryRelease(rls.Name) + ckerr(err, "QueryRelease") + + if !reflect.DeepEqual(rls, res) { + t.Fatalf("Expected %q, got %q", rls, res) + } +} + +func TestQueryRelease(t *testing.T) { + ckerr := func(err error, msg string) { + if err != nil { + t.Fatalf(fmt.Sprintf("Failed to %s: %q", msg, err)) + } + } + + rls := releaseData() + ckerr(storage.StoreRelease(rls), "StoreRelease") + + res, err := storage.QueryRelease(rls.Name) + ckerr(err, "QueryRelease") + + if !reflect.DeepEqual(rls, res) { + t.Fatalf("Expected %q, got %q", rls, res) + } +} + +func TestDeleteRelease(t *testing.T) { + ckerr := func(err error, msg string) { + if err != nil { + t.Fatalf(fmt.Sprintf("Failed to %s: %q", msg, err)) + } + } + + rls := releaseData() + ckerr(storage.StoreRelease(rls), "StoreRelease") + + res, err := storage.DeleteRelease(rls.Name) + ckerr(err, "DeleteRelease") + + if !reflect.DeepEqual(rls, res) { + t.Fatalf("Expected %q, got %q", rls, res) + } +} + +func TestUpdateRelease(t *testing.T) { + ckeql := func(got, want interface{}, msg string) { + if !reflect.DeepEqual(got, want) { + t.Fatalf(fmt.Sprintf("%s: got %T, want %T", msg, got, want)) + } + } + + ckerr := func(err error, msg string) { + if err != nil { + t.Fatalf(fmt.Sprintf("Failed to %s: %q", msg, err)) + } + } + + rls := releaseData() + ckerr(storage.StoreRelease(rls), "StoreRelease") + + rls.Name = "hungry-hippo" + rls.Version = 2 + rls.Manifest = "old-manifest" + + err := storage.UpdateRelease(rls) + ckerr(err, "UpdateRelease") + + res, err := storage.QueryRelease(rls.Name) + ckerr(err, "QueryRelease") + ckeql(res, rls, "Expected Release") + ckeql(res.Name, rls.Name, "Expected Name") + ckeql(res.Version, rls.Version, "Expected Version") + ckeql(res.Manifest, rls.Manifest, "Expected Manifest") +}