diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index fc4a3a879..76d868e49 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -88,7 +88,7 @@ func runTestActionCmd(t *testing.T, tests []cmdTestCase) { } func storageFixture() *storage.Storage { - return storage.Init(driver.NewMemory()) + return storage.Init(driver.NewMemory("default")) } func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) { diff --git a/pkg/action/action.go b/pkg/action/action.go index 28d88c3ed..cb90b7f42 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -236,7 +236,7 @@ func (c *Configuration) Init(envSettings *cli.EnvSettings, allNamespaces bool, h d.Log = log store = storage.Init(d) case "memory": - d := driver.NewMemory() + d := driver.NewMemory(namespace) store = storage.Init(d) default: // Not sure what to do here. diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 49d1bad41..82d36bcf3 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -77,7 +77,7 @@ func actionConfigFixture(t *testing.T) *Configuration { } return &Configuration{ - Releases: storage.Init(driver.NewMemory()), + Releases: storage.Init(driver.NewMemory("default")), KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, Capabilities: chartutil.DefaultCapabilities, RegistryClient: registryClient, diff --git a/pkg/action/install.go b/pkg/action/install.go index e1a55618d..cf0164530 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -179,7 +179,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. i.cfg.Capabilities = chartutil.DefaultCapabilities i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} - i.cfg.Releases = storage.Init(driver.NewMemory()) + i.cfg.Releases = storage.Init(driver.NewMemory(i.Namespace)) } else if !i.ClientOnly && len(i.APIVersions) > 0 { i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") } diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index ca8756c0c..6fc7c8e7f 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -17,6 +17,7 @@ limitations under the License. package driver import ( + "github.com/pkg/errors" "strconv" "strings" "sync" @@ -29,15 +30,53 @@ var _ Driver = (*Memory)(nil) // MemoryDriverName is the string name of this driver. const MemoryDriverName = "Memory" +var ( + // ErrNamespaceNotFound indicates that a namespace is not found. + ErrNamespaceNotFound = errors.New("namespace not found") + // ErrNamespaceAlreadyExists indicates that a namespace already exists. + ErrNamespaceAlreadyExists = errors.New("namespace already exists") +) + // Memory is the in-memory storage driver implementation. type Memory struct { sync.RWMutex - cache map[string]records + allNamespacesCache map[string]map[string]records + namespace string } // NewMemory initializes a new memory driver. -func NewMemory() *Memory { - return &Memory{cache: map[string]records{}} +func NewMemory(namespace string) *Memory { + memory := &Memory{ + allNamespacesCache: map[string]map[string]records{}, + namespace: namespace, + } + if namespace != "" { + _ = memory.CreateNamespace(namespace) + } + + return memory +} + +// SetNamespace sets the current namespace of the memory driver +func (mem *Memory) SetNamespace(namespace string) { + defer unlock(mem.wlock()) + mem.namespace = namespace +} + +// GetNamespace returns the current namespace of the memory driver +func (mem *Memory) GetNamespace() string { + defer unlock(mem.rlock()) + return mem.namespace +} + +// Namespace creates a namespace for the memory driver +func (mem *Memory) CreateNamespace(namespace string) error { + _, exists := mem.allNamespacesCache[namespace] + if exists { + return ErrNamespaceAlreadyExists + } + mem.allNamespacesCache[namespace] = make(map[string]records) + return nil } // Name returns the name of the driver. @@ -56,7 +95,11 @@ func (mem *Memory) Get(key string) (*rspb.Release, error) { if _, err := strconv.Atoi(ver); err != nil { return nil, ErrInvalidKey } - if recs, ok := mem.cache[name]; ok { + cache, exists := mem.allNamespacesCache[mem.namespace] + if !exists { + return nil, ErrNamespaceNotFound + } + if recs, ok := cache[name]; ok { if r := recs.Get(key); r != nil { return r.rls, nil } @@ -72,13 +115,17 @@ func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error defer unlock(mem.rlock()) var ls []*rspb.Release - for _, recs := range mem.cache { - recs.Iter(func(_ int, rec *record) bool { - if filter(rec.rls) { - ls = append(ls, rec.rls) + for namespace, cache := range mem.allNamespacesCache { + if mem.namespace == namespace || mem.namespace == "" { + for _, recs := range cache { + recs.Iter(func(_ int, rec *record) bool { + if filter(rec.rls) { + ls = append(ls, rec.rls) + } + return true + }) } - return true - }) + } } return ls, nil } @@ -93,18 +140,22 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { lbs.fromMap(keyvals) var ls []*rspb.Release - for _, recs := range mem.cache { - recs.Iter(func(_ int, rec *record) bool { - // A query for a release name that doesn't exist (has been deleted) - // can cause rec to be nil. - if rec == nil { - return false - } - if rec.lbs.match(lbs) { - ls = append(ls, rec.rls) + for namespace, cache := range mem.allNamespacesCache { + if mem.namespace == namespace || mem.namespace == "" { + for _, recs := range cache { + recs.Iter(func(_ int, rec *record) bool { + // A query for a release name that doesn't exist (has been deleted) + // can cause rec to be nil. + if rec == nil { + return false + } + if rec.lbs.match(lbs) { + ls = append(ls, rec.rls) + } + return true + }) } - return true - }) + } } return ls, nil } @@ -113,14 +164,18 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { func (mem *Memory) Create(key string, rls *rspb.Release) error { defer unlock(mem.wlock()) - if recs, ok := mem.cache[rls.Name]; ok { + cache, exists := mem.allNamespacesCache[mem.namespace] + if !exists { + return ErrNamespaceNotFound + } + if recs, ok := cache[rls.Name]; ok { if err := recs.Add(newRecord(key, rls)); err != nil { return err } - mem.cache[rls.Name] = recs + cache[rls.Name] = recs return nil } - mem.cache[rls.Name] = records{newRecord(key, rls)} + cache[rls.Name] = records{newRecord(key, rls)} return nil } @@ -128,7 +183,11 @@ func (mem *Memory) Create(key string, rls *rspb.Release) error { func (mem *Memory) Update(key string, rls *rspb.Release) error { defer unlock(mem.wlock()) - if rs, ok := mem.cache[rls.Name]; ok && rs.Exists(key) { + cache, exists := mem.allNamespacesCache[mem.namespace] + if !exists { + return ErrNamespaceNotFound + } + if rs, ok := cache[rls.Name]; ok && rs.Exists(key) { rs.Replace(key, newRecord(key, rls)) return nil } @@ -146,14 +205,19 @@ func (mem *Memory) Delete(key string) (*rspb.Release, error) { return nil, ErrInvalidKey } + cache, exists := mem.allNamespacesCache[mem.namespace] + if !exists { + return nil, ErrNamespaceNotFound + } + name, ver := elems[0], elems[1] if _, err := strconv.Atoi(ver); err != nil { return nil, ErrInvalidKey } - if recs, ok := mem.cache[name]; ok { + if recs, ok := cache[name]; ok { if r := recs.Remove(key); r != nil { // recs.Remove changes the slice reference, so we have to re-assign it. - mem.cache[name] = recs + cache[name] = recs return r.rls, nil } } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index c74df9432..0b808f16e 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -25,31 +25,47 @@ import ( ) func TestMemoryName(t *testing.T) { - if mem := NewMemory(); mem.Name() != MemoryDriverName { + if mem := NewMemory("default"); mem.Name() != MemoryDriverName { t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name()) } } func TestMemoryCreate(t *testing.T) { var tests = []struct { - desc string - rls *rspb.Release - err bool + desc string + namespace string + rls *rspb.Release + err bool }{ { - "create should success", + "create should succeed in default namespace", + "default", releaseStub("rls-c", 1, "default", rspb.StatusDeployed), false, }, { "create should fail (release already exists)", + "default", releaseStub("rls-a", 1, "default", rspb.StatusDeployed), true, }, + { + "create should succeed in testing namespace", + "testing", + releaseStub("rls-c", 1, "default", rspb.StatusDeployed), + false, + }, + { + "create should fail (release already exists) in testing namespace", + "testing", + releaseStub("rls-c", 1, "default", rspb.StatusDeployed), + true, + }, } ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) key := testKey(tt.rls.Name, tt.rls.Version) rls := tt.rls @@ -62,17 +78,21 @@ func TestMemoryCreate(t *testing.T) { } func TestMemoryGet(t *testing.T) { + ts := tsFixtureMemory(t) var tests = []struct { - desc string - key string - err bool + desc string + namespace string + key string + err bool }{ - {"release key should exist", "rls-a.v1", false}, - {"release key should not exist", "rls-a.v5", true}, + {"release key should exist in default namespace", "default", "rls-a.v1", false}, + {"release key should not exist in default namespace", "default", "rls-a.v5", true}, + {"release key should exist in testing namespace", "testing", "rls-a.v1", false}, + {"release key should not exist in testing namespace", "testing", "rls-a.v5", true}, } - ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) if _, err := ts.Get(tt.key); err != nil { if !tt.err { t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) @@ -82,20 +102,36 @@ func TestMemoryGet(t *testing.T) { } func TestMemoryQuery(t *testing.T) { + ts := tsFixtureMemory(t) + var tests = []struct { - desc string - xlen int - lbs map[string]string + desc string + namespace string + xlen int + lbs map[string]string }{ { - "should be 2 query results", + "should be 2 query results for default namespace", + "default", 2, map[string]string{"status": "deployed"}, }, + { + "should be 1 query result for testing namespace", + "testing", + 1, + map[string]string{"status": "deployed"}, + }, + { + "should be 3 query results for all namespaces", + "", + 3, + map[string]string{"status": "deployed"}, + }, } - ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) l, err := ts.Query(tt.lbs) if err != nil { t.Fatalf("Failed to query: %s\n", err) @@ -108,28 +144,47 @@ func TestMemoryQuery(t *testing.T) { } func TestMemoryUpdate(t *testing.T) { + ts := tsFixtureMemory(t) + var tests = []struct { - desc string - key string - rls *rspb.Release - err bool + desc string + namespace string + key string + rls *rspb.Release + err bool }{ { - "update release status", + "update release status which exists in default namespace", + "default", "rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusSuperseded), false, }, + { + "update release status which exists in testing namespace", + "testing", + "rls-a.v1", + releaseStub("rls-a", 1, "default", rspb.StatusSuperseded), + false, + }, { "update release does not exist", + "default", + "rls-z.v1", + releaseStub("rls-z", 1, "default", rspb.StatusUninstalled), + true, + }, + { + "update release does not exist", + "testing", "rls-z.v1", releaseStub("rls-z", 1, "default", rspb.StatusUninstalled), true, }, } - ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) if err := ts.Update(tt.key, tt.rls); err != nil { if !tt.err { t.Fatalf("Failed %q: %s\n", tt.desc, err) @@ -150,21 +205,27 @@ func TestMemoryUpdate(t *testing.T) { func TestMemoryDelete(t *testing.T) { var tests = []struct { - desc string - key string - err bool + desc string + namespace string + key string + err bool }{ - {"release key should exist", "rls-a.v1", false}, - {"release key should not exist", "rls-a.v5", true}, + {"release key should exist in default namespace", "default", "rls-a.v1", false}, + {"release key should not exist in default namespace", "default", "rls-a.v5", true}, + {"release key should exist in testing namespace", "testing", "rls-a.v1", false}, + {"release key should not exist in testing namespace", "testing", "rls-a.v5", true}, } ts := tsFixtureMemory(t) + // all namespaces + ts.namespace = "" start, err := ts.Query(map[string]string{"name": "rls-a"}) if err != nil { t.Errorf("Query failed: %s", err) } startLen := len(start) for _, tt := range tests { + ts.SetNamespace(tt.namespace) if rel, err := ts.Delete(tt.key); err != nil { if !tt.err { t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) @@ -179,6 +240,8 @@ func TestMemoryDelete(t *testing.T) { } } + // all namespaces + ts.namespace = "" // Make sure that the deleted records are gone. end, err := ts.Query(map[string]string{"name": "rls-a"}) if err != nil { diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 4c9b4ef9c..4792cadf1 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -55,13 +55,25 @@ func tsFixtureMemory(t *testing.T) *Memory { releaseStub("rls-b", 2, "default", rspb.StatusSuperseded), } - mem := NewMemory() + mem := NewMemory("default") for _, tt := range hs { err := mem.Create(testKey(tt.Name, tt.Version), tt) if err != nil { t.Fatalf("Test setup failed to create: %s\n", err) } } + + mem.namespace = "testing" + err := mem.CreateNamespace(mem.namespace) + if err != nil { + t.Fatalf("Test setup failed to create: %s\n", err) + } + anotherRls := releaseStub("rls-a", 1, "testing", rspb.StatusDeployed) + err = mem.Create(testKey(anotherRls.Name, anotherRls.Version), anotherRls) + if err != nil { + t.Fatalf("Test setup failed to create: %s\n", err) + } + return mem } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 58a7eb06f..f5279a25a 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -227,7 +227,7 @@ func makeKey(rlsname string, version int) string { func Init(d driver.Driver) *Storage { // default driver is in memory if d == nil { - d = driver.NewMemory() + d = driver.NewMemory("default") } return &Storage{ Driver: d, diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index b8c333d24..025082cd4 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -27,7 +27,7 @@ import ( func TestStorageCreate(t *testing.T) { // initialize storage - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) // create fake release rls := ReleaseTestData{ @@ -49,7 +49,7 @@ func TestStorageCreate(t *testing.T) { func TestStorageUpdate(t *testing.T) { // initialize storage - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) // create fake release rls := ReleaseTestData{ @@ -76,7 +76,7 @@ func TestStorageUpdate(t *testing.T) { func TestStorageDelete(t *testing.T) { // initialize storage - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) // create fake release rls := ReleaseTestData{ @@ -117,7 +117,7 @@ func TestStorageDelete(t *testing.T) { func TestStorageList(t *testing.T) { // initialize storage - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) // setup storage with test releases setup := func() { @@ -166,7 +166,7 @@ func TestStorageList(t *testing.T) { } func TestStorageDeployed(t *testing.T) { - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) const name = "angry-bird" const vers = 4 @@ -206,7 +206,7 @@ func TestStorageDeployed(t *testing.T) { } func TestStorageHistory(t *testing.T) { - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) const name = "angry-bird" @@ -237,7 +237,7 @@ func TestStorageHistory(t *testing.T) { } func TestStorageRemoveLeastRecent(t *testing.T) { - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) storage.Log = t.Logf // Make sure that specifying this at the outset doesn't cause any bugs. @@ -294,7 +294,7 @@ func TestStorageRemoveLeastRecent(t *testing.T) { } func TestStorageLast(t *testing.T) { - storage := Init(driver.NewMemory()) + storage := Init(driver.NewMemory("default")) const name = "angry-bird"