Skip to content

Commit

Permalink
Factor out NamespaceManager
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
  • Loading branch information
awrichar committed May 20, 2022
1 parent 707b4f2 commit 0b14252
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 324 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ $(eval $(call makemock, internal/shareddownload, Manager, shareddow
$(eval $(call makemock, internal/shareddownload, Callbacks, shareddownloadmocks))
$(eval $(call makemock, internal/definitions, DefinitionHandler, definitionsmocks))
$(eval $(call makemock, internal/events, EventManager, eventmocks))
$(eval $(call makemock, internal/namespace, Manager, namespacemocks))
$(eval $(call makemock, internal/networkmap, Manager, networkmapmocks))
$(eval $(call makemock, internal/assets, Manager, assetmocks))
$(eval $(call makemock, internal/contracts, Manager, contractmocks))
Expand Down
4 changes: 4 additions & 0 deletions internal/coreconfig/coreconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const (
PluginConfigName = "name"
// PluginConfigType is the type of the plugin to be loaded
PluginConfigType = "type"
// NamespaceName is a short name for a pre-defined namespace
NamespaceName = "name"
// NamespaceName is a long description for a pre-defined namespace
NamespaceDescription = "description"
)

// The following keys can be access from the root configuration.
Expand Down
41 changes: 41 additions & 0 deletions internal/namespace/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright © 2022 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 namespace

import (
"github.com/hyperledger/firefly-common/pkg/config"
"github.com/hyperledger/firefly/internal/coreconfig"
)

const (
// NamespacePredefined is the list of pre-defined namespaces
NamespacePredefined = "predefined"
)

var (
namespaceConfig = config.RootSection("namespaces")
namespacePredefined = namespaceConfig.SubArray(NamespacePredefined)
)

func InitConfig(withDefaults bool) {
namespacePredefined.AddKnownKey(coreconfig.NamespaceName)
namespacePredefined.AddKnownKey(coreconfig.NamespaceDescription)
if withDefaults {
namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceName, "default")
namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceDescription, "Default predefined namespace")
}
}
126 changes: 126 additions & 0 deletions internal/namespace/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright © 2022 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 namespace

import (
"context"
"fmt"

"github.com/hyperledger/firefly-common/pkg/config"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly-common/pkg/i18n"
"github.com/hyperledger/firefly-common/pkg/log"
"github.com/hyperledger/firefly/internal/coreconfig"
"github.com/hyperledger/firefly/internal/coremsgs"
"github.com/hyperledger/firefly/pkg/core"
"github.com/hyperledger/firefly/pkg/database"
)

type Manager interface {
// Init initializes the manager
Init(ctx context.Context, di database.Plugin) error
}

type namespaceManager struct {
ctx context.Context
nsConfig map[string]config.Section
}

func NewNamespaceManager(ctx context.Context) Manager {
nm := &namespaceManager{
ctx: ctx,
nsConfig: buildNamespaceMap(ctx),
}
return nm
}

func buildNamespaceMap(ctx context.Context) map[string]config.Section {
conf := namespacePredefined
namespaces := make(map[string]config.Section, conf.ArraySize())
for i := 0; i < conf.ArraySize(); i++ {
nsConfig := conf.ArrayEntry(i)
name := nsConfig.GetString(coreconfig.NamespaceName)
if name != "" {
if _, ok := namespaces[name]; ok {
log.L(ctx).Warnf("Duplicate predefined namespace (ignored): %s", name)
}
namespaces[name] = nsConfig
}
}
return namespaces
}

func (nm *namespaceManager) Init(ctx context.Context, di database.Plugin) error {
return nm.initNamespaces(ctx, di)
}

func (nm *namespaceManager) getPredefinedNamespaces(ctx context.Context) ([]*core.Namespace, error) {
defaultNS := config.GetString(coreconfig.NamespacesDefault)
namespaces := []*core.Namespace{
{
Name: core.SystemNamespace,
Type: core.NamespaceTypeSystem,
Description: i18n.Expand(ctx, coremsgs.CoreSystemNSDescription),
},
}
i := 0
foundDefault := false
for name, nsObject := range nm.nsConfig {
if err := core.ValidateFFNameField(ctx, name, fmt.Sprintf("namespaces.predefined[%d].name", i)); err != nil {
return nil, err
}
i++
foundDefault = foundDefault || name == defaultNS
namespaces = append(namespaces, &core.Namespace{
Type: core.NamespaceTypeLocal,
Name: name,
Description: nsObject.GetString("description"),
})
}
if !foundDefault {
return nil, i18n.NewError(ctx, coremsgs.MsgDefaultNamespaceNotFound, defaultNS)
}
return namespaces, nil
}

func (nm *namespaceManager) initNamespaces(ctx context.Context, di database.Plugin) error {
predefined, err := nm.getPredefinedNamespaces(ctx)
if err != nil {
return err
}
for _, newNS := range predefined {
ns, err := di.GetNamespace(ctx, newNS.Name)
if err != nil {
return err
}
var updated bool
if ns == nil {
updated = true
newNS.ID = fftypes.NewUUID()
newNS.Created = fftypes.Now()
} else {
// Only update if the description has changed, and the one in our DB is locally defined
updated = ns.Description != newNS.Description && ns.Type == core.NamespaceTypeLocal
}
if updated {
if err := di.UpsertNamespace(ctx, newNS, true); err != nil {
return err
}
}
}
return nil
}
143 changes: 143 additions & 0 deletions internal/namespace/manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright © 2022 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 namespace

import (
"context"
"fmt"
"testing"

"github.com/hyperledger/firefly-common/pkg/config"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/internal/coreconfig"
"github.com/hyperledger/firefly/mocks/databasemocks"
"github.com/hyperledger/firefly/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

type testNamespaceManager struct {
namespaceManager
mdi *databasemocks.Plugin
}

func (nm *testNamespaceManager) cleanup(t *testing.T) {
nm.mdi.AssertExpectations(t)
}

func newTestNamespaceManager(resetConfig bool) *testNamespaceManager {
if resetConfig {
coreconfig.Reset()
InitConfig(true)
}
nm := &testNamespaceManager{
mdi: &databasemocks.Plugin{},
namespaceManager: namespaceManager{
nsConfig: buildNamespaceMap(context.Background()),
},
}
return nm
}

func TestNewNamespaceManager(t *testing.T) {
nm := NewNamespaceManager(context.Background())
assert.NotNil(t, nm)
}

func TestInit(t *testing.T) {
coreconfig.Reset()
nm := newTestNamespaceManager(false)
defer nm.cleanup(t)

nm.Init(context.Background(), nm.mdi)
}

func TestInitNamespacesBadName(t *testing.T) {
coreconfig.Reset()
namespaceConfig.AddKnownKey("predefined.0."+coreconfig.NamespaceName, "!Badness")

nm := newTestNamespaceManager(false)
defer nm.cleanup(t)

err := nm.initNamespaces(context.Background(), nm.mdi)
assert.Regexp(t, "FF00140", err)
}

func TestInitNamespacesGetFail(t *testing.T) {
nm := newTestNamespaceManager(true)
defer nm.cleanup(t)

nm.mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
err := nm.initNamespaces(context.Background(), nm.mdi)
assert.Regexp(t, "pop", err)
}

func TestInitNamespacesUpsertFail(t *testing.T) {
nm := newTestNamespaceManager(true)
defer nm.cleanup(t)

nm.mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(nil, nil)
nm.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(fmt.Errorf("pop"))
err := nm.initNamespaces(context.Background(), nm.mdi)
assert.Regexp(t, "pop", err)
}

func TestInitNamespacesUpsertNotNeeded(t *testing.T) {
nm := newTestNamespaceManager(true)
defer nm.cleanup(t)

nm.mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(&core.Namespace{
Type: core.NamespaceTypeBroadcast, // any broadcasted NS will not be updated
}, nil)
err := nm.initNamespaces(context.Background(), nm.mdi)
assert.NoError(t, err)
}

func TestInitNamespacesDefaultMissing(t *testing.T) {
coreconfig.Reset()
config.Set(coreconfig.NamespacesPredefined, fftypes.JSONObjectArray{})

nm := newTestNamespaceManager(false)
defer nm.cleanup(t)

err := nm.initNamespaces(context.Background(), nm.mdi)
assert.Regexp(t, "FF10166", err)
}

func TestInitNamespacesDupName(t *testing.T) {
coreconfig.Reset()
InitConfig(true)

namespaceConfig.AddKnownKey("predefined.0.name", "ns1")
namespaceConfig.AddKnownKey("predefined.1.name", "ns2")
namespaceConfig.AddKnownKey("predefined.2.name", "ns2")
config.Set(coreconfig.NamespacesDefault, "ns1")

nm := newTestNamespaceManager(false)
defer nm.cleanup(t)

nsList, err := nm.getPredefinedNamespaces(context.Background())
assert.NoError(t, err)
assert.Len(t, nsList, 3)
names := make([]string, len(nsList))
for i, ns := range nsList {
names[i] = ns.Name
}
assert.Contains(t, names, core.SystemNamespace)
assert.Contains(t, names, "ns1")
assert.Contains(t, names, "ns2")
}
Loading

0 comments on commit 0b14252

Please sign in to comment.