Skip to content

Commit

Permalink
Enforce unique plugin names
Browse files Browse the repository at this point in the history
 - Plugins names must be globally unique
 - Better handling of deprecated config in namespace manager
 - Updated config descriptions

Signed-off-by: Alex Shorsher <alex.shorsher@kaleido.io>
  • Loading branch information
shorsher committed May 26, 2022
1 parent 10f8b9e commit e41e51b
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 66 deletions.
1 change: 1 addition & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ nav_order: 3
|Key|Description|Type|Default Value|
|---|-----------|----|-------------|
|description|A description for the namespace|`string`|`<nil>`
|mode|The namespace mode. Valid values: gateway, multiparty|`string`|`<nil>`
|name|The name of the namespace (must be unique)|`string`|`<nil>`
|plugins|The list of plugins for this namespace|`string`|`<nil>`
|remoteName|The namespace name to be sent in plugin calls, if it differs from namespace name|`string`|`<nil>`
Expand Down
10 changes: 5 additions & 5 deletions internal/coreconfig/coreconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ const (
PluginConfigName = "name"
// PluginConfigType is the type of the plugin to be loaded
PluginConfigType = "type"
// NamespaceMode is a the configured mode of a namespace
// NamespaceMode is the configured mode of a namespace
NamespaceMode = "mode"
// NamespaceRemoteName is a the configured mode of a namespace
// NamespaceRemoteName is the namespace name to be sent in plugin calls
NamespaceRemoteName = "remoteName"
// NamespacePlugins is a the list of namespace plugins
// NamespacePlugins is the list of namespace plugins
NamespacePlugins = "plugins"
// NamespaceName is a short name for a pre-defined namespace
// NamespaceName is the short name for a pre-defined namespace
NamespaceName = "name"
// NamespaceName is a long description for a pre-defined namespace
// NamespaceName is the long description for a pre-defined namespace
NamespaceDescription = "description"
)

Expand Down
2 changes: 1 addition & 1 deletion internal/coremsgs/en_config_descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ var (
ConfigNamespacesPredefined = ffc("config.namespaces.predefined", "A list of namespaces to ensure exists, without requiring a broadcast from the network", "List "+i18n.StringType)
ConfigNamespacesPredefinedName = ffc("config.namespaces.predefined[].name", "The name of the namespace (must be unique)", i18n.StringType)
ConfigNamespacesPredefinedPlugins = ffc("config.namespaces.predefined[].plugins", "The list of plugins for this namespace", i18n.StringType)
ConfigNamespacesPredefinedMode = ffc("config.namespaces.predefined[].mode", "The namespace mode", i18n.StringType)
ConfigNamespacesPredefinedMode = ffc("config.namespaces.predefined[].mode", "The namespace mode. Valid values: gateway, multiparty", i18n.StringType)
ConfigNamespacesPredefinedRemoteName = ffc("config.namespaces.predefined[].remoteName", "The namespace name to be sent in plugin calls, if it differs from namespace name", i18n.StringType)
ConfigNamespacesPredefinedDescription = ffc("config.namespaces.predefined[].description", "A description for the namespace", i18n.StringType)

Expand Down
3 changes: 2 additions & 1 deletion internal/coremsgs/en_error_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,12 @@ var (
MsgInvalidOutputOption = ffe("FF10385", "invalid output option '%s'")
MsgInvalidPluginConfiguration = ffe("FF10386", "Invalid %s plugin configuration - name and type are required")
MsgReferenceMarkdownMissing = ffe("FF10387", "Reference markdown file missing: '%s'")
MsgFFSystemReservedName = ffe("FF10388", "Invalid namespace configuration - ff_system is a reserved name")
MsgFFSystemReservedName = ffe("FF10388", "Invalid namespace configuration - %s is a reserved name")
MsgInvalidNamespaceMode = ffe("FF10389", "Invalid %s namespace configuration - unknown mode")
MsgNamespaceUnknownPlugin = ffe("FF10390", "Invalid %s namespace configuration - unknown plugin %s")
MsgNamespaceMultipartyConfiguration = ffe("FF10391", "Invalid %s multiparty namespace configuration - database, blockchain, shared storage, and data exchange plugins are required")
MsgNamespaceGatewayNoDB = ffe("FF10392", "Invalid %s gateway namespace configuration - a database plugin is required")
MsgNamespaceGatewayInvalidPlugins = ffe("FF10393", "Invalid %s gateway namespace configuration - cannot specify dataexchange or shared storage plugins")
MsgNamespaceGatewayMultiplePluginType = ffe("FF10394", "Invalid %s namespace configuration - multiple %s plugins provided")
MsgDuplicatePluginName = ffe("FF10395", "Invalid plugin configuration - plugin with name %s already exists")
)
3 changes: 1 addition & 2 deletions internal/namespace/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ func InitConfig(withDefaults bool) {
namespacePredefined.AddKnownKey(coreconfig.NamespaceDescription)
namespacePredefined.AddKnownKey(coreconfig.NamespaceRemoteName)
namespacePredefined.AddKnownKey(coreconfig.NamespacePlugins)
// do we need to set the default mode?
namespacePredefined.AddKnownKey(coreconfig.NamespaceMode, "multiparty")
if withDefaults {
namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceName, "default")
namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceDescription, "Default predefined namespace")
namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceMode, "multiparty")
}
}
28 changes: 18 additions & 10 deletions internal/namespace/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package namespace
import (
"context"
"fmt"
"strings"

"github.com/hyperledger/firefly-common/pkg/config"
"github.com/hyperledger/firefly-common/pkg/fftypes"
Expand Down Expand Up @@ -145,25 +144,35 @@ func (nm *namespaceManager) validateNamespaceConfig(ctx context.Context, name st
return err
}

if strings.ToLower(name) == "ff_system" || strings.ToLower(conf.GetString(coreconfig.NamespaceRemoteName)) == "ff_system" {
return i18n.NewError(ctx, coremsgs.MsgFFSystemReservedName)
if name == core.SystemNamespace || conf.GetString(coreconfig.NamespaceRemoteName) == core.SystemNamespace {
return i18n.NewError(ctx, coremsgs.MsgFFSystemReservedName, core.SystemNamespace)
}

mode := conf.GetString(coreconfig.NamespaceMode)
plugins := conf.GetStringSlice(coreconfig.NamespacePlugins)

// If the no plugins are found when querying the config, assume older config file
// If no plugins are found when querying the config, assume older config file
if len(plugins) == 0 {
// Still check to ensure at least one bc, dx, ss, and db plugin exist
if len(nm.bcPlugins) == 0 || len(nm.dxPlugins) == 0 || len(nm.ssPlugins) == 0 || len(nm.dbPlugins) == 0 {
return i18n.NewError(ctx, coremsgs.MsgNamespaceMultipartyConfiguration, name)
for plugin := range nm.bcPlugins {
plugins = append(plugins, plugin)
}

for plugin := range nm.dxPlugins {
plugins = append(plugins, plugin)
}

for plugin := range nm.ssPlugins {
plugins = append(plugins, plugin)
}

for plugin := range nm.dbPlugins {
plugins = append(plugins, plugin)
}
return nil
}

switch mode {
// Multiparty is the default mode when none is provided
case "multiparty", "":
case "multiparty":
if err := nm.validateMultiPartyConfig(ctx, name, plugins); err != nil {
return err
}
Expand Down Expand Up @@ -220,7 +229,6 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s
}

if !dbPlugin || !ssPlugin || !dxPlugin || !bcPlugin {
fmt.Printf("plugins: %+v\n", nm)
return i18n.NewError(ctx, coremsgs.MsgNamespaceMultipartyConfiguration, name)
}

Expand Down
86 changes: 43 additions & 43 deletions internal/namespace/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package namespace
import (
"context"
"fmt"
"strings"
"testing"

"github.com/hyperledger/firefly-common/pkg/config"
Expand All @@ -35,6 +36,7 @@ import (
"github.com/hyperledger/firefly/pkg/dataexchange"
"github.com/hyperledger/firefly/pkg/sharedstorage"
"github.com/hyperledger/firefly/pkg/tokens"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
Expand Down Expand Up @@ -236,23 +238,23 @@ func TestInitNamespacesMultipartyMultipleDB(t *testing.T) {
assert.Regexp(t, "FF10394", err)
}

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

utTestConfig := config.RootSection("default")
utTestConfig.AddKnownKey(coreconfig.NamespaceName, "default")
utTestConfig.AddKnownKey(coreconfig.NamespaceRemoteName, "default")
utTestConfig.AddKnownKey(coreconfig.NamespaceMode)
utTestConfig.AddKnownKey(coreconfig.NamespaceMode, "multiparty")
utTestConfig.AddKnownKey(coreconfig.NamespaceDescription)
utTestConfig.AddKnownKey(coreconfig.NamespacePlugins, []string{})
nm.nsConfig = map[string]config.Section{"default": utTestConfig}

nm.mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(nil, nil)
nm.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(nil)
// nm.mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(nil, nil)
// nm.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(nil)

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

func TestInitNamespacesGatewayMultipleDB(t *testing.T) {
Expand Down Expand Up @@ -315,26 +317,6 @@ func TestInitNamespacesGatewayWithDX(t *testing.T) {
assert.Regexp(t, "FF10393", err)
}

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

utTestConfig := config.RootSection("default")
utTestConfig.AddKnownKey(coreconfig.NamespaceName, "default")
utTestConfig.AddKnownKey(coreconfig.NamespaceRemoteName, "default")
utTestConfig.AddKnownKey(coreconfig.NamespaceMode)
utTestConfig.AddKnownKey(coreconfig.NamespaceDescription)
utTestConfig.AddKnownKey(coreconfig.NamespacePlugins, []string{})
nm.nsConfig = map[string]config.Section{"default": utTestConfig}
nm.bcPlugins = nil
nm.dbPlugins = nil
nm.ssPlugins = nil
nm.dxPlugins = nil

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

func TestInitNamespacesGatewayWithSharedStorage(t *testing.T) {
nm := newTestNamespaceManager(true)
defer nm.cleanup(t)
Expand Down Expand Up @@ -427,24 +409,42 @@ func TestInitNamespacesDefaultMissing(t *testing.T) {

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

namespaceConfig.AddKnownKey("predefined.0.name", "ns1")
namespaceConfig.AddKnownKey("predefined.0.remoteName", "ns1")
namespaceConfig.AddKnownKey("predefined.0.mode", "gateway")
namespaceConfig.AddKnownKey("predefined.0.plugins", []string{"ethereum", "postgres", "erc721"})

namespaceConfig.AddKnownKey("predefined.1.name", "ns2")
namespaceConfig.AddKnownKey("predefined.1.remoteName", "ns2")
namespaceConfig.AddKnownKey("predefined.1.mode", "gateway")
namespaceConfig.AddKnownKey("predefined.1.plugins", []string{"ethereum", "postgres", "erc721"})

namespaceConfig.AddKnownKey("predefined.2.name", "ns2")
namespaceConfig.AddKnownKey("predefined.2.remoteName", "ns2")
namespaceConfig.AddKnownKey("predefined.2.mode", "gateway")
namespaceConfig.AddKnownKey("predefined.2.plugins", []string{"ethereum", "postgres", "erc721"})

config.Set(coreconfig.NamespacesDefault, "ns1")
InitConfig(false)

viper.SetConfigType("yaml")
err := viper.ReadConfig(strings.NewReader(`
namespaces:
default: ns1
predefined:
- name: ns1
remoteName: ns1
mode: gateway
org:
key: 0x123456
plugins:
- sqlite3
- ethereum
- erc721
- name: ns2
remoteName: ns2
mode: gateway
org:
key: 0x223456
plugins:
- sqlite3
- ethereum
- erc721
- name: ns2
remoteName: ns2
mode: gateway
org:
key: 0x223456
plugins:
- sqlite3
- ethereum
- erc721
`))
assert.NoError(t, err)

nm := newTestNamespaceManager(false)
defer nm.cleanup(t)
Expand Down
12 changes: 10 additions & 2 deletions internal/orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ type orchestrator struct {
sharedDownload shareddownload.Manager
txHelper txcommon.Helper
namespace namespace.Manager
// Used to detect duplicate plugin names
pluginNames map[string]bool
}

func NewOrchestrator(withDefaults bool) Orchestrator {
Expand Down Expand Up @@ -369,16 +371,21 @@ func (or *orchestrator) initDatabasePlugins(ctx context.Context, plugins []datab

func (or *orchestrator) validatePluginConfig(ctx context.Context, config config.Section, sectionName string) error {
name := config.GetString(coreconfig.PluginConfigName)
dxType := config.GetString(coreconfig.PluginConfigType)
pluginType := config.GetString(coreconfig.PluginConfigType)

if name == "" || dxType == "" {
if name == "" || pluginType == "" {
return i18n.NewError(ctx, coremsgs.MsgInvalidPluginConfiguration, sectionName)
}

if err := core.ValidateFFNameField(ctx, name, "name"); err != nil {
return err
}

if _, ok := or.pluginNames[name]; ok {
return i18n.NewError(ctx, coremsgs.MsgDuplicatePluginName, name)
}
or.pluginNames[name] = true

return nil
}

Expand Down Expand Up @@ -444,6 +451,7 @@ func (or *orchestrator) initDataExchange(ctx context.Context) (err error) {
}

func (or *orchestrator) initPlugins(ctx context.Context) (err error) {
or.pluginNames = make(map[string]bool)
if or.metrics == nil {
or.metrics = metrics.NewMetricsManager(ctx)
}
Expand Down
17 changes: 15 additions & 2 deletions internal/orchestrator/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ func newTestOrchestrator() *testOrchestrator {
ctx, cancel := context.WithCancel(context.Background())
tor := &testOrchestrator{
orchestrator: orchestrator{
ctx: ctx,
cancelCtx: cancel,
ctx: ctx,
cancelCtx: cancel,
pluginNames: make(map[string]bool),
},
mdi: &databasemocks.Plugin{},
mdm: &datamocks.Manager{},
Expand Down Expand Up @@ -747,6 +748,18 @@ func TestBadTokensPlugin(t *testing.T) {
assert.Error(t, err)
}

func TestDuplicatePluginNames(t *testing.T) {
or := newTestOrchestrator()
or.pluginNames["duplicate"] = true
tifactory.InitConfig(tokensConfig)
utConfig := config.RootSection("test")
utConfig.AddKnownKey(coreconfig.PluginConfigName, "duplicate")
utConfig.AddKnownKey(coreconfig.PluginConfigType, "fftokens")
ctx := context.Background()
err := or.validatePluginConfig(ctx, utConfig, "test")
assert.Regexp(t, "FF10395", err)
}

func TestGoodTokensPlugin(t *testing.T) {
or := newTestOrchestrator()
defer or.cleanup(t)
Expand Down

0 comments on commit e41e51b

Please sign in to comment.