diff --git a/cmd/firefly.go b/cmd/firefly.go index ffe03f74d3..8fcbbe8d16 100644 --- a/cmd/firefly.go +++ b/cmd/firefly.go @@ -83,6 +83,7 @@ func init() { func resetConfig() { coreconfig.Reset() + namespace.InitConfig() apiserver.InitConfig() } @@ -90,7 +91,7 @@ func getRootManager() namespace.Manager { if _utManager != nil { return _utManager } - return namespace.NewNamespaceManager(true) + return namespace.NewNamespaceManager() } // Execute is called by the main method of the package @@ -142,6 +143,9 @@ func run() error { mgr.WaitStop() return nil case <-resetChan: + // This API that performs a full stop/restart reset, is deprecated + // in favor of selective reload of namespaces based on listening to changes + // in the configuration file. log.L(rootCtx).Infof("Restarting due to configuration change") cancelRunCtx() mgr.WaitStop() @@ -149,7 +153,7 @@ func run() error { <-ffDone // Re-read the configuration resetConfig() - if err := config.ReadConfig(configSuffix, cfgFile); err != nil { + if err = config.ReadConfig(configSuffix, cfgFile); err != nil { return err } case err := <-errChan: @@ -186,7 +190,7 @@ func startFirefly(ctx context.Context, cancelCtx context.CancelFunc, mgr namespa close(ffDone) }() - if err = mgr.Init(ctx, cancelCtx, resetChan); err != nil { + if err = mgr.Init(ctx, cancelCtx, resetChan, resetConfig); err != nil { errChan <- err return } diff --git a/cmd/firefly_test.go b/cmd/firefly_test.go index 43ad086b66..e0aa3e9531 100644 --- a/cmd/firefly_test.go +++ b/cmd/firefly_test.go @@ -56,7 +56,7 @@ func TestShowConfig(t *testing.T) { func TestExecEngineInitFail(t *testing.T) { o := &namespacemocks.Manager{} - o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("splutter")) + o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("splutter")) _utManager = o defer func() { _utManager = nil }() os.Chdir(configDir) @@ -66,7 +66,7 @@ func TestExecEngineInitFail(t *testing.T) { func TestExecEngineStartFail(t *testing.T) { o := &namespacemocks.Manager{} - o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) o.On("Start").Return(fmt.Errorf("bang")) _utManager = o defer func() { _utManager = nil }() @@ -77,7 +77,7 @@ func TestExecEngineStartFail(t *testing.T) { func TestExecOkExitSIGINT(t *testing.T) { o := &namespacemocks.Manager{} - o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) o.On("Start").Return(nil) o.On("WaitStop").Return() _utManager = o @@ -93,7 +93,7 @@ func TestExecOkExitSIGINT(t *testing.T) { func TestExecOkCancel(t *testing.T) { o := &namespacemocks.Manager{} - init := o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + init := o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) init.RunFn = func(a mock.Arguments) { cancelCtx := a[1].(context.CancelFunc) cancelCtx() @@ -111,7 +111,7 @@ func TestExecOkCancel(t *testing.T) { func TestExecOkRestartThenExit(t *testing.T) { o := &namespacemocks.Manager{} initCount := 0 - init := o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + init := o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) init.RunFn = func(a mock.Arguments) { resetChan := a[2].(chan bool) initCount++ @@ -137,7 +137,7 @@ func TestExecOkRestartConfigProblem(t *testing.T) { assert.NoError(t, err) defer os.RemoveAll(tmpDir) var orContext context.Context - init := o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + init := o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) init.RunFn = func(a mock.Arguments) { orContext = a[0].(context.Context) resetChan := a[2].(chan bool) @@ -158,7 +158,7 @@ func TestExecOkRestartConfigProblem(t *testing.T) { func TestAPIServerError(t *testing.T) { o := &namespacemocks.Manager{} - o.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + o.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) o.On("Start").Return(nil) as := &apiservermocks.Server{} as.On("Serve", mock.Anything, o).Return(fmt.Errorf("pop")) diff --git a/docs/config_docs_generate_test.go b/docs/config_docs_generate_test.go index f414a91799..c9855b995f 100644 --- a/docs/config_docs_generate_test.go +++ b/docs/config_docs_generate_test.go @@ -33,7 +33,7 @@ import ( func TestGenerateConfigDocs(t *testing.T) { // Initialize config of all plugins - namespace.NewNamespaceManager(false) + namespace.InitConfig() apiserver.InitConfig() f, err := os.Create(filepath.Join("reference", "config.md")) assert.NoError(t, err) diff --git a/docs/config_docs_test.go b/docs/config_docs_test.go index 398328033b..c63edcbea6 100644 --- a/docs/config_docs_test.go +++ b/docs/config_docs_test.go @@ -34,7 +34,7 @@ import ( func TestConfigDocsUpToDate(t *testing.T) { // Initialize config of all plugins - namespace.NewNamespaceManager(false) + namespace.InitConfig() apiserver.InitConfig() generatedConfig, err := config.GenerateConfigMarkdown(context.Background(), configDocHeader, config.GetKnownKeys()) assert.NoError(t, err) diff --git a/docs/reference/config.md b/docs/reference/config.md index c28874a3b5..52669181eb 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -360,6 +360,12 @@ nav_order: 2 |size|Max size of cached validators for data manager|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`` |ttl|Time to live of cached validators for data manager|`string`|`` +## config + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|autoReload|Monitor the configuration file for changes, and automatically add/remove/reload namespaces and plugins|`boolean`|`` + ## cors |Key|Description|Type|Default Value| diff --git a/internal/apiserver/route_spi_post_reset.go b/internal/apiserver/route_spi_post_reset.go index 6931cc0432..cba784bf32 100644 --- a/internal/apiserver/route_spi_post_reset.go +++ b/internal/apiserver/route_spi_post_reset.go @@ -36,8 +36,7 @@ var spiPostReset = &ffapi.Route{ JSONOutputCodes: []int{http.StatusNoContent}, Extensions: &coreExtensions{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { - cr.mgr.Reset(cr.ctx) - return nil, nil + return nil, cr.mgr.Reset(cr.ctx) }, }, } diff --git a/internal/apiserver/route_spi_post_reset_test.go b/internal/apiserver/route_spi_post_reset_test.go index 781a54e477..696654894a 100644 --- a/internal/apiserver/route_spi_post_reset_test.go +++ b/internal/apiserver/route_spi_post_reset_test.go @@ -32,7 +32,7 @@ func TestAdminPostResetConfig(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mgr.On("Reset", mock.Anything).Return() + mgr.On("Reset", mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 204, res.Result().StatusCode) diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go index 38cc391f6e..d63bf89b32 100644 --- a/internal/blockchain/ethereum/ethereum.go +++ b/internal/blockchain/ethereum/ethereum.go @@ -155,13 +155,13 @@ func (e *Ethereum) Init(ctx context.Context, cancelCtx context.CancelFunc, conf } if ethconnectConf.GetString(ffresty.HTTPConfigURL) == "" { - return i18n.NewError(ctx, coremsgs.MsgMissingPluginConfig, "url", "blockchain.ethereum.ethconnect") + return i18n.NewError(ctx, coremsgs.MsgMissingPluginConfig, "url", ethconnectConf) } e.client = ffresty.New(e.ctx, ethconnectConf) e.topic = ethconnectConf.GetString(EthconnectConfigTopic) if e.topic == "" { - return i18n.NewError(ctx, coremsgs.MsgMissingPluginConfig, "topic", "blockchain.ethereum.ethconnect") + return i18n.NewError(ctx, coremsgs.MsgMissingPluginConfig, "topic", ethconnectConf) } e.prefixShort = ethconnectConf.GetString(EthconnectPrefixShort) e.prefixLong = ethconnectConf.GetString(EthconnectPrefixLong) diff --git a/internal/coreconfig/coreconfig.go b/internal/coreconfig/coreconfig.go index b3ba1c9fa9..c6c3dcd560 100644 --- a/internal/coreconfig/coreconfig.go +++ b/internal/coreconfig/coreconfig.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -116,6 +116,9 @@ var ( // BroadcastBatchTimeout is the timeout to wait for a batch to fill, before sending BroadcastBatchTimeout = ffc("broadcast.batch.timeout") + // ConfigAutoReload starts a filesystem listener against the config file, and if it changes analyzes the config file for changes that require individual namespaces to restart + ConfigAutoReload = ffc("config.autoReload") + // CacheEnabled determines whether cache will be enabled or not, default to true CacheEnabled = ffc("cache.enabled") diff --git a/internal/coremsgs/en_config_descriptions.go b/internal/coremsgs/en_config_descriptions.go index 393e911d67..80876e288c 100644 --- a/internal/coremsgs/en_config_descriptions.go +++ b/internal/coremsgs/en_config_descriptions.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -31,6 +31,8 @@ var ( ConfigGlobalMigrationsDirectory = ffc("config.global.migrations.directory", "The directory containing the numerically ordered migration DDL files to apply to the database", i18n.StringType) ConfigGlobalShutdownTimeout = ffc("config.global.shutdownTimeout", "The maximum amount of time to wait for any open HTTP requests to finish before shutting down the HTTP server", i18n.TimeDurationType) + ConfigConfigAutoReload = ffc("config.config.autoReload", "Monitor the configuration file for changes, and automatically add/remove/reload namespaces and plugins", i18n.BooleanType) + ConfigLegacyAdmin = ffc("config.admin.enabled", "Deprecated - use spi.enabled instead", i18n.BooleanType) ConfigSPIAddress = ffc("config.spi.address", "The IP address on which the admin HTTP API should listen", "IP Address "+i18n.StringType) ConfigSPIEnabled = ffc("config.spi.enabled", "Enables the admin HTTP API", i18n.BooleanType) diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go index 18993da9f3..a704e98cb7 100644 --- a/internal/coremsgs/en_error_messages.go +++ b/internal/coremsgs/en_error_messages.go @@ -276,4 +276,6 @@ var ( MsgUnknownInterfaceFormat = ffe("FF10435", "Unknown interface format: %s", 400) MsgUnknownNamespace = ffe("FF10436", "Unknown namespace '%s'", 404) MsgMissingNamespace = ffe("FF10437", "Missing namespace in request", 400) + MsgDeprecatedResetWithAutoReload = ffe("FF10438", "The deprecated reset API cannot be used when dynamic config reload is enabled", 409) + MsgConfigArrayVsRawConfigMismatch = ffe("FF10439", "Error processing configuration - mismatch between raw and processed array lengths") ) diff --git a/internal/namespace/config.go b/internal/namespace/config.go index 98e5b9e778..fc4380cdc5 100644 --- a/internal/namespace/config.go +++ b/internal/namespace/config.go @@ -17,8 +17,16 @@ package namespace import ( + "github.com/hyperledger/firefly-common/pkg/auth/authfactory" "github.com/hyperledger/firefly-common/pkg/config" + "github.com/hyperledger/firefly/internal/blockchain/bifactory" "github.com/hyperledger/firefly/internal/coreconfig" + "github.com/hyperledger/firefly/internal/database/difactory" + "github.com/hyperledger/firefly/internal/dataexchange/dxfactory" + "github.com/hyperledger/firefly/internal/events/eifactory" + "github.com/hyperledger/firefly/internal/identity/iifactory" + "github.com/hyperledger/firefly/internal/sharedstorage/ssfactory" + "github.com/hyperledger/firefly/internal/tokens/tifactory" "github.com/hyperledger/firefly/pkg/core" ) @@ -29,11 +37,27 @@ const ( ) var ( - namespaceConfig = config.RootSection("namespaces") - namespacePredefined = namespaceConfig.SubArray(NamespacePredefined) + namespaceConfigSection = config.RootSection("namespaces") + namespacePredefined = namespaceConfigSection.SubArray(NamespacePredefined) + + blockchainConfig = config.RootArray("plugins.blockchain") + tokensConfig = config.RootArray("plugins.tokens") + databaseConfig = config.RootArray("plugins.database") + sharedstorageConfig = config.RootArray("plugins.sharedstorage") + dataexchangeConfig = config.RootArray("plugins.dataexchange") + identityConfig = config.RootArray("plugins.identity") + authConfig = config.RootArray("plugins.auth") + eventsConfig = config.RootSection("events") // still at root + + // Deprecated configs + deprecatedTokensConfig = config.RootArray("tokens") + deprecatedBlockchainConfig = config.RootSection("blockchain") + deprecatedDatabaseConfig = config.RootSection("database") + deprecatedSharedStorageConfig = config.RootSection("sharedstorage") + deprecatedDataexchangeConfig = config.RootSection("dataexchange") ) -func InitConfig(withDefaults bool) { +func InitConfig() { namespacePredefined.AddKnownKey(coreconfig.NamespaceName) namespacePredefined.AddKnownKey(coreconfig.NamespaceDescription) namespacePredefined.AddKnownKey(coreconfig.NamespacePlugins) @@ -53,9 +77,17 @@ func InitConfig(withDefaults bool) { contractConf.AddKnownKey(coreconfig.NamespaceMultipartyContractFirstEvent, string(core.SubOptsFirstEventOldest)) contractConf.AddKnownKey(coreconfig.NamespaceMultipartyContractLocation) - if withDefaults { - namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceName, "default") - namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceDescription, "Default predefined namespace") - namespaceConfig.AddKnownKey(NamespacePredefined+".0."+coreconfig.NamespaceAssetKeyNormalization, "blockchain_plugin") - } + bifactory.InitConfigDeprecated(deprecatedBlockchainConfig) + bifactory.InitConfig(blockchainConfig) + difactory.InitConfigDeprecated(deprecatedDatabaseConfig) + difactory.InitConfig(databaseConfig) + ssfactory.InitConfigDeprecated(deprecatedSharedStorageConfig) + ssfactory.InitConfig(sharedstorageConfig) + dxfactory.InitConfig(dataexchangeConfig) + dxfactory.InitConfigDeprecated(deprecatedDataexchangeConfig) + iifactory.InitConfig(identityConfig) + tifactory.InitConfigDeprecated(deprecatedTokensConfig) + tifactory.InitConfig(tokensConfig) + authfactory.InitConfigArray(authConfig) + eifactory.InitConfig(eventsConfig) } diff --git a/internal/namespace/configreload.go b/internal/namespace/configreload.go new file mode 100644 index 0000000000..f397f99359 --- /dev/null +++ b/internal/namespace/configreload.go @@ -0,0 +1,208 @@ +// 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" + "encoding/json" + "fmt" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly/internal/coreconfig" + "github.com/spf13/viper" +) + +func (nm *namespaceManager) dumpRootConfig() (jsonTree fftypes.JSONObject) { + viperTree := viper.AllSettings() + b, _ := json.Marshal(viperTree) + _ = json.Unmarshal(b, &jsonTree) + return +} + +func (nm *namespaceManager) startConfigListener() error { + if config.GetBool(coreconfig.ConfigAutoReload) { + return config.WatchConfig(nm.ctx, nm.configFileChanged, nil) + } + return nil +} + +func (nm *namespaceManager) configFileChanged() { + log.L(nm.ctx).Infof("Detected configuration file reload") + + // Because of the things we do to make defaults work with arrays, we have to reset + // the config when it changes and re-read it. + // We are passed this by our parent, as the config initialization of defaults and sections + // might include others than under the namespaces tree (API Server etc. etc.) + nm.resetConfig() + err := viper.ReadInConfig() + if err != nil { + log.L(nm.ctx).Errorf("Failed to re-read configuration after config reload notification: %s", err) + nm.cancelCtx() // stop the world + return + } + + nm.configReloaded(nm.ctx) +} + +func (nm *namespaceManager) configReloaded(ctx context.Context) { + + // Get Viper to dump the whole new config, with everything resolved across env vars + // and the config file etc. + // We use this to detect if anything has changed. + rawConfig := nm.dumpRootConfig() + + // Build the new set of plugins from the config (including those that are unchanged) + allPluginsInNewConf, err := nm.loadPlugins(ctx, rawConfig) + if err != nil { + log.L(ctx).Errorf("Failed to initialize plugins after config reload: %s", err) + return + } + + // Analyze the new list to see which plugins need to be updated, + // so we load the namespaces against the correct list of plugins + availablePlugins, updatedPlugins, pluginsToStop := nm.analyzePluginChanges(ctx, allPluginsInNewConf) + + // Build the new set of namespaces (including those that are unchanged) + allNewNamespaces, err := nm.loadNamespaces(ctx, rawConfig, availablePlugins) + if err != nil { + log.L(ctx).Errorf("Failed to load namespaces after config reload: %s", err) + return + } + + // From this point we need to block any API calls resolving namespaces, + // until the reload is complete + nm.nsMux.Lock() + defer nm.nsMux.Unlock() + + // Stop all defunct namespaces + availableNS, updatedNamespaces := nm.stopDefunctNamespaces(ctx, availablePlugins, allNewNamespaces) + + // Stop all defunct plugins - now the namespaces using them are all stopped + nm.stopDefunctPlugins(ctx, pluginsToStop) + + // Update the new lists + nm.plugins = availablePlugins + nm.namespaces = availableNS + + // Only initialize updated plugins + if err = nm.initPlugins(updatedPlugins); err != nil { + log.L(ctx).Errorf("Failed to initialize plugins after config reload: %s", err) + nm.cancelCtx() // stop the world + return + } + + // Only initialize the updated namespaces (which includes all that depend on above plugins) + if err = nm.initNamespaces(ctx, updatedNamespaces); err != nil { + log.L(ctx).Errorf("Failed to initialize namespaces after config reload: %s", err) + nm.cancelCtx() // stop the world + return + } + + // Now finally we can start all the new things + if err = nm.startNamespacesAndPlugins(updatedNamespaces, updatedPlugins); err != nil { + log.L(ctx).Errorf("Failed to initialize namespaces after config reload: %s", err) + nm.cancelCtx() // stop the world + return + } + +} + +func (nm *namespaceManager) stopDefunctNamespaces(ctx context.Context, newPlugins map[string]*plugin, newNamespaces map[string]*namespace) (availableNamespaces, updatedNamespaces map[string]*namespace) { + + // build a set of all the namespaces we've either added new, or have changed + updatedNamespaces = make(map[string]*namespace) + availableNamespaces = make(map[string]*namespace) + namespacesToStop := make(map[string]*namespace) + for nsName, newNS := range newNamespaces { + if existingNS := nm.namespaces[nsName]; existingNS != nil { + var changes []string + if !existingNS.configHash.Equals(newNS.configHash) { + changes = append(changes, "namespace_config") // Encompasses the list of plugins + } + for _, pluginName := range newNS.pluginNames { + existingPlugin := nm.plugins[pluginName] + newPlugin := newPlugins[pluginName] + if existingPlugin == nil || newPlugin == nil || + !existingPlugin.configHash.Equals(newPlugin.configHash) { + changes = append(changes, fmt.Sprintf("plugin:%s", pluginName)) + } + } + if len(changes) == 0 { + log.L(ctx).Debugf("Namespace '%s' unchanged after config reload", nsName) + availableNamespaces[nsName] = existingNS + continue + } + // We need to stop the existing namespace + log.L(ctx).Infof("Namespace '%s' configuration changed: %v", nsName, changes) + namespacesToStop[nsName] = existingNS + } + // This is either changed, or brand new - mark it in the map + availableNamespaces[nsName] = newNS + updatedNamespaces[nsName] = newNS + } + + // Stop everything we need to stop + for nsName, existingNS := range nm.namespaces { + if namespacesToStop[nsName] != nil || newNamespaces[nsName] == nil { + log.L(ctx).Debugf("Stopping namespace '%s' after config reload. Loaded at %s", nsName, existingNS.loadTime) + nm.stopNamespace(ctx, existingNS) + } + } + + return availableNamespaces, updatedNamespaces + +} + +func (nm *namespaceManager) analyzePluginChanges(ctx context.Context, newPlugins map[string]*plugin) (availablePlugins, updatedPlugins, pluginsToStop map[string]*plugin) { + + // build a set of all the plugins we've either added new, or have changed + availablePlugins = make(map[string]*plugin) + updatedPlugins = make(map[string]*plugin) + pluginsToStop = make(map[string]*plugin) + for pluginName, newPlugin := range newPlugins { + if existingPlugin := nm.plugins[pluginName]; existingPlugin != nil { + if existingPlugin.configHash.Equals(newPlugin.configHash) { + log.L(ctx).Debugf("Plugin '%s' unchanged after config reload", pluginName) + availablePlugins[pluginName] = existingPlugin + continue + } + // We need to stop the existing plugin + pluginsToStop[pluginName] = existingPlugin + } + // This is either changed, or brand new - mark it in the map + updatedPlugins[pluginName] = newPlugin + availablePlugins[pluginName] = newPlugin + } + + // Look for everything that's deleted + for pluginName, existingPlugin := range nm.plugins { + if newPlugins[pluginName] == nil { + pluginsToStop[pluginName] = existingPlugin + } + } + + return +} + +func (nm *namespaceManager) stopDefunctPlugins(ctx context.Context, pluginsToStop map[string]*plugin) { + for pluginName, plugin := range pluginsToStop { + log.L(ctx).Debugf("Stopping plugin '%s' after config reload. Loaded at %s", pluginName, plugin.loadTime) + plugin.cancelCtx() + } +} diff --git a/internal/namespace/configreload_test.go b/internal/namespace/configreload_test.go new file mode 100644 index 0000000000..e63217297d --- /dev/null +++ b/internal/namespace/configreload_test.go @@ -0,0 +1,989 @@ +// 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" + "io/ioutil" + "strings" + "testing" + "time" + + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/internal/coreconfig" + "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/pkg/database" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var exampleConfig1base = ` +--- +http: + address: 0.0.0.0 + port: 5000 + publicURL: https://myfirefly.example.com +log: + level: debug +metrics: + enabled: true + address: 127.0.0.1 + port: 6000 + path: /metrics +namespaces: + default: ns1 + predefined: + - defaultKey: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + name: ns1 + plugins: + - sharedstorage-ns1 + - database0 + - blockchain-ns1 + - ff-dx + - erc1155-ns1 + - erc20_erc721-ns1 + - test_user_auth-ns1 + multiparty: + enabled: true + node: + name: node1 + org: + name: org1 + key: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + contract: + - firstevent: "0" + location: + address: 0x7359d2ecc199C48369b390522c29b77A5Af30882 + - defaultKey: 0x630659A26fa005d50Fa9706D8a4e242fd4169A61 + name: ns2 + plugins: + - database0 + - blockchain-ns2 + - test_user_auth-ns2 + multiparty: {} +node: {} +plugins: + blockchain: + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect1.example.com:5000 + name: blockchain-ns1 + type: ethereum + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect2.example.com:5000 + name: blockchain-ns2 + type: ethereum + database: + - name: database0 + postgres: + url: postgres://postgrs.example.com:5432/firefly?sslmode=require + type: postgres + dataexchange: + - ffdx: + url: https://ffdx:3000 + initEnabled: true + name: ff-dx + type: ffdx + sharedstorage: + - ipfs: + api: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + gateway: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + name: sharedstorage-ns1 + type: ipfs + - ipfs: + api: + url: https://ipfs2.example.com + gateway: + url: https://ipfs2.example.com + name: sharedstorage-ns2 + type: ipfs + tokens: + - fftokens: + url: https://ff-tokens-ns1-erc1155:3000 + name: erc1155-ns1 + type: fftokens + - fftokens: + url: https://ff-tokens-ns1-erc20-erc721:3000 + name: erc20_erc721-ns1 + type: fftokens + - fftokens: + url: https://ff-tokens-ns2-erc20-erc721:3000 + name: erc20_erc721-ns2 + type: fftokens + auth: + - name: test_user_auth-ns1 + type: basic + basic: + passwordfile: /etc/firefly/test_users + - name: test_user_auth-ns2 + type: basic + basic: + passwordfile: /etc/firefly/test_users +ui: + path: ./frontend +` + +// here we deliberately make a bunch of ordering changes in fields, +// but the only actual difference is the creation of a third namespace, +// and some NEW plugins +var exampleConfig2extraNS = ` +--- +http: + address: 0.0.0.0 + port: 5000 + publicURL: https://myfirefly.example.com +ui: + path: ./frontend +namespaces: + default: ns1 + predefined: + - defaultKey: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + name: ns1 + plugins: + - sharedstorage-ns1 + - database0 + - blockchain-ns1 + - ff-dx + - erc1155-ns1 + - erc20_erc721-ns1 + - test_user_auth-ns1 + multiparty: + enabled: true + node: + name: node1 + org: + name: org1 + key: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + contract: + - firstevent: "0" + location: + address: 0x7359d2ecc199C48369b390522c29b77A5Af30882 + - defaultKey: 0x630659A26fa005d50Fa9706D8a4e242fd4169A61 + plugins: + - database0 + - blockchain-ns2 + - test_user_auth-ns2 + name: ns2 + multiparty: {} + - defaultKey: 0xF49C223038FA129c2Ba23D5c5f3Cdb50120F3EDe + name: ns3 + plugins: + - database0 + - blockchain-ns3 + - test_user_auth-ns3 + multiparty: {} +node: {} +log: + level: debug +metrics: + enabled: true + address: 127.0.0.1 + port: 6000 + path: /metrics +plugins: + blockchain: + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect1.example.com:5000 + name: blockchain-ns1 + type: ethereum + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect2.example.com:5000 + name: blockchain-ns2 + type: ethereum + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect3.example.com:5000 + name: blockchain-ns3 + type: ethereum + database: + - name: database0 + type: postgres + postgres: + url: postgres://postgrs.example.com:5432/firefly?sslmode=require + dataexchange: + - ffdx: + url: https://ffdx:3000 + initEnabled: true + name: ff-dx + type: ffdx + sharedstorage: + - ipfs: + gateway: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + api: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + type: ipfs + name: sharedstorage-ns1 + - ipfs: + api: + url: https://ipfs2.example.com + gateway: + url: https://ipfs2.example.com + name: sharedstorage-ns2 + type: ipfs + tokens: + - fftokens: + url: https://ff-tokens-ns1-erc1155:3000 + type: fftokens + name: erc1155-ns1 + - fftokens: + url: https://ff-tokens-ns1-erc20-erc721:3000 + type: fftokens + name: erc20_erc721-ns1 + - fftokens: + url: https://ff-tokens-ns2-erc20-erc721:3000 + type: fftokens + name: erc20_erc721-ns2 + auth: + - name: test_user_auth-ns1 + basic: + passwordfile: /etc/firefly/test_users + type: basic + - name: test_user_auth-ns2 + basic: + passwordfile: /etc/firefly/test_users + type: basic + - name: test_user_auth-ns3 + type: basic + basic: + passwordfile: /etc/firefly/test_users +` + +var exampleConfig3NSchanges = ` +--- +http: + address: 0.0.0.0 + port: 5000 + publicURL: https://myfirefly.example.com +log: + level: debug +metrics: + enabled: true + address: 127.0.0.1 + port: 6000 + path: /metrics +namespaces: + default: ns1 + predefined: + - defaultKey: 0x763617D0e180F4909D796F8c46b6b2d17c61b5f2 # new default key + name: ns1 + plugins: + - sharedstorage-ns1 + - database0 + - blockchain-ns1 + - ff-dx + - erc1155-ns1 + - erc20_erc721-ns1 + - test_user_auth-ns1 + multiparty: + enabled: true + node: + name: node1 + org: + name: org1 + key: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + contract: + - firstevent: "0" + location: + address: 0x7359d2ecc199C48369b390522c29b77A5Af30882 + - defaultKey: 0x630659A26fa005d50Fa9706D8a4e242fd4169A61 + name: ns2 + plugins: + - database0 + - blockchain-ns2 + - test_user_auth-ns2 # config changed + multiparty: {} +node: {} +plugins: + blockchain: + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect1.example.com:5000 + name: blockchain-ns1 + type: ethereum + - ethereum: + ethconnect: + topic: "0" + url: https://ethconnect2.example.com:5000 + name: blockchain-ns2 + type: ethereum + database: + - name: database0 + postgres: + url: postgres://postgrs.example.com:5432/firefly?sslmode=require + type: postgres + dataexchange: + - ffdx: + url: https://ffdx:3000 + initEnabled: true + name: ff-dx + type: ffdx + sharedstorage: + - ipfs: + api: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + gateway: + url: https://ipfs1.example.com + auth: + username: someuser + password: somepass + name: sharedstorage-ns1 + type: ipfs + - ipfs: + api: + url: https://ipfs2.example.com + gateway: + url: https://ipfs2.example.com + name: sharedstorage-ns2 + type: ipfs + tokens: + - fftokens: + url: https://ff-tokens-ns1-erc1155:3000 + name: erc1155-ns1 + type: fftokens + - fftokens: + url: https://ff-tokens-ns1-erc20-erc721:3000 + name: erc20_erc721-ns1 + type: fftokens + - fftokens: + url: https://ff-tokens-ns2-erc20-erc721:3000 + name: erc20_erc721-ns2 + type: fftokens + auth: + - name: test_user_auth-ns1 + type: basic + basic: + passwordfile: /etc/firefly/test_users + - name: test_user_auth-ns2 + type: basic + basic: + passwordfile: /etc/firefly/test_users_new +ui: + path: ./frontend +` + +func mockInitConfig(nmm *nmMocks) { + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() + nmm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + nmm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + nmm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mti[1].On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + nmm.mei[0].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[1].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[2].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns3").Return(nil, nil).Maybe() + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + nmm.mai.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + nmm.mbi.On("Start").Return(nil) + nmm.mdx.On("Start").Return(nil) + nmm.mti[1].On("Start").Return(nil) + + nmm.mo.On("Init", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + if nmm.nm != nil && nmm.nm.namespaces["ns1"] != nil && nmm.nm.namespaces["ns1"].Contracts != nil { + nmm.nm.namespaces["ns1"].Contracts.Active = &core.MultipartyContract{ + Info: core.MultipartyContractInfo{ + Version: 2, + }, + } + } + }). + Return(nil) + nmm.mo.On("Start").Return(nil) + nmm.mo.On("WaitStop").Return(nil).Maybe() +} + +func TestConfigListenerE2E(t *testing.T) { + + testDir := t.TempDir() + configFilename := fmt.Sprintf("%s/config.firefly.yaml", testDir) + err := ioutil.WriteFile(configFilename, []byte(exampleConfig1base), 0664) + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + err = config.ReadConfig("firefly", configFilename) + assert.NoError(t, err) + config.Set(coreconfig.ConfigAutoReload, true) + + nm := NewNamespaceManager().(*namespaceManager) + nmm := mockPluginFactories(nm) + mockInitConfig(nmm) + + ctx, cancelCtx := context.WithCancel(context.Background()) + err = nm.Init(ctx, cancelCtx, make(chan bool), func() { coreconfig.Reset() }) + assert.NoError(t, err) + defer func() { + cancelCtx() + nm.WaitStop() + }() + + err = nm.Start() + assert.NoError(t, err) + + err = ioutil.WriteFile(configFilename, []byte(exampleConfig2extraNS), 0664) + assert.NoError(t, err) + + for nm.namespaces["ns3"] == nil { + time.Sleep(10 * time.Millisecond) + } + +} + +func TestConfigListenerUnreadableYAML(t *testing.T) { + + testDir := t.TempDir() + configFilename := fmt.Sprintf("%s/config.firefly.yaml", testDir) + viper.SetConfigFile(configFilename) + + coreconfig.Reset() + InitConfig() + config.Set(coreconfig.ConfigAutoReload, true) + + nm := NewNamespaceManager().(*namespaceManager) + nmm := mockPluginFactories(nm) + mockInitConfig(nmm) + + ctx, cancelCtx := context.WithCancel(context.Background()) + err := nm.Init(ctx, cancelCtx, make(chan bool), func() { coreconfig.Reset() }) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + err = ioutil.WriteFile(configFilename, []byte(`--\n: ! YAML !!!: !`), 0664) + assert.NoError(t, err) + + // Should stop itself + <-nm.ctx.Done() + nm.WaitStop() + +} + +func TestConfigReload1to2(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + originalPlugins := nm.plugins + originalPluginHashes := make(map[string]*fftypes.Bytes32) + for k, v := range originalPlugins { + originalPluginHashes[k] = v.configHash + } + originaNS := nm.namespaces + originalNSHashes := make(map[string]*fftypes.Bytes32) + for k, v := range originaNS { + originalNSHashes[k] = v.configHash + } + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(exampleConfig2extraNS)) + assert.NoError(t, err) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Error occurred in config reload") + default: + } + + // Check none of the plugins reloaded + for name := range originalPlugins { + assert.True(t, originalPlugins[name] == nm.plugins[name], name) + assert.Equal(t, originalPluginHashes[name], nm.plugins[name].configHash, name) + } + + // Check we have two more than before + assert.Len(t, nm.plugins, len(originalPlugins)+2) + assert.NotNil(t, nm.plugins["blockchain-ns3"]) + assert.NotNil(t, nm.plugins["test_user_auth-ns3"]) + + // Check none of the namespaces reloaded + for name := range originaNS { + assert.True(t, originaNS[name] == nm.namespaces[name], name) + assert.Equal(t, originalNSHashes[name], nm.namespaces[name].configHash, name) + } + + // Check we have one more than before + assert.Len(t, nm.namespaces, len(originaNS)+1) + assert.NotNil(t, nm.namespaces["ns3"]) + +} + +func TestConfigReload1to3(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + originalPlugins := nm.plugins + originalPluginHashes := make(map[string]*fftypes.Bytes32) + for k, v := range originalPlugins { + originalPluginHashes[k] = v.configHash + } + originaNS := nm.namespaces + originalNSHashes := make(map[string]*fftypes.Bytes32) + for k, v := range originaNS { + originalNSHashes[k] = v.configHash + } + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(exampleConfig3NSchanges)) + assert.NoError(t, err) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Error occurred in config reload") + default: + } + + // Check none of the plugins reloaded + for name := range originalPlugins { + if name != "test_user_auth-ns2" { + assert.True(t, originalPlugins[name] == nm.plugins[name], name) + assert.Equal(t, originalPluginHashes[name], nm.plugins[name].configHash, name) + } else { + assert.False(t, originalPlugins[name] == nm.plugins[name], name) + assert.NotEqual(t, originalPluginHashes[name], nm.plugins[name].configHash, name) + } + } + + // Check both namespaces reloaded + assert.Len(t, nm.namespaces, len(originaNS)) + assert.False(t, originaNS["ns1"] == nm.namespaces["ns1"]) + assert.NotEqual(t, originalNSHashes["ns1"], nm.namespaces["ns1"].configHash) + +} + +func TestConfigReloadBadNewConfigPlugins(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + originalPlugins := nm.plugins + originaNS := nm.namespaces + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(` +plugins: + database: [{"type": "invalid"}] +`)) + assert.NoError(t, err) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Config parse failure should not have crashed the system") + default: + } + + // Check we didn't lose our plugins + assert.Len(t, nm.plugins, len(originalPlugins)) + assert.Len(t, nm.namespaces, len(originaNS)) + +} + +func TestConfigReloadBadNSMissingRequiredPlugins(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + originalPlugins := nm.plugins + originaNS := nm.namespaces + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(` +namespaces: + predefined: + - defaultKey: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + name: ns1 + plugins: + - sharedstorage-ns1 + - database0 + - blockchain-ns1 + - ff-dx + - erc1155-ns1 + - erc20_erc721-ns1 + - test_user_auth-ns1 +`)) + assert.NoError(t, err) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Config parse failure should not have crashed the system") + default: + } + + // Check we didn't lose our plugins + assert.Len(t, nm.plugins, len(originalPlugins)) + assert.Len(t, nm.namespaces, len(originaNS)) + +} + +func TestConfigDownToNothingOk(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + // Nothing - no plugins, no namespaces + assert.NoError(t, err) + + nmm.mo.On("WaitStop").Return(nil) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Should have been happy destroying everything") + default: + } + + // Check we didn't lose our plugins + assert.Len(t, nm.plugins, len(nmm.mei)) // Just the events plugins + assert.Empty(t, nm.namespaces, 0) + +} + +func TestConfigStartPluginsFails(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(exampleConfig1base)) + assert.NoError(t, err) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + mockInitConfig(nmm) + + err = nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + // Nothing - no plugins, no namespaces + assert.NoError(t, err) + + nmm.mo.On("WaitStop").Return(nil) + + // Drive the config reload + nm.configReloaded(nm.ctx) + + // Check that we didn't cancel the context + select { + case <-nm.ctx.Done(): + assert.Fail(t, "Should have been happy destroying everything") + default: + } + + // Check we didn't lose our plugins + assert.Len(t, nm.plugins, len(nmm.mei)) // Just the events plugins + assert.Empty(t, nm.namespaces, 0) + +} + +func TestConfigReloadInitPluginsFailOnReload(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + // Start with empty config + for _, mei := range nmm.mei { + mei.On("Init", mock.Anything, mock.Anything).Return(nil).Maybe() + } + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + err := nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(` +plugins: + database: + - name: "badness" + type: "postgres" +`)) + assert.NoError(t, err) + + // Drive the config reload + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + nm.configReloaded(nm.ctx) + + // Should terminate + <-nm.ctx.Done() + nmm.mae.On("WaitStop").Return(nil).Maybe() + nmm.mo.On("WaitStop").Return(nil).Maybe() + nm.WaitStop() + +} + +func TestConfigReloadInitNamespacesFailOnReload(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + // Start with empty config + for _, mei := range nmm.mei { + mei.On("Init", mock.Anything, mock.Anything).Return(nil).Maybe() + } + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + err := nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(` +plugins: + database: + - name: "postgres" + type: "postgres" +namespaces: + predefined: + - name: default + plugins: + - postgres + `)) + assert.NoError(t, err) + + // Drive the config reload + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, fmt.Errorf("pop")) + nm.configReloaded(nm.ctx) + + // Should terminate + <-nm.ctx.Done() + nmm.mae.On("WaitStop").Return(nil).Maybe() + nmm.mo.On("WaitStop").Return(nil).Maybe() + nm.WaitStop() + +} + +func TestConfigReloadInitNamespacesFailOnStart(t *testing.T) { + logrus.SetLevel(logrus.TraceLevel) + + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + + // Start with empty config + for _, mei := range nmm.mei { + mei.On("Init", mock.Anything, mock.Anything).Return(nil).Maybe() + } + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + err := nm.Init(ctx, cancelCtx, make(chan bool), func() {}) + assert.NoError(t, err) + + err = nm.Start() + assert.NoError(t, err) + + coreconfig.Reset() + InitConfig() + viper.SetConfigType("yaml") + err = viper.ReadConfig(strings.NewReader(` +plugins: + database: + - name: "postgres" + type: "postgres" +namespaces: + predefined: + - name: default + plugins: + - postgres + `)) + assert.NoError(t, err) + + // Drive the config reload + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + nmm.mo.On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mo.On("Start", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + nm.configReloaded(nm.ctx) + + // Should terminate + <-nm.ctx.Done() + nmm.mae.On("WaitStop").Return(nil).Maybe() + nmm.mo.On("WaitStop").Return(nil).Maybe() + nm.WaitStop() + +} diff --git a/internal/namespace/manager.go b/internal/namespace/manager.go index c0f3b2c1d3..c6cfc710b4 100644 --- a/internal/namespace/manager.go +++ b/internal/namespace/manager.go @@ -18,7 +18,6 @@ package namespace import ( "context" - "encoding/json" "fmt" "strconv" "sync" @@ -52,30 +51,14 @@ import ( "github.com/hyperledger/firefly/pkg/identity" "github.com/hyperledger/firefly/pkg/sharedstorage" "github.com/hyperledger/firefly/pkg/tokens" -) - -var ( - blockchainConfig = config.RootArray("plugins.blockchain") - tokensConfig = config.RootArray("plugins.tokens") - databaseConfig = config.RootArray("plugins.database") - sharedstorageConfig = config.RootArray("plugins.sharedstorage") - dataexchangeConfig = config.RootArray("plugins.dataexchange") - identityConfig = config.RootArray("plugins.identity") - authConfig = config.RootArray("plugins.auth") - - // Deprecated configs - deprecatedTokensConfig = config.RootArray("tokens") - deprecatedBlockchainConfig = config.RootSection("blockchain") - deprecatedDatabaseConfig = config.RootSection("database") - deprecatedSharedStorageConfig = config.RootSection("sharedstorage") - deprecatedDataexchangeConfig = config.RootSection("dataexchange") + "github.com/spf13/viper" ) type Manager interface { - Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool) error + Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool, resetConfig func()) error Start() error WaitStop() - Reset(ctx context.Context) + Reset(ctx context.Context) error Orchestrator(ctx context.Context, ns string) (orchestrator.Orchestrator, error) MustOrchestrator(ns string) orchestrator.Orchestrator @@ -88,72 +71,73 @@ type Manager interface { type namespace struct { core.Namespace + ctx context.Context + cancelCtx context.CancelFunc orchestrator orchestrator.Orchestrator + loadTime *fftypes.FFTime config orchestrator.Config - plugins []string + configHash *fftypes.Bytes32 + pluginNames []string + plugins *orchestrator.Plugins } type namespaceManager struct { - reset chan bool - nsMux sync.Mutex - namespaces map[string]*namespace - pluginNames map[string]bool - plugins struct { - blockchain map[string]blockchainPlugin - identity map[string]identityPlugin - database map[string]databasePlugin - sharedstorage map[string]sharedStoragePlugin - dataexchange map[string]dataExchangePlugin - tokens map[string]tokensPlugin - events map[string]eventsPlugin - auth map[string]authPlugin - } + reset chan bool + resetConfig func() + ctx context.Context + cancelCtx context.CancelFunc + nsMux sync.Mutex + namespaces map[string]*namespace + plugins map[string]*plugin metricsEnabled bool cacheManager cache.Manager metrics metrics.Manager adminEvents spievents.Manager - utOrchestrator orchestrator.Orchestrator tokenBroadcastNames map[string]string + watchConfig func() // indirect from viper.WatchConfig for testing + + orchestratorFactory func(ns *core.Namespace, config orchestrator.Config, plugins *orchestrator.Plugins, metrics metrics.Manager, cacheManager cache.Manager) orchestrator.Orchestrator + blockchainFactory func(ctx context.Context, pluginType string) (blockchain.Plugin, error) + databaseFactory func(ctx context.Context, pluginType string) (database.Plugin, error) + dataexchangeFactory func(ctx context.Context, pluginType string) (dataexchange.Plugin, error) + sharedstorageFactory func(ctx context.Context, pluginType string) (sharedstorage.Plugin, error) + tokensFactory func(ctx context.Context, pluginType string) (tokens.Plugin, error) + identityFactory func(ctx context.Context, pluginType string) (identity.Plugin, error) + eventsFactory func(ctx context.Context, pluginType string) (events.Plugin, error) + authFactory func(ctx context.Context, pluginType string) (auth.Plugin, error) } -type blockchainPlugin struct { - config config.Section - plugin blockchain.Plugin -} - -type databasePlugin struct { - config config.Section - plugin database.Plugin -} - -type dataExchangePlugin struct { - config config.Section - plugin dataexchange.Plugin -} - -type sharedStoragePlugin struct { - config config.Section - plugin sharedstorage.Plugin -} - -type tokensPlugin struct { - config config.Section - plugin tokens.Plugin -} - -type identityPlugin struct { - config config.Section - plugin identity.Plugin -} - -type eventsPlugin struct { - config config.Section - plugin events.Plugin -} +type pluginCategory string + +const ( + pluginCategoryBlockchain pluginCategory = "blockchain" + pluginCategoryDatabase pluginCategory = "database" + pluginCategoryDataexchange pluginCategory = "dataexchange" + pluginCategorySharedstorage pluginCategory = "sharedstorage" + pluginCategoryTokens pluginCategory = "tokens" + pluginCategoryIdentity pluginCategory = "identity" + pluginCategoryEvents pluginCategory = "events" + pluginCategoryAuth pluginCategory = "auth" +) -type authPlugin struct { - config config.Section - plugin auth.Plugin +type plugin struct { + name string + category pluginCategory + pluginType string + ctx context.Context + cancelCtx context.CancelFunc + config config.Section + configHash *fftypes.Bytes32 + loadTime *fftypes.FFTime + + blockchain blockchain.Plugin + database database.Plugin + dataexchange dataexchange.Plugin + sharedstorage sharedstorage.Plugin + tokens tokens.Plugin + identity identity.Plugin + events events.Plugin + auth auth.Plugin } func stringSlicesEqual(a, b []string) bool { @@ -168,53 +152,68 @@ func stringSlicesEqual(a, b []string) bool { return true } -func NewNamespaceManager(withDefaults bool) Manager { +func NewNamespaceManager() Manager { nm := &namespaceManager{ namespaces: make(map[string]*namespace), metricsEnabled: config.GetBool(coreconfig.MetricsEnabled), tokenBroadcastNames: make(map[string]string), + watchConfig: viper.WatchConfig, + + orchestratorFactory: orchestrator.NewOrchestrator, + blockchainFactory: bifactory.GetPlugin, + databaseFactory: difactory.GetPlugin, + dataexchangeFactory: dxfactory.GetPlugin, + sharedstorageFactory: ssfactory.GetPlugin, + tokensFactory: tifactory.GetPlugin, + identityFactory: iifactory.GetPlugin, + eventsFactory: eifactory.GetPlugin, + authFactory: authfactory.GetPlugin, } + return nm +} - InitConfig(withDefaults) - - // Initialize the config on all the factories - bifactory.InitConfigDeprecated(deprecatedBlockchainConfig) - bifactory.InitConfig(blockchainConfig) - difactory.InitConfigDeprecated(deprecatedDatabaseConfig) - difactory.InitConfig(databaseConfig) - ssfactory.InitConfigDeprecated(deprecatedSharedStorageConfig) - ssfactory.InitConfig(sharedstorageConfig) - dxfactory.InitConfig(dataexchangeConfig) - dxfactory.InitConfigDeprecated(deprecatedDataexchangeConfig) - iifactory.InitConfig(identityConfig) - tifactory.InitConfigDeprecated(deprecatedTokensConfig) - tifactory.InitConfig(tokensConfig) - authfactory.InitConfigArray(authConfig) +func (nm *namespaceManager) Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool, resetConfig func()) (err error) { + nm.reset = reset // channel to ask our parent to reload us + nm.resetConfig = resetConfig // function to cause our parent to call InitConfig on all components, including us + nm.ctx = ctx + nm.cancelCtx = cancelCtx - // Events still live at the root of the config - eifactory.InitConfig(config.RootSection("events")) + initTimeRawConfig := nm.dumpRootConfig() + nm.loadManagers(ctx) + if nm.plugins, err = nm.loadPlugins(ctx, initTimeRawConfig); err != nil { + return err + } + if nm.namespaces, err = nm.loadNamespaces(ctx, initTimeRawConfig, nm.plugins); err != nil { + return err + } - return nm + return nm.initComponents(ctx) } -func (nm *namespaceManager) Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool) (err error) { - nm.reset = reset +func (nm *namespaceManager) initComponents(ctx context.Context) (err error) { + if nm.metricsEnabled { + // Ensure metrics are registered, before initializing the namespaces + metrics.Registry() + } - if err = nm.loadPlugins(ctx); err != nil { + // Initialize all the plugins on initial startup + if err = nm.initPlugins(nm.plugins); err != nil { return err } - if err = nm.initPlugins(ctx, cancelCtx); err != nil { + + // Initialize all the namespaces on initial startup + if err = nm.initNamespaces(ctx, nm.namespaces); err != nil { return err } - if err = nm.loadNamespaces(ctx); err != nil { + + if err := nm.startConfigListener(); err != nil { return err } - if nm.metricsEnabled { - // Ensure metrics are registered - metrics.Registry() - } + return nil +} +func (nm *namespaceManager) initNamespaces(ctx context.Context, newNamespaces map[string]*namespace) error { // In network version 1, the blockchain plugin and multiparty contract were global and singular. // Therefore, if any namespace was EVER pointed at a V1 contract, that contract and that namespace's plugins // become the de facto configuration for ff_system as well. There can only be one V1 contract in the history @@ -223,7 +222,7 @@ func (nm *namespaceManager) Init(ctx context.Context, cancelCtx context.CancelFu var v1Namespace *namespace var v1Contract *core.MultipartyContract - for _, ns := range nm.namespaces { + for _, ns := range newNamespaces { if err := nm.initNamespace(ctx, ns); err != nil { return err } @@ -239,7 +238,7 @@ func (nm *namespaceManager) Init(ctx context.Context, cancelCtx context.CancelFu if v1Namespace == nil { v1Namespace = ns v1Contract = contract - } else if !stringSlicesEqual(v1Namespace.plugins, ns.plugins) || + } else if !stringSlicesEqual(v1Namespace.pluginNames, ns.pluginNames) || v1Contract.Location.String() != contract.Location.String() || v1Contract.FirstEvent != contract.FirstEvent { return i18n.NewError(ctx, coremsgs.MsgCannotInitLegacyNS, core.LegacySystemNamespace, v1Namespace.Name, ns.Name) @@ -250,17 +249,23 @@ func (nm *namespaceManager) Init(ctx context.Context, cancelCtx context.CancelFu if v1Namespace != nil { systemNS := &namespace{ - Namespace: v1Namespace.Namespace, - config: v1Namespace.config, - plugins: v1Namespace.plugins, + Namespace: v1Namespace.Namespace, + loadTime: v1Namespace.loadTime, + config: v1Namespace.config, + pluginNames: v1Namespace.pluginNames, + plugins: v1Namespace.plugins, + configHash: v1Namespace.configHash, } systemNS.Name = core.LegacySystemNamespace systemNS.NetworkName = core.LegacySystemNamespace - nm.namespaces[core.LegacySystemNamespace] = systemNS - err = nm.initNamespace(ctx, systemNS) + newNamespaces[core.LegacySystemNamespace] = systemNS + if err := nm.initNamespace(ctx, systemNS); err != nil { + return err + } log.L(ctx).Infof("Initialized namespace '%s' as a copy of '%s'", core.LegacySystemNamespace, v1Namespace.Name) } - return err + + return nil } func (nm *namespaceManager) findV1Contract(ns *namespace) *core.MultipartyContract { @@ -276,17 +281,8 @@ func (nm *namespaceManager) findV1Contract(ns *namespace) *core.MultipartyContra } func (nm *namespaceManager) initNamespace(ctx context.Context, ns *namespace) (err error) { - var plugins *orchestrator.Plugins - if ns.config.Multiparty.Enabled { - plugins, err = nm.validateMultiPartyConfig(ctx, ns.Name, ns.plugins) - } else { - plugins, err = nm.validateNonMultipartyConfig(ctx, ns.Name, ns.plugins) - } - if err != nil { - return err - } - database := plugins.Database.Plugin + database := ns.plugins.Database.Plugin existing, err := database.GetNamespace(ctx, ns.Name) switch { case err != nil: @@ -307,45 +303,49 @@ func (nm *namespaceManager) initNamespace(ctx context.Context, ns *namespace) (e return err } - or := nm.utOrchestrator - if or == nil { - or = orchestrator.NewOrchestrator(&ns.Namespace, ns.config, plugins, nm.metrics, nm.cacheManager) - } - ns.orchestrator = or - orCtx, orCancel := context.WithCancel(ctx) - if err := or.Init(orCtx, orCancel); err != nil { + ns.orchestrator = nm.orchestratorFactory(&ns.Namespace, ns.config, ns.plugins, nm.metrics, nm.cacheManager) + ns.ctx, ns.cancelCtx = context.WithCancel(ctx) + if err := ns.orchestrator.Init(ns.ctx, ns.cancelCtx); err != nil { return err } - go func() { - <-orCtx.Done() - nm.nsMux.Lock() - defer nm.nsMux.Unlock() - log.L(ctx).Infof("Terminated namespace '%s'", ns.Name) - delete(nm.namespaces, ns.Name) - }() return nil } +func (nm *namespaceManager) stopNamespace(ctx context.Context, ns *namespace) { + if ns.cancelCtx != nil { + log.L(ctx).Infof("Requesting stop of namespace '%s'", ns.Name) + ns.cancelCtx() + ns.orchestrator.WaitStop() + log.L(ctx).Infof("Namespace '%s' stopped", ns.Name) + } +} + func (nm *namespaceManager) Start() error { + // On initial start, we need to start everything + return nm.startNamespacesAndPlugins(nm.namespaces, nm.plugins) +} + +func (nm *namespaceManager) startNamespacesAndPlugins(namespacesToStart map[string]*namespace, pluginsToStart map[string]*plugin) error { // Orchestrators must be started before plugins so as not to miss events - for _, ns := range nm.namespaces { + for _, ns := range namespacesToStart { if err := ns.orchestrator.Start(); err != nil { return err } } - for _, plugin := range nm.plugins.blockchain { - if err := plugin.plugin.Start(); err != nil { - return err - } - } - for _, plugin := range nm.plugins.dataexchange { - if err := plugin.plugin.Start(); err != nil { - return err - } - } - for _, plugin := range nm.plugins.tokens { - if err := plugin.plugin.Start(); err != nil { - return err + for _, plugin := range pluginsToStart { + switch plugin.category { + case pluginCategoryBlockchain: + if err := plugin.blockchain.Start(); err != nil { + return err + } + case pluginCategoryDataexchange: + if err := plugin.dataexchange.Start(); err != nil { + return err + } + case pluginCategoryTokens: + if err := plugin.tokens.Start(); err != nil { + return err + } } } return nil @@ -360,12 +360,19 @@ func (nm *namespaceManager) WaitStop() { nm.nsMux.Unlock() for _, ns := range namespaces { - ns.orchestrator.WaitStop() + nm.stopNamespace(nm.ctx, ns) } nm.adminEvents.WaitStop() } -func (nm *namespaceManager) Reset(ctx context.Context) { +func (nm *namespaceManager) Reset(ctx context.Context) error { + if config.GetBool(coreconfig.ConfigAutoReload) { + // We do not allow these settings to be combined, because viper does not provide a way to + // stop the file listener on the old root Viper instance (before reset). So we would + // leak file listeners in the background. + return i18n.NewError(context.Background(), coremsgs.MsgDeprecatedResetWithAutoReload) + } + // Queue a restart of the root context to pick up a configuration change. // Caller is responsible for terminating the passed context to trigger the actual reset // (allows caller to cleanly finish processing the current request/event). @@ -373,10 +380,11 @@ func (nm *namespaceManager) Reset(ctx context.Context) { <-ctx.Done() nm.reset <- true }() + + return nil } -func (nm *namespaceManager) loadPlugins(ctx context.Context) (err error) { - nm.pluginNames = make(map[string]bool) +func (nm *namespaceManager) loadManagers(ctx context.Context) { if nm.metrics == nil { nm.metrics = metrics.NewMetricsManager(ctx) } @@ -385,100 +393,84 @@ func (nm *namespaceManager) loadPlugins(ctx context.Context) (err error) { nm.cacheManager = cache.NewCacheManager(ctx) } - if nm.plugins.database == nil { - nm.plugins.database, err = nm.getDatabasePlugins(ctx) - if err != nil { - return err - } - } - if nm.adminEvents == nil { nm.adminEvents = spievents.NewAdminEventManager(ctx) } +} - if nm.plugins.identity == nil { - nm.plugins.identity, err = nm.getIdentityPlugins(ctx) - if err != nil { - return err - } +func (nm *namespaceManager) loadPlugins(ctx context.Context, rawConfig fftypes.JSONObject) (newPlugins map[string]*plugin, err error) { + + newPlugins = make(map[string]*plugin) + + if err := nm.getDatabasePlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.blockchain == nil { - nm.plugins.blockchain, err = nm.getBlockchainPlugins(ctx) - if err != nil { - return err - } + if err := nm.getIdentityPlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.sharedstorage == nil { - nm.plugins.sharedstorage, err = nm.getSharedStoragePlugins(ctx) - if err != nil { - return err - } + if err := nm.getBlockchainPlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.dataexchange == nil { - nm.plugins.dataexchange, err = nm.getDataExchangePlugins(ctx) - if err != nil { - return err - } + if err := nm.getSharedStoragePlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.tokens == nil { - nm.plugins.tokens, err = nm.getTokensPlugins(ctx) - if err != nil { - return err - } + if err := nm.getDataExchangePlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.events == nil { - nm.plugins.events, err = nm.getEventPlugins(ctx) - if err != nil { - return err - } + if err := nm.getTokensPlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - if nm.plugins.auth == nil { - nm.plugins.auth, err = nm.getAuthPlugin(ctx) - if err != nil { - return err - } + if err := nm.getEventPlugins(ctx, newPlugins, rawConfig); err != nil { + return nil, err } - return nil + if err := nm.getAuthPlugin(ctx, newPlugins, rawConfig); err != nil { + return nil, err + } + + return newPlugins, nil +} + +func (nm *namespaceManager) configHash(rawConfigObject fftypes.JSONObject) *fftypes.Bytes32 { + return fftypes.HashString(rawConfigObject.String()) } -func (nm *namespaceManager) getTokensPlugins(ctx context.Context) (plugins map[string]tokensPlugin, err error) { - plugins = make(map[string]tokensPlugin) +func (nm *namespaceManager) getTokensPlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { // Broadcast names must be unique broadcastNames := make(map[string]bool) tokensConfigArraySize := tokensConfig.ArraySize() + rawPluginTokensConfig := rawConfig.GetObject("plugins").GetObjectArray("tokens") + if len(rawPluginTokensConfig) != tokensConfigArraySize { + log.L(ctx).Errorf("Expected len(%d) for plugins.tokens: %s", tokensConfigArraySize, rawPluginTokensConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < tokensConfigArraySize; i++ { config := tokensConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "tokens") + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryTokens, config, rawPluginTokensConfig[i]) if err != nil { - return nil, err + return err } broadcastName := config.GetString(coreconfig.PluginBroadcastName) // If there is no broadcast name, use the plugin name if broadcastName == "" { - broadcastName = name + broadcastName = pc.name } if _, exists := broadcastNames[broadcastName]; exists { - return nil, i18n.NewError(ctx, coremsgs.MsgDuplicatePluginBroadcastName, "tokens", broadcastName) + return i18n.NewError(ctx, coremsgs.MsgDuplicatePluginBroadcastName, pluginCategoryTokens, broadcastName) } broadcastNames[broadcastName] = true - nm.tokenBroadcastNames[name] = broadcastName + nm.tokenBroadcastNames[pc.name] = broadcastName - plugin, err := tifactory.GetPlugin(ctx, pluginType) + pc.tokens, err = nm.tokensFactory(ctx, pc.pluginType) if err != nil { - return nil, err - } - - plugins[name] = tokensPlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } @@ -494,46 +486,43 @@ func (nm *namespaceManager) getTokensPlugins(ctx context.Context) (plugins map[s name := deprecatedConfig.GetString(coreconfig.PluginConfigName) pluginType := deprecatedConfig.GetString(tokens.TokensConfigPlugin) if name == "" || pluginType == "" { - return nil, i18n.NewError(ctx, coremsgs.MsgMissingTokensPluginConfig) + return i18n.NewError(ctx, coremsgs.MsgMissingTokensPluginConfig) } if err = fftypes.ValidateFFNameField(ctx, name, "name"); err != nil { - return nil, err + return err } nm.tokenBroadcastNames[name] = name - plugin, err := tifactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategoryTokens, name, pluginType, deprecatedConfig, rawConfig.GetObject("plugins").GetObject("tokens")) + if err == nil { + pc.tokens, err = nm.tokensFactory(ctx, pluginType) } - - plugins[name] = tokensPlugin{ - config: deprecatedConfig, - plugin: plugin, + if err != nil { + return err } } } - return plugins, err + return nil } -func (nm *namespaceManager) getDatabasePlugins(ctx context.Context) (plugins map[string]databasePlugin, err error) { - plugins = make(map[string]databasePlugin) +func (nm *namespaceManager) getDatabasePlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { dbConfigArraySize := databaseConfig.ArraySize() + rawPluginDatabaseConfig := rawConfig.GetObject("plugins").GetObjectArray("database") + if len(rawPluginDatabaseConfig) != dbConfigArraySize { + log.L(ctx).Errorf("Expected len(%d) for plugins.database: %s", dbConfigArraySize, rawPluginDatabaseConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < dbConfigArraySize; i++ { config := databaseConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "database") + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryDatabase, config, rawPluginDatabaseConfig[i]) if err != nil { - return nil, err + return err } - plugin, err := difactory.GetPlugin(ctx, pluginType) + pc.database, err = nm.databaseFactory(ctx, pc.pluginType) if err != nil { - return nil, err - } - - plugins[name] = databasePlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } @@ -541,60 +530,70 @@ func (nm *namespaceManager) getDatabasePlugins(ctx context.Context) (plugins map if len(plugins) == 0 { pluginType := deprecatedDatabaseConfig.GetString(coreconfig.PluginConfigType) if pluginType != "" { - plugin, err := difactory.GetPlugin(ctx, deprecatedDatabaseConfig.GetString(coreconfig.PluginConfigType)) - if err != nil { - return nil, err - } log.L(ctx).Warnf("Your database config uses a deprecated configuration structure - the database configuration has been moved under the 'plugins' section") - name := "database_0" - plugins[name] = databasePlugin{ - config: deprecatedDatabaseConfig.SubSection(pluginType), - plugin: plugin, + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategoryDatabase, "database_0", pluginType, deprecatedDatabaseConfig, rawConfig.GetObject("plugins").GetObject("database")) + if err == nil { + pc.database, err = nm.databaseFactory(ctx, pluginType) + } + if err != nil { + return err } } } - return plugins, err + return nil } -func (nm *namespaceManager) validatePluginConfig(ctx context.Context, config config.Section, sectionName string) (name, pluginType string, err error) { - name = config.GetString(coreconfig.PluginConfigName) - pluginType = config.GetString(coreconfig.PluginConfigType) +func (nm *namespaceManager) validatePluginConfig(ctx context.Context, plugins map[string]*plugin, category pluginCategory, config config.Section, rawConfig fftypes.JSONObject) (*plugin, error) { + name := config.GetString(coreconfig.PluginConfigName) + pluginType := config.GetString(coreconfig.PluginConfigType) if name == "" || pluginType == "" { - return "", "", i18n.NewError(ctx, coremsgs.MsgInvalidPluginConfiguration, sectionName) + return nil, i18n.NewError(ctx, coremsgs.MsgInvalidPluginConfiguration, category) } if err := fftypes.ValidateFFNameField(ctx, name, "name"); err != nil { - return "", "", err + return nil, err } - if _, ok := nm.pluginNames[name]; ok { - return "", "", i18n.NewError(ctx, coremsgs.MsgDuplicatePluginName, name) + return nm.newPluginCommon(ctx, plugins, category, name, pluginType, config, rawConfig) +} + +func (nm *namespaceManager) newPluginCommon(ctx context.Context, plugins map[string]*plugin, category pluginCategory, name, pluginType string, config config.Section, rawConfig fftypes.JSONObject) (*plugin, error) { + if _, ok := plugins[name]; ok { + return nil, i18n.NewError(ctx, coremsgs.MsgDuplicatePluginName, name) } - nm.pluginNames[name] = true - return name, pluginType, nil + pc := &plugin{ + name: name, + category: category, + pluginType: pluginType, + config: config.SubSection(pluginType), + configHash: nm.configHash(rawConfig), + loadTime: fftypes.Now(), + } + log.L(ctx).Tracef("Plugin %s config: %s", name, rawConfig.String()) + plugins[name] = pc + // context is always inherited from namespaceManager BG context _not_ the context of the caller + pc.ctx, pc.cancelCtx = context.WithCancel(nm.ctx) + return pc, nil } -func (nm *namespaceManager) getDataExchangePlugins(ctx context.Context) (plugins map[string]dataExchangePlugin, err error) { - plugins = make(map[string]dataExchangePlugin) +func (nm *namespaceManager) getDataExchangePlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { dxConfigArraySize := dataexchangeConfig.ArraySize() + rawPluginDXConfig := rawConfig.GetObject("plugins").GetObjectArray("dataexchange") + if len(rawPluginDXConfig) != dxConfigArraySize { + log.L(ctx).Errorf("Expected len(%d) for plugins.dataexchange: %s", dxConfigArraySize, rawPluginDXConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < dxConfigArraySize; i++ { config := dataexchangeConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "dataexchange") - if err != nil { - return nil, err + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryDataexchange, config, rawPluginDXConfig[i]) + if err == nil { + pc.dataexchange, err = nm.dataexchangeFactory(ctx, pc.pluginType) } - - plugin, err := dxfactory.GetPlugin(ctx, pluginType) if err != nil { - return nil, err - } - - plugins[name] = dataExchangePlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } @@ -602,64 +601,56 @@ func (nm *namespaceManager) getDataExchangePlugins(ctx context.Context) (plugins if len(plugins) == 0 { pluginType := deprecatedDataexchangeConfig.GetString(coreconfig.PluginConfigType) if pluginType != "" { - plugin, err := dxfactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } log.L(ctx).Warnf("Your data exchange config uses a deprecated configuration structure - the data exchange configuration has been moved under the 'plugins' section") - name := "dataexchange_0" - plugins[name] = dataExchangePlugin{ - config: deprecatedDataexchangeConfig.SubSection(pluginType), - plugin: plugin, + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategoryDataexchange, "dataexchange_0", pluginType, deprecatedDataexchangeConfig, rawConfig.GetObject("plugins").GetObject("dataexchange")) + if err == nil { + pc.dataexchange, err = nm.dataexchangeFactory(ctx, pluginType) + } + if err != nil { + return err } } } - return plugins, err + return nil } -func (nm *namespaceManager) getIdentityPlugins(ctx context.Context) (plugins map[string]identityPlugin, err error) { - plugins = make(map[string]identityPlugin) +func (nm *namespaceManager) getIdentityPlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { configSize := identityConfig.ArraySize() + rawPluginIdentityConfig := rawConfig.GetObject("plugins").GetObjectArray("identity") + if len(rawPluginIdentityConfig) != configSize { + log.L(ctx).Errorf("Expected len(%d) for plugins.identity: %s", configSize, rawPluginIdentityConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < configSize; i++ { config := identityConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "identity") - if err != nil { - return nil, err + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryIdentity, config, rawPluginIdentityConfig[i]) + if err == nil { + pc.identity, err = nm.identityFactory(ctx, pc.pluginType) } - - plugin, err := iifactory.GetPlugin(ctx, pluginType) if err != nil { - return nil, err - } - - plugins[name] = identityPlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } - return plugins, err + return nil } -func (nm *namespaceManager) getBlockchainPlugins(ctx context.Context) (plugins map[string]blockchainPlugin, err error) { - plugins = make(map[string]blockchainPlugin) +func (nm *namespaceManager) getBlockchainPlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { blockchainConfigArraySize := blockchainConfig.ArraySize() + rawPluginBlockchainsConfig := rawConfig.GetObject("plugins").GetObjectArray("blockchain") + if len(rawPluginBlockchainsConfig) != blockchainConfigArraySize { + log.L(ctx).Errorf("Expected len(%d) for plugins.blockchain: %s", blockchainConfigArraySize, rawPluginBlockchainsConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < blockchainConfigArraySize; i++ { config := blockchainConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "blockchain") - if err != nil { - return nil, err + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryBlockchain, config, rawPluginBlockchainsConfig[i]) + if err == nil { + pc.blockchain, err = nm.blockchainFactory(ctx, pc.pluginType) } - - plugin, err := bifactory.GetPlugin(ctx, pluginType) if err != nil { - return nil, err - } - - plugins[name] = blockchainPlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } @@ -667,40 +658,36 @@ func (nm *namespaceManager) getBlockchainPlugins(ctx context.Context) (plugins m if len(plugins) == 0 { pluginType := deprecatedBlockchainConfig.GetString(coreconfig.PluginConfigType) if pluginType != "" { - plugin, err := bifactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } log.L(ctx).Warnf("Your blockchain config uses a deprecated configuration structure - the blockchain configuration has been moved under the 'plugins' section") - name := "blockchain_0" - plugins[name] = blockchainPlugin{ - config: deprecatedBlockchainConfig.SubSection(pluginType), - plugin: plugin, + + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategoryBlockchain, "blockchain_0", pluginType, deprecatedBlockchainConfig, rawConfig.GetObject("plugins").GetObject("blockchain")) + if err == nil { + pc.blockchain, err = nm.blockchainFactory(ctx, pluginType) + } + if err != nil { + return err } } } - return plugins, err + return nil } -func (nm *namespaceManager) getSharedStoragePlugins(ctx context.Context) (plugins map[string]sharedStoragePlugin, err error) { - plugins = make(map[string]sharedStoragePlugin) +func (nm *namespaceManager) getSharedStoragePlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { configSize := sharedstorageConfig.ArraySize() + rawPluginSharedStorageConfig := rawConfig.GetObject("plugins").GetObjectArray("sharedstorage") + if len(rawPluginSharedStorageConfig) != configSize { + log.L(ctx).Errorf("Expected len(%d) for plugins.sharedstorage: %s", configSize, rawPluginSharedStorageConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < configSize; i++ { config := sharedstorageConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "sharedstorage") - if err != nil { - return nil, err + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategorySharedstorage, config, rawPluginSharedStorageConfig[i]) + if err == nil { + pc.sharedstorage, err = nm.sharedstorageFactory(ctx, pc.pluginType) } - - plugin, err := ssfactory.GetPlugin(ctx, pluginType) if err != nil { - return nil, err - } - - plugins[name] = sharedStoragePlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } @@ -708,66 +695,72 @@ func (nm *namespaceManager) getSharedStoragePlugins(ctx context.Context) (plugin if len(plugins) == 0 { pluginType := deprecatedSharedStorageConfig.GetString(coreconfig.PluginConfigType) if pluginType != "" { - plugin, err := ssfactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } log.L(ctx).Warnf("Your shared storage config uses a deprecated configuration structure - the shared storage configuration has been moved under the 'plugins' section") - name := "sharedstorage_0" - plugins[name] = sharedStoragePlugin{ - config: deprecatedSharedStorageConfig.SubSection(pluginType), - plugin: plugin, + + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategorySharedstorage, "sharedstorage_0", pluginType, deprecatedSharedStorageConfig, rawConfig.GetObject("plugins").GetObject("sharedstorage")) + if err == nil { + pc.sharedstorage, err = nm.sharedstorageFactory(ctx, pluginType) + } + if err != nil { + return err } } } - return plugins, err + return nil } -func (nm *namespaceManager) initPlugins(ctx context.Context, cancelCtx context.CancelFunc) (err error) { - for _, entry := range nm.plugins.database { - if err = entry.plugin.Init(ctx, entry.config); err != nil { - return err - } - entry.plugin.SetHandler(database.GlobalHandler, nm) - } - for _, entry := range nm.plugins.blockchain { - if err = entry.plugin.Init(ctx, cancelCtx, entry.config, nm.metrics, nm.cacheManager); err != nil { - return err - } - } - for _, entry := range nm.plugins.dataexchange { - if err = entry.plugin.Init(ctx, cancelCtx, entry.config); err != nil { - return err - } - } - for _, entry := range nm.plugins.sharedstorage { - if err = entry.plugin.Init(ctx, entry.config); err != nil { - return err - } - } - for name, entry := range nm.plugins.tokens { - if err = entry.plugin.Init(ctx, cancelCtx, name, entry.config); err != nil { - return err - } - } - for _, entry := range nm.plugins.events { - if err = entry.plugin.Init(ctx, entry.config); err != nil { - return err +func (nm *namespaceManager) initPlugins(pluginsToStart map[string]*plugin) (err error) { + for name, p := range nm.plugins { + if pluginsToStart[name] == nil { + continue } - } - for name, entry := range nm.plugins.auth { - if err = entry.plugin.Init(ctx, name, entry.config); err != nil { - return err + switch p.category { + case pluginCategoryDatabase: + if err = p.database.Init(p.ctx, p.config); err != nil { + return err + } + p.database.SetHandler(database.GlobalHandler, nm) + case pluginCategoryBlockchain: + if err = p.blockchain.Init(p.ctx, nm.cancelCtx /* allow plugin to stop whole process */, p.config, nm.metrics, nm.cacheManager); err != nil { + return err + } + case pluginCategoryDataexchange: + if err = p.dataexchange.Init(p.ctx, nm.cancelCtx /* allow plugin to stop whole process */, p.config); err != nil { + return err + } + case pluginCategorySharedstorage: + if err = p.sharedstorage.Init(p.ctx, p.config); err != nil { + return err + } + case pluginCategoryTokens: + if err = p.tokens.Init(p.ctx, nm.cancelCtx /* allow plugin to stop whole process */, name, p.config); err != nil { + return err + } + case pluginCategoryEvents: + if err = p.events.Init(p.ctx, p.config); err != nil { + return err + } + case pluginCategoryAuth: + if err = p.auth.Init(p.ctx, name, p.config); err != nil { + return err + } } } return nil } -func (nm *namespaceManager) loadNamespaces(ctx context.Context) (err error) { +func (nm *namespaceManager) loadNamespaces(ctx context.Context, rawConfig fftypes.JSONObject, availablePlugins map[string]*plugin) (newNS map[string]*namespace, err error) { defaultName := config.GetString(coreconfig.NamespacesDefault) size := namespacePredefined.ArraySize() + rawPredefinedNSConfig := rawConfig.GetObject("namespaces").GetObjectArray("predefined") + if len(rawPredefinedNSConfig) != size { + log.L(ctx).Errorf("Expected len(%d) for namespaces.predefined: %s", size, rawPredefinedNSConfig) + return nil, i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } foundDefault := false + + newNS = make(map[string]*namespace) for i := 0; i < size; i++ { nsConfig := namespacePredefined.ArrayEntry(i) name := nsConfig.GetString(coreconfig.NamespaceName) @@ -775,22 +768,24 @@ func (nm *namespaceManager) loadNamespaces(ctx context.Context) (err error) { log.L(ctx).Warnf("Skipping unnamed entry at namespaces.predefined[%d]", i) continue } - if _, ok := nm.namespaces[name]; ok { + if _, ok := newNS[name]; ok { log.L(ctx).Warnf("Duplicate predefined namespace (ignored): %s", name) continue } foundDefault = foundDefault || name == defaultName - if nm.namespaces[name], err = nm.loadNamespace(ctx, name, i, nsConfig); err != nil { - return err + if newNS[name], err = nm.loadNamespace(ctx, name, i, nsConfig, rawPredefinedNSConfig[i], availablePlugins); err != nil { + return nil, err } } - if !foundDefault { - return i18n.NewError(ctx, coremsgs.MsgDefaultNamespaceNotFound, defaultName) + // We allow startup with zero namespaces defined, so that we can have a FF Core + // ready to accept config updates to add new namespaces. + if !foundDefault && size > 0 { + return nil, i18n.NewError(ctx, coremsgs.MsgDefaultNamespaceNotFound, defaultName) } - return err + return newNS, err } -func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, index int, conf config.Section) (*namespace, error) { +func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, index int, conf config.Section, rawNSConfig fftypes.JSONObject, availablePlugins map[string]*plugin) (ns *namespace, err error) { if err := fftypes.ValidateFFNameField(ctx, name, fmt.Sprintf("namespaces.predefined[%d].name", index)); err != nil { return nil, err } @@ -855,30 +850,20 @@ func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, inde // If no plugins are listed under this namespace, use all defined plugins by default pluginsRaw := conf.Get(coreconfig.NamespacePlugins) - plugins := conf.GetStringSlice(coreconfig.NamespacePlugins) + pluginNames := conf.GetStringSlice(coreconfig.NamespacePlugins) if pluginsRaw == nil { - for plugin := range nm.plugins.blockchain { - plugins = append(plugins, plugin) - } - - for plugin := range nm.plugins.dataexchange { - plugins = append(plugins, plugin) - } - - for plugin := range nm.plugins.sharedstorage { - plugins = append(plugins, plugin) - } - - for plugin := range nm.plugins.database { - plugins = append(plugins, plugin) - } - - for plugin := range nm.plugins.identity { - plugins = append(plugins, plugin) - } - - for plugin := range nm.plugins.tokens { - plugins = append(plugins, plugin) + for pluginName := range nm.plugins { + p := availablePlugins[pluginName] + switch p.category { + case pluginCategoryBlockchain, + pluginCategoryDatabase, + pluginCategoryDataexchange, + pluginCategoryIdentity, + pluginCategorySharedstorage, + pluginCategoryTokens, + pluginCategoryAuth: + pluginNames = append(pluginNames, pluginName) + } } } @@ -894,12 +879,7 @@ func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, inde for i := 0; i < contractConfArraySize; i++ { conf := contractsConf.ArrayEntry(i) - locationObject := conf.GetObject(coreconfig.NamespaceMultipartyContractLocation) - b, err := json.Marshal(locationObject) - if err != nil { - return nil, err - } - location := fftypes.JSONAnyPtrBytes(b) + location := fftypes.JSONAnyPtr(conf.GetObject(coreconfig.NamespaceMultipartyContractLocation).String()) contract := multiparty.Contract{ Location: location, FirstEvent: conf.GetString(coreconfig.NamespaceMultipartyContractFirstEvent), @@ -916,124 +896,127 @@ func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, inde config.Multiparty.Node.Description = nodeDesc } - return &namespace{ + ns = &namespace{ Namespace: core.Namespace{ Name: name, NetworkName: networkName, Description: conf.GetString(coreconfig.NamespaceDescription), }, - config: config, - plugins: plugins, - }, nil + loadTime: fftypes.Now(), + config: config, + configHash: nm.configHash(rawNSConfig), + pluginNames: pluginNames, + } + log.L(ctx).Tracef("Namespace %s config: %s", name, rawNSConfig.String()) + + if ns.plugins, err = nm.validateNSPlugins(ctx, ns, availablePlugins); err != nil { + return nil, err + } + + if ns.config.Multiparty.Enabled { + err = nm.validateMultiPartyConfig(ctx, ns) + } else { + err = nm.validateNonMultipartyConfig(ctx, ns) + } + if err != nil { + return nil, err + } + + ns.plugins.Events = make(map[string]events.Plugin) + for name, p := range nm.plugins { + if p.category == pluginCategoryEvents { + ns.plugins.Events[name] = p.events + } + } + + return ns, nil } -func (nm *namespaceManager) validatePlugins(ctx context.Context, name string, plugins []string) (*orchestrator.Plugins, error) { +func (nm *namespaceManager) validateNSPlugins(ctx context.Context, ns *namespace, availablePlugins map[string]*plugin) (*orchestrator.Plugins, error) { var result orchestrator.Plugins - for _, pluginName := range plugins { - if instance, ok := nm.plugins.blockchain[pluginName]; ok { + for _, pluginName := range ns.pluginNames { + p := availablePlugins[pluginName] + if p == nil { + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceUnknownPlugin, ns.Name, pluginName) + } + switch p.category { + case pluginCategoryBlockchain: if result.Blockchain.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "blockchain") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "blockchain") } result.Blockchain = orchestrator.BlockchainPlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.blockchain, } - continue - } - if instance, ok := nm.plugins.dataexchange[pluginName]; ok { + case pluginCategoryDataexchange: if result.DataExchange.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "dataexchange") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "dataexchange") } result.DataExchange = orchestrator.DataExchangePlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.dataexchange, } - continue - } - if instance, ok := nm.plugins.sharedstorage[pluginName]; ok { + case pluginCategorySharedstorage: if result.SharedStorage.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "sharedstorage") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "sharedstorage") } result.SharedStorage = orchestrator.SharedStoragePlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.sharedstorage, } - continue - } - if instance, ok := nm.plugins.database[pluginName]; ok { + case pluginCategoryDatabase: if result.Database.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "database") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "database") } result.Database = orchestrator.DatabasePlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.database, } - continue - } - if instance, ok := nm.plugins.tokens[pluginName]; ok { + case pluginCategoryTokens: result.Tokens = append(result.Tokens, orchestrator.TokensPlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.tokens, }) - continue - } - if instance, ok := nm.plugins.identity[pluginName]; ok { + case pluginCategoryIdentity: + if result.Identity.Plugin != nil { + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "identity") + } result.Identity = orchestrator.IdentityPlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.identity, + } + case pluginCategoryAuth: + if result.Auth.Plugin != nil { + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, ns.Name, "auth") } - continue - } - if instance, ok := nm.plugins.auth[pluginName]; ok { result.Auth = orchestrator.AuthPlugin{ Name: pluginName, - Plugin: instance.plugin, + Plugin: p.auth, } - continue } - - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceUnknownPlugin, name, pluginName) } return &result, nil } -func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name string, plugins []string) (*orchestrator.Plugins, error) { +func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, ns *namespace) error { - result, err := nm.validatePlugins(ctx, name, plugins) - if err != nil { - return nil, err + if ns.plugins.Database.Plugin == nil || + ns.plugins.SharedStorage.Plugin == nil || + ns.plugins.DataExchange.Plugin == nil || + ns.plugins.Blockchain.Plugin == nil { + return i18n.NewError(ctx, coremsgs.MsgNamespaceWrongPluginsMultiparty, ns.Name) } - if result.Database.Plugin == nil || - result.SharedStorage.Plugin == nil || - result.DataExchange.Plugin == nil || - result.Blockchain.Plugin == nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceWrongPluginsMultiparty, name) - } - - result.Events = make(map[string]events.Plugin, len(nm.plugins.events)) - for name, entry := range nm.plugins.events { - result.Events[name] = entry.plugin - } - return result, nil + return nil } -func (nm *namespaceManager) validateNonMultipartyConfig(ctx context.Context, name string, plugins []string) (*orchestrator.Plugins, error) { - - result, err := nm.validatePlugins(ctx, name, plugins) - if err != nil { - return nil, err - } +func (nm *namespaceManager) validateNonMultipartyConfig(ctx context.Context, ns *namespace) error { - if result.Database.Plugin == nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceNoDatabase, name) + if ns.plugins.Database.Plugin == nil { + return i18n.NewError(ctx, coremsgs.MsgNamespaceNoDatabase, ns.Name) } - result.Events = make(map[string]events.Plugin, len(nm.plugins.events)) - for name, entry := range nm.plugins.events { - result.Events[name] = entry.plugin - } - return result, nil + return nil } func (nm *namespaceManager) SPIEvents() spievents.Manager { @@ -1093,8 +1076,7 @@ func (nm *namespaceManager) ResolveOperationByNamespacedID(ctx context.Context, return or.Operations().ResolveOperationByID(ctx, u, op) } -func (nm *namespaceManager) getEventPlugins(ctx context.Context) (plugins map[string]eventsPlugin, err error) { - plugins = make(map[string]eventsPlugin) +func (nm *namespaceManager) getEventPlugins(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { enabledTransports := config.GetStringSlice(coreconfig.EventTransportsEnabled) uniqueTransports := make(map[string]bool) for _, transport := range enabledTransports { @@ -1103,44 +1085,46 @@ func (nm *namespaceManager) getEventPlugins(ctx context.Context) (plugins map[st // Cannot disable the internal listener uniqueTransports[system.SystemEventsTransport] = true for transport := range uniqueTransports { - plugin, err := eifactory.GetPlugin(ctx, transport) + + eventsPlugin, err := nm.eventsFactory(ctx, transport) if err != nil { - return nil, err + return err } + name := eventsPlugin.Name() + rawEventConfig := rawConfig.GetObject("events").GetObject(name) - name := plugin.Name() - section := config.RootSection("events").SubSection(name) - plugin.InitConfig(section) - plugins[name] = eventsPlugin{ - config: section, - plugin: plugin, + eventsSection := config.RootSection("events") + pc, err := nm.newPluginCommon(ctx, plugins, pluginCategoryEvents, + name, name, /* name is category for events */ + eventsSection, rawEventConfig) + if err != nil { + return err } + pc.events = eventsPlugin } - return plugins, err + return nil } -func (nm *namespaceManager) getAuthPlugin(ctx context.Context) (plugins map[string]authPlugin, err error) { - plugins = make(map[string]authPlugin) - +func (nm *namespaceManager) getAuthPlugin(ctx context.Context, plugins map[string]*plugin, rawConfig fftypes.JSONObject) (err error) { authConfigArraySize := authConfig.ArraySize() + rawPluginAuthConfig := rawConfig.GetObject("plugins").GetObjectArray("auth") + if len(rawPluginAuthConfig) != authConfigArraySize { + log.L(ctx).Errorf("Expected len(%d) for plugins.auth: %s", authConfigArraySize, rawPluginAuthConfig) + return i18n.NewError(ctx, coremsgs.MsgConfigArrayVsRawConfigMismatch) + } for i := 0; i < authConfigArraySize; i++ { config := authConfig.ArrayEntry(i) - name, pluginType, err := nm.validatePluginConfig(ctx, config, "auth") + pc, err := nm.validatePluginConfig(ctx, plugins, pluginCategoryAuth, config, rawPluginAuthConfig[i]) if err != nil { - return nil, err + return err } - plugin, err := authfactory.GetPlugin(ctx, pluginType) + pc.auth, err = nm.authFactory(ctx, pc.pluginType) if err != nil { - return nil, err - } - - plugins[name] = authPlugin{ - config: config.SubSection(pluginType), - plugin: plugin, + return err } } - return plugins, err + return nil } func (nm *namespaceManager) Authorize(ctx context.Context, authReq *fftypes.AuthReq) error { diff --git a/internal/namespace/manager_test.go b/internal/namespace/manager_test.go index fdebdb770c..f75ea6e296 100644 --- a/internal/namespace/manager_test.go +++ b/internal/namespace/manager_test.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -19,18 +19,24 @@ package namespace import ( "context" "fmt" + "os" "strings" "testing" "github.com/hyperledger/firefly-common/mocks/authmocks" + "github.com/hyperledger/firefly-common/pkg/auth" "github.com/hyperledger/firefly-common/pkg/auth/authfactory" "github.com/hyperledger/firefly-common/pkg/config" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/blockchain/bifactory" + "github.com/hyperledger/firefly/internal/cache" "github.com/hyperledger/firefly/internal/coreconfig" "github.com/hyperledger/firefly/internal/database/difactory" "github.com/hyperledger/firefly/internal/dataexchange/dxfactory" + "github.com/hyperledger/firefly/internal/events/eifactory" "github.com/hyperledger/firefly/internal/identity/iifactory" + "github.com/hyperledger/firefly/internal/metrics" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/internal/sharedstorage/ssfactory" "github.com/hyperledger/firefly/internal/tokens/tifactory" "github.com/hyperledger/firefly/mocks/blockchainmocks" @@ -45,291 +51,405 @@ import ( "github.com/hyperledger/firefly/mocks/sharedstoragemocks" "github.com/hyperledger/firefly/mocks/spieventsmocks" "github.com/hyperledger/firefly/mocks/tokenmocks" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" + "github.com/hyperledger/firefly/pkg/dataexchange" + "github.com/hyperledger/firefly/pkg/events" + "github.com/hyperledger/firefly/pkg/identity" + "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" ) -type testNamespaceManager struct { - namespaceManager - mmi *metricsmocks.Manager - mae *spieventsmocks.Manager - mbi *blockchainmocks.Plugin - cmi *cachemocks.Manager - mdi *databasemocks.Plugin - mdx *dataexchangemocks.Plugin - mps *sharedstoragemocks.Plugin - mti *tokenmocks.Plugin - mev *eventsmocks.Plugin - auth *authmocks.Plugin -} - -func (nm *testNamespaceManager) cleanup(t *testing.T) { - nm.mmi.AssertExpectations(t) - nm.mae.AssertExpectations(t) - nm.mbi.AssertExpectations(t) - nm.cmi.AssertExpectations(t) - nm.mdi.AssertExpectations(t) - nm.mdx.AssertExpectations(t) - nm.mps.AssertExpectations(t) - nm.mti.AssertExpectations(t) - nm.auth.AssertExpectations(t) -} - -func newTestNamespaceManager(resetConfig bool) *testNamespaceManager { - if resetConfig { - coreconfig.Reset() - InitConfig(true) - namespaceConfig.AddKnownKey("predefined.0.multiparty.enabled", true) +var testBaseConfig = ` +--- +namespaces: + predefined: + - defaultKey: 0xbEa50Ec98776beF144Fc63078e7b15291Ac64cfA + name: default + plugins: + - ethereum + - postgres + - ffdx + - ipfs + - erc721 + - erc1155 + - basicauth + multiparty: + enabled: true + node: + name: node1 + org: + name: org1 + contract: + - firstevent: "0" + location: + address: 0x7359d2ecc199C48369b390522c29b77A5Af30882 +plugins: + blockchain: + - name: ethereum + type: ethereum + database: + - name: postgres + type: postgres + dataexchange: + - name: ffdx + type: ffdx + sharedstorage: + - ipfs: + name: ipfs + type: ipfs + tokens: + - name: erc721 + type: type1 + - name: erc1155 + type: type2 + auth: + - name: basicauth + type: basicauth + identity: + - name: tbd + type: tbd +` + +type nmMocks struct { + nm *namespaceManager + mmi *metricsmocks.Manager + mae *spieventsmocks.Manager + mbi *blockchainmocks.Plugin + cmi *cachemocks.Manager + mdi *databasemocks.Plugin + mdx *dataexchangemocks.Plugin + mps *sharedstoragemocks.Plugin + mti []*tokenmocks.Plugin + mei []*eventsmocks.Plugin + mai *authmocks.Plugin + mii *identitymocks.Plugin + mo *orchestratormocks.Orchestrator +} + +func (nmm *nmMocks) cleanup(t *testing.T) { + nmm.mmi.AssertExpectations(t) + nmm.mae.AssertExpectations(t) + nmm.mbi.AssertExpectations(t) + nmm.cmi.AssertExpectations(t) + nmm.mdi.AssertExpectations(t) + nmm.mdx.AssertExpectations(t) + nmm.mps.AssertExpectations(t) + nmm.mti[0].AssertExpectations(t) + nmm.mti[1].AssertExpectations(t) + nmm.mai.AssertExpectations(t) + nmm.mii.AssertExpectations(t) + nmm.mei[0].AssertExpectations(t) + nmm.mei[1].AssertExpectations(t) + nmm.mei[2].AssertExpectations(t) + nmm.mo.AssertExpectations(t) +} + +func factoryMocks(m *mock.Mock, name string) { + m.On("Name").Return(name).Maybe() + m.On("InitConfig", mock.Anything).Maybe() +} + +func mockPluginFactories(inm Manager) (nmm *nmMocks) { + nm := inm.(*namespaceManager) + nmm = &nmMocks{ + mmi: &metricsmocks.Manager{}, + mae: &spieventsmocks.Manager{}, + mbi: &blockchainmocks.Plugin{}, + cmi: &cachemocks.Manager{}, + mdi: &databasemocks.Plugin{}, + mdx: &dataexchangemocks.Plugin{}, + mps: &sharedstoragemocks.Plugin{}, + mti: []*tokenmocks.Plugin{{}, {}}, + mei: []*eventsmocks.Plugin{{}, {}, {}}, + mai: &authmocks.Plugin{}, + mii: &identitymocks.Plugin{}, + mo: &orchestratormocks.Orchestrator{}, + } + factoryMocks(&nmm.mbi.Mock, "ethereum") + factoryMocks(&nmm.mdi.Mock, "postgres") + factoryMocks(&nmm.mdx.Mock, "ffdx") + factoryMocks(&nmm.mps.Mock, "ipfs") + factoryMocks(&nmm.mti[0].Mock, "erc721") + factoryMocks(&nmm.mti[1].Mock, "erc1155") + factoryMocks(&nmm.mei[0].Mock, "system") + factoryMocks(&nmm.mei[1].Mock, "websockets") + factoryMocks(&nmm.mei[2].Mock, "webhooks") + factoryMocks(&nmm.mai.Mock, "basicauth") + + nm.orchestratorFactory = func(ns *core.Namespace, config orchestrator.Config, plugins *orchestrator.Plugins, metrics metrics.Manager, cacheManager cache.Manager) orchestrator.Orchestrator { + return nmm.mo + } + nm.blockchainFactory = func(ctx context.Context, pluginType string) (blockchain.Plugin, error) { + return nmm.mbi, nil } - nm := &testNamespaceManager{ - mmi: &metricsmocks.Manager{}, - mae: &spieventsmocks.Manager{}, - mbi: &blockchainmocks.Plugin{}, - cmi: &cachemocks.Manager{}, - mdi: &databasemocks.Plugin{}, - mdx: &dataexchangemocks.Plugin{}, - mps: &sharedstoragemocks.Plugin{}, - mti: &tokenmocks.Plugin{}, - mev: &eventsmocks.Plugin{}, - auth: &authmocks.Plugin{}, - namespaceManager: namespaceManager{ - reset: make(chan bool, 1), - namespaces: make(map[string]*namespace), - pluginNames: make(map[string]bool), - tokenBroadcastNames: make(map[string]string), - }, + nm.databaseFactory = func(ctx context.Context, pluginType string) (database.Plugin, error) { + return nmm.mdi, nil } - nm.plugins.blockchain = map[string]blockchainPlugin{ - "ethereum": {plugin: nm.mbi}, + nm.dataexchangeFactory = func(ctx context.Context, pluginType string) (dataexchange.Plugin, error) { + return nmm.mdx, nil } - nm.plugins.database = map[string]databasePlugin{ - "postgres": {plugin: nm.mdi}, + nm.sharedstorageFactory = func(ctx context.Context, pluginType string) (sharedstorage.Plugin, error) { + return nmm.mps, nil } - nm.plugins.dataexchange = map[string]dataExchangePlugin{ - "ffdx": {plugin: nm.mdx}, + nm.tokensFactory = func(ctx context.Context, pluginType string) (tokens.Plugin, error) { + if pluginType == "type1" { + return nmm.mti[0], nil + } + return nmm.mti[1], nil } - nm.plugins.sharedstorage = map[string]sharedStoragePlugin{ - "ipfs": {plugin: nm.mps}, + nm.identityFactory = func(ctx context.Context, pluginType string) (identity.Plugin, error) { + return nmm.mii, nil } - nm.plugins.identity = map[string]identityPlugin{ - "tbd": {plugin: &identitymocks.Plugin{}}, + nm.eventsFactory = func(ctx context.Context, pluginType string) (events.Plugin, error) { + switch pluginType { + case "system": + return nmm.mei[0], nil + case "websockets": + return nmm.mei[1], nil + case "webhooks": + return nmm.mei[2], nil + default: + panic(fmt.Errorf("Add plugin type %s to test", pluginType)) + } } - nm.plugins.tokens = map[string]tokensPlugin{ - "erc721": {plugin: nm.mti}, - "erc1155": {plugin: nm.mti}, + nm.authFactory = func(ctx context.Context, pluginType string) (auth.Plugin, error) { + return nmm.mai, nil } - nm.plugins.events = map[string]eventsPlugin{ - "websockets": {plugin: nm.mev}, + + nmm.nm = nm + return nmm +} + +func newTestNamespaceManager(t *testing.T, initConfig bool) (*namespaceManager, *nmMocks, func()) { + coreconfig.Reset() + InitConfig() + ctx, cancelCtx := context.WithCancel(context.Background()) + nm := &namespaceManager{ + ctx: ctx, + cancelCtx: cancelCtx, + reset: make(chan bool, 1), + resetConfig: InitConfig, + namespaces: make(map[string]*namespace), + plugins: make(map[string]*plugin), + tokenBroadcastNames: make(map[string]string), } - nm.plugins.auth = map[string]authPlugin{ - "basicauth": {plugin: nm.auth}, + nmm := mockPluginFactories(nm) + nmm.nm.watchConfig = func() { + <-ctx.Done() + } + nmm.nm.metrics = nmm.mmi + nmm.nm.adminEvents = nmm.mae + + if initConfig { + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(testBaseConfig)) + assert.NoError(t, err) + + nmm.mo.On("Init", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + nm.namespaces["default"].Contracts.Active = &core.MultipartyContract{ + Info: core.MultipartyContractInfo{ + Version: 2, + }, + } + }). + Return(nil). + Once() + + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil).Once() + nmm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return().Once() + nmm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nmm.mmi, mock.Anything).Return(nil).Once() + nmm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + nmm.mps.On("Init", mock.Anything, mock.Anything).Return(nil).Once() + nmm.mti[0].On("Init", mock.Anything, mock.Anything, "erc721", mock.Anything).Return(nil).Once() + nmm.mti[1].On("Init", mock.Anything, mock.Anything, "erc1155", mock.Anything).Return(nil).Once() + nmm.mei[0].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[1].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[2].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil).Once() + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil).Once() + nmm.mai.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + err = nmm.nm.Init(nmm.nm.ctx, nmm.nm.cancelCtx, nmm.nm.reset, nmm.nm.resetConfig) + assert.NoError(t, err) + } + + return nm, nmm, func() { + if a := recover(); a != nil { + panic(a) + } + nmm.nm.cancelCtx() + nmm.cleanup(t) } - nm.namespaceManager.metrics = nm.mmi - nm.namespaceManager.adminEvents = nm.mae - return nm } func TestNewNamespaceManager(t *testing.T) { - nm := NewNamespaceManager(true) + nm := NewNamespaceManager() assert.NotNil(t, nm) } -func TestInit(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) +func TestInitEmpty(t *testing.T) { + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() nm.metricsEnabled = true - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - nm.namespaces["default"].Contracts.Active = &core.MultipartyContract{ - Info: core.MultipartyContractInfo{ - Version: 2, - }, - } - }). - Return(nil) - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + nmm.mei[0].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[1].On("Init", mock.Anything, mock.Anything).Return(nil) + nmm.mei[2].On("Init", mock.Anything, mock.Anything).Return(nil) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.Init(nm.ctx, nm.cancelCtx, nm.reset, nm.resetConfig) assert.NoError(t, err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) - assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) - assert.Panics(t, func() { nm.MustOrchestrator(core.LegacySystemNamespace) }) + assert.Len(t, nm.plugins, 3) // events + assert.Empty(t, nm.namespaces) +} - mo.AssertExpectations(t) +func TestInitAllPlugins(t *testing.T) { + _, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() } -func TestInitDatabaseFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) +func TestInitComponentsPluginsFail(t *testing.T) { + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nmm.mai.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + nm.namespaces = map[string]*namespace{} + nm.plugins = map[string]*plugin{ + "basicauth": nm.plugins["basicauth"], + } + err := nm.initComponents(context.Background()) + assert.Regexp(t, "pop", err) +} - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) +func TestInitDatabaseFail(t *testing.T) { + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + nmm.mdi.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + + err := nm.initPlugins(map[string]*plugin{ + "postgres": nm.plugins["postgres"], + }) assert.EqualError(t, err, "pop") } func TestInitBlockchainFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(fmt.Errorf("pop")) + nmm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nmm.mmi, mock.Anything).Return(fmt.Errorf("pop")) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initPlugins(map[string]*plugin{ + "ethereum": nm.plugins["ethereum"], + }) assert.EqualError(t, err, "pop") } func TestInitDataExchangeFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nmm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initPlugins(map[string]*plugin{ + "ffdx": nm.plugins["ffdx"], + }) assert.EqualError(t, err, "pop") } func TestInitSharedStorageFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + nmm.mps.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initPlugins(map[string]*plugin{ + "ipfs": nm.plugins["ipfs"], + }) assert.EqualError(t, err, "pop") } func TestInitTokensFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nmm.mti[0].On("Init", mock.Anything, mock.Anything, "erc721", mock.Anything).Return(fmt.Errorf("pop")) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, mock.Anything, nil).Return(fmt.Errorf("pop")) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initPlugins(map[string]*plugin{ + "erc721": nm.plugins["erc721"], + }) assert.EqualError(t, err, "pop") } func TestInitEventsFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nm, nmm, cleanup := newTestNamespaceManager(t, false) + defer cleanup() - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + for _, mei := range nmm.mei { + mei.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Maybe() + } - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.Init(nm.ctx, nm.cancelCtx, nm.reset, nm.resetConfig) assert.EqualError(t, err, "pop") } func TestInitAuthFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.utOrchestrator = &orchestratormocks.Orchestrator{} + nmm.mai.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initPlugins(map[string]*plugin{ + "basicauth": nm.plugins["basicauth"], + }) assert.EqualError(t, err, "pop") } func TestInitOrchestratorFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.mdi.On("Capabilities").Return(&database.Capabilities{ - Concurrency: true, - }) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mbi.On("GetAndConvertDeprecatedContractConfig", mock.Anything).Return(nil, "", fmt.Errorf("pop")) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + nm.plugins = make(map[string]*plugin) + nmm.mo.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + err := nm.initComponents(nm.ctx) assert.Regexp(t, "pop", err) } +func TestInitConfigListenerFail(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + nm.metricsEnabled = false + nm.plugins = make(map[string]*plugin) + nm.namespaces = make(map[string]*namespace) + + badDir := t.TempDir() + os.Remove(badDir) + viper.SetConfigFile(fmt.Sprintf("%s/problem", badDir)) + config.Set(coreconfig.ConfigAutoReload, true) + + err := nm.initComponents(nm.ctx) + assert.Regexp(t, "FF00194", err) +} + func TestInitVersion1(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { + nm.namespaces["default"].config.Multiparty.Enabled = true nm.namespaces["default"].Contracts.Active = &core.MultipartyContract{ Location: fftypes.JSONAnyPtr("{}"), Info: core.MultipartyContractInfo{ @@ -337,43 +457,28 @@ func TestInitVersion1(t *testing.T) { }, } }). - Return(nil).Once() - mo.On("Init", mock.Anything, mock.Anything).Return(nil).Once() - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + Return(nil).Twice() // legacy system namespace + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.initNamespaces(nm.ctx, nm.namespaces) assert.NoError(t, err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) + assert.Equal(t, nmm.mo, nm.MustOrchestrator("default")) assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) assert.NotNil(t, nm.MustOrchestrator(core.LegacySystemNamespace)) - mo.AssertExpectations(t) } func TestInitFFSystemWithTerminatedV1Contract(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.metricsEnabled = true - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { + nm.namespaces["default"].config.Multiparty.Enabled = true nm.namespaces["default"].Contracts = &core.MultipartyContracts{ Active: &core.MultipartyContract{ Info: core.MultipartyContractInfo{ @@ -388,37 +493,26 @@ func TestInitFFSystemWithTerminatedV1Contract(t *testing.T) { }}, } }). - Return(nil) - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + Return(nil).Twice() // legacy system namespace - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + err := nm.initNamespaces(nm.ctx, nm.namespaces) assert.NoError(t, err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) + assert.Equal(t, nmm.mo, nm.MustOrchestrator("default")) assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) - mo.AssertExpectations(t) } func TestLegacyNamespaceConflictingPlugins(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.metricsEnabled = true + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -436,8 +530,7 @@ func TestLegacyNamespaceConflictingPlugins(t *testing.T) { `)) assert.NoError(t, err) - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { nm.namespaces["default"].Contracts = &core.MultipartyContracts{ Active: &core.MultipartyContract{ @@ -457,36 +550,29 @@ func TestLegacyNamespaceConflictingPlugins(t *testing.T) { } }). Return(nil) - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + nm.namespaces, err = nm.loadNamespaces(nm.ctx, nm.dumpRootConfig(), nm.plugins) + assert.Len(t, nm.namespaces, 2) + assert.NoError(t, err) + + err = nm.initNamespaces(nm.ctx, nm.namespaces) assert.Regexp(t, "FF10421", err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) + assert.Equal(t, nmm.mo, nm.MustOrchestrator("default")) assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) - mo.AssertExpectations(t) } func TestLegacyNamespaceConflictingPluginsTooManyPlugins(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.metricsEnabled = true + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -503,8 +589,7 @@ func TestLegacyNamespaceConflictingPluginsTooManyPlugins(t *testing.T) { `)) assert.NoError(t, err) - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { nm.namespaces["default"].Contracts = &core.MultipartyContracts{ Active: &core.MultipartyContract{ @@ -524,36 +609,29 @@ func TestLegacyNamespaceConflictingPluginsTooManyPlugins(t *testing.T) { } }). Return(nil) - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + nm.namespaces, err = nm.loadNamespaces(nm.ctx, nm.dumpRootConfig(), nm.plugins) + assert.Len(t, nm.namespaces, 2) + assert.NoError(t, err) + + err = nm.initNamespaces(nm.ctx, nm.namespaces) assert.Regexp(t, "FF10421", err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) + assert.Equal(t, nmm.mo, nm.MustOrchestrator("default")) assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) - mo.AssertExpectations(t) } func TestLegacyNamespaceMatchingPlugins(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.metricsEnabled = true + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -570,8 +648,7 @@ func TestLegacyNamespaceMatchingPlugins(t *testing.T) { `)) assert.NoError(t, err) - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { nm.namespaces["default"].Contracts = &core.MultipartyContracts{ Active: &core.MultipartyContract{ @@ -591,39 +668,31 @@ func TestLegacyNamespaceMatchingPlugins(t *testing.T) { } }). Return(nil) - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, "ns2").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + nm.namespaces, err = nm.loadNamespaces(nm.ctx, nm.dumpRootConfig(), nm.plugins) + assert.Len(t, nm.namespaces, 2) + assert.NoError(t, err) + + err = nm.initNamespaces(nm.ctx, nm.namespaces) assert.NoError(t, err) - assert.Equal(t, mo, nm.MustOrchestrator("default")) + assert.Equal(t, nmm.mo, nm.MustOrchestrator("default")) assert.Panics(t, func() { nm.MustOrchestrator("unknown") }) - mo.AssertExpectations(t) } func TestInitVersion1Fail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo := &orchestratormocks.Orchestrator{} - mo.On("Init", mock.Anything, mock.Anything). + nmm.mo.On("Init", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { + nm.namespaces["default"].config.Multiparty.Enabled = true nm.namespaces["default"].Contracts.Active = &core.MultipartyContract{ Location: fftypes.JSONAnyPtr("{}"), Info: core.MultipartyContractInfo{ @@ -632,395 +701,409 @@ func TestInitVersion1Fail(t *testing.T) { } }). Return(nil).Once() - mo.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() - nm.utOrchestrator = mo - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) - nm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + nmm.mo.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, nil) + nmm.mdi.On("GetNamespace", mock.Anything, core.LegacySystemNamespace).Return(nil, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(nil) + + err := nm.initNamespaces(nm.ctx, nm.namespaces) assert.EqualError(t, err, "pop") - mo.AssertExpectations(t) } func TestInitNamespaceQueryFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - ns := &namespace{ - Namespace: core.Namespace{ - Name: "default", - }, - plugins: []string{"postgres"}, - } + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, fmt.Errorf("pop")) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(nil, fmt.Errorf("pop")) - err := nm.initNamespace(context.Background(), ns) + err := nm.initNamespace(context.Background(), nm.namespaces["default"]) assert.EqualError(t, err, "pop") } func TestInitNamespaceExistingUpsertFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - ns := &namespace{ - Namespace: core.Namespace{ - Name: "default", - }, - plugins: []string{"postgres"}, - } + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + existing := &core.Namespace{ NetworkName: "ns1", } - nm.mdi.On("GetNamespace", mock.Anything, "default").Return(existing, nil) - nm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(fmt.Errorf("pop")) + nmm.mdi.On("GetNamespace", mock.Anything, "default").Return(existing, nil) + nmm.mdi.On("UpsertNamespace", mock.Anything, mock.AnythingOfType("*core.Namespace"), true).Return(fmt.Errorf("pop")) - err := nm.initNamespace(context.Background(), ns) + err := nm.initNamespace(context.Background(), nm.namespaces["default"]) assert.EqualError(t, err, "pop") } func TestDeprecatedDatabasePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() difactory.InitConfigDeprecated(deprecatedDatabaseConfig) deprecatedDatabaseConfig.Set(coreconfig.PluginConfigType, "postgres") - plugins, err := nm.getDatabasePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getDatabasePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDeprecatedDatabasePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() difactory.InitConfigDeprecated(deprecatedDatabaseConfig) deprecatedDatabaseConfig.Set(coreconfig.PluginConfigType, "wrong") - _, err := nm.getDatabasePlugins(context.Background()) - assert.Regexp(t, "FF10122.*wrong", err) + nm.databaseFactory = func(ctx context.Context, pluginType string) (database.Plugin, error) { + return nil, fmt.Errorf("pop") + } + err := nm.getDatabasePlugins(context.Background(), make(map[string]*plugin), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDatabasePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() difactory.InitConfig(databaseConfig) config.Set("plugins.database", []fftypes.JSONObject{{}}) databaseConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") databaseConfig.AddKnownKey(coreconfig.PluginConfigType, "postgres") - plugins, err := nm.getDatabasePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getDatabasePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDatabasePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() difactory.InitConfig(databaseConfig) config.Set("plugins.database", []fftypes.JSONObject{{}}) databaseConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") databaseConfig.AddKnownKey(coreconfig.PluginConfigType, "unknown") - plugins, err := nm.getDatabasePlugins(context.Background()) - assert.Nil(t, plugins) - assert.Error(t, err) + nm.databaseFactory = func(ctx context.Context, pluginType string) (database.Plugin, error) { + return nil, fmt.Errorf("pop") + } + err := nm.getDatabasePlugins(context.Background(), make(map[string]*plugin), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDatabasePluginBadName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.database = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() difactory.InitConfig(databaseConfig) config.Set("plugins.database", []fftypes.JSONObject{{}}) databaseConfig.AddKnownKey(coreconfig.PluginConfigName, "wrong////") databaseConfig.AddKnownKey(coreconfig.PluginConfigType, "postgres") - - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + err := nm.getDatabasePlugins(context.Background(), make(map[string]*plugin), nm.dumpRootConfig()) + assert.Regexp(t, "FF00140", err) } func TestIdentityPluginBadName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() iifactory.InitConfig(identityConfig) identityConfig.AddKnownKey(coreconfig.PluginConfigName, "wrong//") identityConfig.AddKnownKey(coreconfig.PluginConfigType, "tbd") config.Set("plugins.identity", []fftypes.JSONObject{{}}) - _, err := nm.getIdentityPlugins(context.Background()) + err := nm.getIdentityPlugins(context.Background(), make(map[string]*plugin), nm.dumpRootConfig()) assert.Regexp(t, "FF00140.*name", err) } func TestIdentityPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() iifactory.InitConfig(identityConfig) identityConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") identityConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") config.Set("plugins.identity", []fftypes.JSONObject{{}}) - _, err := nm.getIdentityPlugins(context.Background()) - assert.Regexp(t, "FF10212.*wrong", err) + nm.identityFactory = func(ctx context.Context, pluginType string) (identity.Plugin, error) { + return nil, fmt.Errorf("pop") + } + err := nm.getIdentityPlugins(context.Background(), make(map[string]*plugin), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestIdentityPluginNoType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.identity = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() iifactory.InitConfig(identityConfig) identityConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") config.Set("plugins.identity", []fftypes.JSONObject{{}}) ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) + err := nm.Init(ctx, cancelCtx, nm.reset, nm.resetConfig) assert.Regexp(t, "FF10386.*type", err) } func TestIdentityPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() iifactory.InitConfig(identityConfig) identityConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") identityConfig.AddKnownKey(coreconfig.PluginConfigType, "onchain") config.Set("plugins.identity", []fftypes.JSONObject{{}}) - plugins, err := nm.getIdentityPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getIdentityPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.NoError(t, err) assert.Equal(t, 1, len(plugins)) } func TestDeprecatedBlockchainPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() bifactory.InitConfigDeprecated(deprecatedBlockchainConfig) deprecatedBlockchainConfig.Set(coreconfig.PluginConfigType, "ethereum") - plugins, err := nm.getBlockchainPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getBlockchainPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDeprecatedBlockchainPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() deprecatedBlockchainConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") - _, err := nm.getBlockchainPlugins(context.Background()) - assert.Regexp(t, "FF10110.*wrong", err) + nm.blockchainFactory = func(ctx context.Context, pluginType string) (blockchain.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestBlockchainPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() bifactory.InitConfig(blockchainConfig) config.Set("plugins.blockchain", []fftypes.JSONObject{{}}) blockchainConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") blockchainConfig.AddKnownKey(coreconfig.PluginConfigType, "ethereum") - plugins, err := nm.getBlockchainPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getBlockchainPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestBlockchainPluginNoType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() bifactory.InitConfig(blockchainConfig) config.Set("plugins.blockchain", []fftypes.JSONObject{{}}) blockchainConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") - _, err := nm.getBlockchainPlugins(context.Background()) - assert.Error(t, err) + plugins := make(map[string]*plugin) + err := nm.getBlockchainPlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10386", err) } func TestBlockchainPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.blockchain = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() bifactory.InitConfig(blockchainConfig) config.Set("plugins.blockchain", []fftypes.JSONObject{{}}) blockchainConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") blockchainConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong//") - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + plugins := make(map[string]*plugin) + nm.blockchainFactory = func(ctx context.Context, pluginType string) (blockchain.Plugin, error) { + return nil, fmt.Errorf("pop") + } + err := nm.getBlockchainPlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDeprecatedSharedStoragePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() ssfactory.InitConfigDeprecated(deprecatedSharedStorageConfig) deprecatedSharedStorageConfig.Set(coreconfig.PluginConfigType, "ipfs") - plugins, err := nm.getSharedStoragePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getSharedStoragePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDeprecatedSharedStoragePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() ssfactory.InitConfigDeprecated(deprecatedSharedStorageConfig) deprecatedSharedStorageConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") - _, err := nm.getSharedStoragePlugins(context.Background()) - assert.Regexp(t, "FF10134.*wrong", err) + nm.sharedstorageFactory = func(ctx context.Context, pluginType string) (sharedstorage.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestSharedStoragePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() ssfactory.InitConfig(sharedstorageConfig) config.Set("plugins.sharedstorage", []fftypes.JSONObject{{}}) sharedstorageConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") sharedstorageConfig.AddKnownKey(coreconfig.PluginConfigType, "ipfs") - plugins, err := nm.getSharedStoragePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getSharedStoragePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestSharedStoragePluginNoType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() ssfactory.InitConfig(sharedstorageConfig) config.Set("plugins.sharedstorage", []fftypes.JSONObject{{}}) sharedstorageConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") - _, err := nm.getSharedStoragePlugins(context.Background()) - assert.Error(t, err) + plugins := make(map[string]*plugin) + err := nm.getSharedStoragePlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10386", err) } func TestSharedStoragePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.sharedstorage = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() ssfactory.InitConfig(sharedstorageConfig) config.Set("plugins.sharedstorage", []fftypes.JSONObject{{}}) sharedstorageConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") sharedstorageConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong//") - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + nm.sharedstorageFactory = func(ctx context.Context, pluginType string) (sharedstorage.Plugin, error) { + return nil, fmt.Errorf("pop") + } + plugins := make(map[string]*plugin) + err := nm.getSharedStoragePlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDeprecatedDataExchangePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() dxfactory.InitConfigDeprecated(deprecatedDataexchangeConfig) deprecatedDataexchangeConfig.Set(coreconfig.PluginConfigType, "ffdx") - plugins, err := nm.getDataExchangePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getDataExchangePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDeprecatedDataExchangePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() dxfactory.InitConfigDeprecated(deprecatedDataexchangeConfig) deprecatedDataexchangeConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") - _, err := nm.getDataExchangePlugins(context.Background()) - assert.Regexp(t, "FF10213.*wrong", err) + nm.dataexchangeFactory = func(ctx context.Context, pluginType string) (dataexchange.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDataExchangePlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() dxfactory.InitConfig(dataexchangeConfig) config.Set("plugins.dataexchange", []fftypes.JSONObject{{}}) dataexchangeConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") dataexchangeConfig.AddKnownKey(coreconfig.PluginConfigType, "ffdx") - plugins, err := nm.getDataExchangePlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getDataExchangePlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDataExchangePluginNoType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() dxfactory.InitConfig(dataexchangeConfig) config.Set("plugins.dataexchange", []fftypes.JSONObject{{}}) dataexchangeConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") - _, err := nm.getDataExchangePlugins(context.Background()) - assert.Error(t, err) + plugins := make(map[string]*plugin) + err := nm.getDataExchangePlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10386", err) } func TestDataExchangePluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.dataexchange = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() dxfactory.InitConfig(dataexchangeConfig) config.Set("plugins.dataexchange", []fftypes.JSONObject{{}}) dataexchangeConfig.AddKnownKey(coreconfig.PluginConfigName, "flapflip") dataexchangeConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong//") - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + nm.dataexchangeFactory = func(ctx context.Context, pluginType string) (dataexchange.Plugin, error) { + return nil, fmt.Errorf("pop") + } + plugins := make(map[string]*plugin) + err := nm.getDataExchangePlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestDeprecatedTokensPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfigDeprecated(deprecatedTokensConfig) config.Set("tokens", []fftypes.JSONObject{{}}) deprecatedTokensConfig.AddKnownKey(coreconfig.PluginConfigName, "test") deprecatedTokensConfig.AddKnownKey(tokens.TokensConfigPlugin, "fftokens") - plugins, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestDeprecatedTokensPluginNoName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfigDeprecated(deprecatedTokensConfig) config.Set("tokens", []fftypes.JSONObject{{}}) deprecatedTokensConfig.AddKnownKey(tokens.TokensConfigPlugin, "fftokens") - _, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Regexp(t, "FF10273", err) } func TestDeprecatedTokensPluginBadName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfigDeprecated(deprecatedTokensConfig) config.Set("tokens", []fftypes.JSONObject{{}}) deprecatedTokensConfig.AddKnownKey(coreconfig.PluginConfigName, "BAD!") deprecatedTokensConfig.AddKnownKey(tokens.TokensConfigPlugin, "fftokens") - _, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Regexp(t, "FF00140", err) } func TestDeprecatedTokensPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfigDeprecated(deprecatedTokensConfig) config.Set("tokens", []fftypes.JSONObject{{}}) deprecatedTokensConfig.AddKnownKey(coreconfig.PluginConfigName, "test") deprecatedTokensConfig.AddKnownKey(tokens.TokensConfigPlugin, "wrong") - _, err := nm.getTokensPlugins(context.Background()) - assert.Regexp(t, "FF10272.*wrong", err) + nm.tokensFactory = func(ctx context.Context, pluginType string) (tokens.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestTokensPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfig(tokensConfig) config.Set("plugins.tokens", []fftypes.JSONObject{{}}) tokensConfig.AddKnownKey(coreconfig.PluginConfigName, "erc20_erc721") tokensConfig.AddKnownKey(coreconfig.PluginConfigType, "fftokens") - plugins, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestTokensPluginDuplicateBroadcastName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + coreconfig.Reset() tifactory.InitConfig(tokensConfig) viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` @@ -1039,14 +1122,16 @@ func TestTokensPluginDuplicateBroadcastName(t *testing.T) { `)) assert.NoError(t, err) - plugins, err := nm.getTokensPlugins(context.Background()) - assert.Nil(t, plugins) + plugins := make(map[string]*plugin) + err = nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Regexp(t, "FF10419", err) } func TestMultipleTokensPluginsWithBroadcastName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + coreconfig.Reset() tifactory.InitConfig(tokensConfig) viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` @@ -1065,121 +1150,164 @@ func TestMultipleTokensPluginsWithBroadcastName(t *testing.T) { `)) assert.NoError(t, err) - plugins, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + err = nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 2, len(plugins)) assert.NoError(t, err) } func TestTokensPluginNoType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfig(tokensConfig) config.Set("plugins.tokens", []fftypes.JSONObject{{}}) tokensConfig.AddKnownKey(coreconfig.PluginConfigName, "erc20_erc721") - _, err := nm.getTokensPlugins(context.Background()) - assert.Error(t, err) + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10386", err) } func TestTokensPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.tokens = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfig(tokensConfig) config.Set("plugins.tokens", []fftypes.JSONObject{{}}) tokensConfig.AddKnownKey(coreconfig.PluginConfigName, "erc20_erc721") tokensConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + nm.tokensFactory = func(ctx context.Context, pluginType string) (tokens.Plugin, error) { + return nil, fmt.Errorf("pop") + } + plugins := make(map[string]*plugin) + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestTokensPluginDuplicate(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.pluginNames["erc20_erc721"] = true + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() tifactory.InitConfig(tokensConfig) config.Set("plugins.tokens", []fftypes.JSONObject{{}}) tokensConfig.AddKnownKey(coreconfig.PluginConfigName, "erc20_erc721") tokensConfig.AddKnownKey(coreconfig.PluginConfigType, "fftokens") - _, err := nm.getTokensPlugins(context.Background()) + plugins := make(map[string]*plugin) + plugins["erc20_erc721"] = &plugin{} + err := nm.getTokensPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Regexp(t, "FF10395", err) } func TestEventsPluginDefaults(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.events = nil - plugins, err := nm.getEventPlugins(context.Background()) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + plugins := make(map[string]*plugin) + err := nm.getEventPlugins(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 3, len(plugins)) assert.NoError(t, err) } +func TestEventsPluginDuplicate(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + eifactory.InitConfig(eventsConfig) + eventsConfig.AddKnownKey(coreconfig.PluginConfigName, "websockets") + eventsConfig.AddKnownKey(coreconfig.PluginConfigType, "websockets") + plugins := make(map[string]*plugin) + plugins["websockets"] = &plugin{} + err := nm.getEventPlugins(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10395", err) +} + func TestAuthPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() authfactory.InitConfigArray(authConfig) config.Set("plugins.auth", []fftypes.JSONObject{{}}) authConfig.AddKnownKey(coreconfig.PluginConfigName, "basicauth") authConfig.AddKnownKey(coreconfig.PluginConfigType, "basic") - plugins, err := nm.getAuthPlugin(context.Background()) + plugins := make(map[string]*plugin) + err := nm.getAuthPlugin(context.Background(), plugins, nm.dumpRootConfig()) assert.Equal(t, 1, len(plugins)) assert.NoError(t, err) } func TestAuthPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.auth = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() authfactory.InitConfigArray(authConfig) config.Set("plugins.auth", []fftypes.JSONObject{{}}) authConfig.AddKnownKey(coreconfig.PluginConfigName, "basicauth") authConfig.AddKnownKey(coreconfig.PluginConfigType, "wrong") - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + nm.authFactory = func(ctx context.Context, pluginType string) (auth.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestAuthPluginInvalid(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() authfactory.InitConfigArray(authConfig) config.Set("plugins.auth", []fftypes.JSONObject{{}}) authConfig.AddKnownKey(coreconfig.PluginConfigName, "bad name not allowed") authConfig.AddKnownKey(coreconfig.PluginConfigType, "basic") - plugins, err := nm.getAuthPlugin(context.Background()) - assert.Equal(t, 0, len(plugins)) - assert.Error(t, err) + plugins := make(map[string]*plugin) + err := nm.getAuthPlugin(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF00140", err) +} + +func TestAuthPluginDuplicate(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() + authfactory.InitConfigArray(authConfig) + config.Set("plugins.auth", []fftypes.JSONObject{{}}) + authConfig.AddKnownKey(coreconfig.PluginConfigName, "basicauth") + authConfig.AddKnownKey(coreconfig.PluginConfigType, "basicauth") + plugins := make(map[string]*plugin) + plugins["basicauth"] = &plugin{} + err := nm.getAuthPlugin(context.Background(), plugins, nm.dumpRootConfig()) + assert.Regexp(t, "FF10395", err) +} + +func TestRawConfigCorrelation(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + _, err := nm.loadNamespaces(nm.ctx, fftypes.JSONObject{}, nm.plugins) + err = nm.getDatabasePlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getBlockchainPlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getDataExchangePlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getSharedStoragePlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getTokensPlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getIdentityPlugins(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) + err = nm.getAuthPlugin(nm.ctx, nm.plugins, fftypes.JSONObject{}) + assert.Regexp(t, "FF10439", err) } func TestEventsPluginBadType(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - nm.plugins.events = nil + nm, _, cleanup := newTestNamespaceManager(t, false) + defer cleanup() config.Set(coreconfig.EventTransportsEnabled, []string{"!unknown!"}) - ctx, cancelCtx := context.WithCancel(context.Background()) - err := nm.Init(ctx, cancelCtx, nm.reset) - assert.Error(t, err) + nm.eventsFactory = func(ctx context.Context, pluginType string) (events.Plugin, error) { + return nil, fmt.Errorf("pop") + } + _, err := nm.loadPlugins(context.Background(), nm.dumpRootConfig()) + assert.Regexp(t, "pop", err) } func TestInitBadNamespace(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - nm.utOrchestrator = &orchestratormocks.Orchestrator{} - - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1190,14 +1318,15 @@ func TestInitBadNamespace(t *testing.T) { assert.NoError(t, err) ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + err = nm.Init(ctx, cancelCtx, nm.reset, nm.resetConfig) assert.Regexp(t, "FF00140", err) } func TestLoadNamespacesReservedName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1207,14 +1336,15 @@ func TestLoadNamespacesReservedName(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + _, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10388", err) } func TestLoadNamespacesReservedNetworkName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1227,14 +1357,15 @@ func TestLoadNamespacesReservedNetworkName(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + _, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10388", err) } func TestLoadNamespacesDuplicate(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1247,15 +1378,16 @@ func TestLoadNamespacesDuplicate(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + newNS, err := nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) - assert.Len(t, nm.namespaces, 1) + assert.Len(t, newNS, 1) } func TestLoadNamespacesNoName(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1264,14 +1396,15 @@ func TestLoadNamespacesNoName(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + _, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10166", err) } func TestLoadNamespacesNoDefault(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1282,14 +1415,15 @@ func TestLoadNamespacesNoDefault(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + _, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10166", err) } func TestLoadNamespacesUseDefaults(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1307,16 +1441,17 @@ func TestLoadNamespacesUseDefaults(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + newNS, err := nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) - assert.Len(t, nm.namespaces, 1) - assert.Equal(t, "oldest", nm.namespaces["ns1"].config.Multiparty.Contracts[0].FirstEvent) + assert.Len(t, newNS, 1) + assert.Equal(t, "oldest", newNS["ns1"].config.Multiparty.Contracts[0].FirstEvent) } func TestLoadNamespacesNonMultipartyNoDatabase(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1327,25 +1462,16 @@ func TestLoadNamespacesNonMultipartyNoDatabase(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10392", err) + } func TestLoadNamespacesMultipartyUnknownPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1358,25 +1484,15 @@ func TestLoadNamespacesMultipartyUnknownPlugin(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10390.*unknown", err) } func TestLoadNamespacesMultipartyMultipleBlockchains(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1389,25 +1505,15 @@ func TestLoadNamespacesMultipartyMultipleBlockchains(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*blockchain", err) } func TestLoadNamespacesMultipartyMultipleDX(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1420,25 +1526,15 @@ func TestLoadNamespacesMultipartyMultipleDX(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*dataexchange", err) } func TestLoadNamespacesMultipartyMultipleSS(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1451,25 +1547,15 @@ func TestLoadNamespacesMultipartyMultipleSS(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*sharedstorage", err) } func TestLoadNamespacesMultipartyMultipleDB(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1482,25 +1568,57 @@ func TestLoadNamespacesMultipartyMultipleDB(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*database", err) } +func TestLoadNamespacesMultipartyMultipleAuths(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + coreconfig.Reset() + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(` + namespaces: + default: ns1 + predefined: + - name: ns1 + plugins: [basicauth, basicauth] + multiparty: + enabled: true + `)) + assert.NoError(t, err) + + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) + assert.Regexp(t, "FF10394.*auth", err) +} + +func TestLoadNamespacesMultipartyMultipleIdentity(t *testing.T) { + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + + coreconfig.Reset() + viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(` + namespaces: + default: ns1 + predefined: + - name: ns1 + plugins: [tbd, tbd] + multiparty: + enabled: true + `)) + assert.NoError(t, err) + + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) + assert.Regexp(t, "FF10394.*identity", err) +} + func TestInitNamespacesMultipartyWithAuth(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1513,14 +1631,15 @@ func TestInitNamespacesMultipartyWithAuth(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) } func TestLoadNamespacesNonMultipartyWithAuth(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1533,14 +1652,15 @@ func TestLoadNamespacesNonMultipartyWithAuth(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) } func TestLoadNamespacesMultipartyContract(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1551,30 +1671,20 @@ func TestLoadNamespacesMultipartyContract(t *testing.T) { enabled: true contract: - location: - address: 0x4ae50189462b0e5d52285f59929d037f790771a6 + address: 0x4ae50189462b0e5d52285f59929d037f790771a6 firstEvent: oldest `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) } -func TestLoadNamespacesMultipartyContractBadLocation(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - fail := make(chan string) - namespaceConfig.AddKnownKey("predefined.0.multiparty.contract.0.location.address", fail) - - err := nm.loadNamespaces(context.Background()) - assert.Regexp(t, "json:", err) -} - func TestLoadNamespacesNonMultipartyMultipleDB(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1585,25 +1695,15 @@ func TestLoadNamespacesNonMultipartyMultipleDB(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*database", err) } func TestLoadNamespacesNonMultipartyMultipleBlockchains(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1614,25 +1714,15 @@ func TestLoadNamespacesNonMultipartyMultipleBlockchains(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10394.*blockchain", err) } func TestLoadNamespacesMultipartyMissingPlugins(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1645,25 +1735,15 @@ func TestLoadNamespacesMultipartyMissingPlugins(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10391", err) } func TestLoadNamespacesNonMultipartyUnknownPlugin(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1674,25 +1754,15 @@ func TestLoadNamespacesNonMultipartyUnknownPlugin(t *testing.T) { `)) assert.NoError(t, err) - nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return() - nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil) - nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil) - nm.mti.On("Init", mock.Anything, mock.Anything, "erc1155", nil).Return(nil) - nm.mev.On("Init", mock.Anything, mock.Anything).Return(nil) - nm.auth.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - ctx, cancelCtx := context.WithCancel(context.Background()) - err = nm.Init(ctx, cancelCtx, nm.reset) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.Regexp(t, "FF10390.*unknown", err) } func TestLoadNamespacesNonMultipartyTokens(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -1703,170 +1773,136 @@ func TestLoadNamespacesNonMultipartyTokens(t *testing.T) { `)) assert.NoError(t, err) - err = nm.loadNamespaces(context.Background()) + nm.namespaces, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) } func TestStart(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": {orchestrator: mo}, - } - nm.plugins.blockchain = nil - nm.plugins.dataexchange = nil - nm.plugins.tokens = nil - nm.metricsEnabled = true + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo.On("Start", mock.Anything).Return(nil) + nmm.mbi.On("Start", mock.Anything).Return(nil) + nmm.mdx.On("Start", mock.Anything).Return(nil) + nmm.mti[0].On("Start", mock.Anything).Return(nil) + nmm.mti[1].On("Start", mock.Anything).Return(nil) + nmm.mo.On("Start", mock.Anything).Return(nil) err := nm.Start() assert.NoError(t, err) - - mo.AssertExpectations(t) } func TestStartBlockchainFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": { - orchestrator: mo, - }, - } - - mo.On("Start", mock.Anything).Return(nil) - nm.mbi.On("Start").Return(fmt.Errorf("pop")) + nmm.mo.On("Start", mock.Anything).Return(nil) + nmm.mbi.On("Start").Return(fmt.Errorf("pop")) - err := nm.Start() + err := nm.startNamespacesAndPlugins(nm.namespaces, map[string]*plugin{ + "ethereum": nm.plugins["ethereum"], + }) assert.EqualError(t, err, "pop") - mo.AssertExpectations(t) - } func TestStartDataExchangeFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": { - orchestrator: mo, - }, - } - nm.plugins.blockchain = nil - - mo.On("Start", mock.Anything).Return(nil) - nm.mdx.On("Start").Return(fmt.Errorf("pop")) + nmm.mo.On("Start", mock.Anything).Return(nil) + nmm.mdx.On("Start").Return(fmt.Errorf("pop")) - err := nm.Start() + err := nm.startNamespacesAndPlugins(nm.namespaces, map[string]*plugin{ + "ffdx": nm.plugins["ffdx"], + }) assert.EqualError(t, err, "pop") - mo.AssertExpectations(t) - } func TestStartTokensFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": { - orchestrator: mo, - }, - } - nm.plugins.blockchain = nil - nm.plugins.dataexchange = nil - - mo.On("Start", mock.Anything).Return(nil) - nm.mti.On("Start").Return(fmt.Errorf("pop")) + nmm.mo.On("Start", mock.Anything).Return(nil) + nmm.mti[0].On("Start").Return(fmt.Errorf("pop")) - err := nm.Start() + err := nm.startNamespacesAndPlugins(nm.namespaces, map[string]*plugin{ + "erc721": nm.plugins["erc721"], + }) assert.EqualError(t, err, "pop") } func TestStartOrchestratorFail(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": {orchestrator: mo}, - } + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo.On("Start", mock.Anything).Return(fmt.Errorf("pop")) + nmm.mo.On("Start", mock.Anything).Return(fmt.Errorf("pop")) - err := nm.Start() + err := nm.startNamespacesAndPlugins(nm.namespaces, map[string]*plugin{}) assert.EqualError(t, err, "pop") - - mo.AssertExpectations(t) } func TestWaitStop(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - mo := &orchestratormocks.Orchestrator{} - nm.namespaces = map[string]*namespace{ - "ns": {orchestrator: mo}, - } - mae := nm.adminEvents.(*spieventsmocks.Manager) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() - mo.On("WaitStop").Return() - mae.On("WaitStop").Return() + nmm.mo.On("WaitStop").Return() + nmm.mae.On("WaitStop").Return() nm.WaitStop() - mo.AssertExpectations(t) - mae.AssertExpectations(t) + nmm.mo.AssertExpectations(t) + nmm.mae.AssertExpectations(t) } func TestReset(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() childCtx, childCancel := context.WithCancel(context.Background()) - nm.Reset(childCtx) + err := nm.Reset(childCtx) + assert.NoError(t, err) childCancel() - assert.True(t, <-nm.namespaceManager.reset) + assert.True(t, <-nm.reset) +} + +func TestResetRejectIfConfigAutoReload(t *testing.T) { + coreconfig.Reset() + config.Set(coreconfig.ConfigAutoReload, true) + + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + config.Set(coreconfig.ConfigAutoReload, true) + + err := nm.Reset(context.Background()) + assert.Regexp(t, "FF10438", err) + } func TestLoadMetrics(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.metrics = nil - err := nm.loadPlugins(context.Background()) - assert.NoError(t, err) + nm.loadManagers(context.Background()) } func TestLoadAdminEvents(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() nm.adminEvents = nil - err := nm.loadPlugins(context.Background()) - assert.NoError(t, err) + nm.loadManagers(context.Background()) assert.NotNil(t, nm.SPIEvents()) } func TestGetNamespaces(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - - nm.namespaces = map[string]*namespace{ - "default": {}, - } + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() results, err := nm.GetNamespaces(context.Background()) assert.Nil(t, err) @@ -1874,8 +1910,8 @@ func TestGetNamespaces(t *testing.T) { } func TestGetOperationByNamespacedID(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} nm.namespaces = map[string]*namespace{ @@ -1893,8 +1929,8 @@ func TestGetOperationByNamespacedID(t *testing.T) { } func TestGetOperationByNamespacedIDBadID(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} nm.namespaces = map[string]*namespace{ @@ -1908,8 +1944,8 @@ func TestGetOperationByNamespacedIDBadID(t *testing.T) { } func TestGetOperationByNamespacedIDNoOrchestrator(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} nm.namespaces = map[string]*namespace{ @@ -1925,8 +1961,8 @@ func TestGetOperationByNamespacedIDNoOrchestrator(t *testing.T) { } func TestResolveOperationByNamespacedID(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} mom := &operationmocks.Manager{} @@ -1946,8 +1982,8 @@ func TestResolveOperationByNamespacedID(t *testing.T) { } func TestResolveOperationByNamespacedIDBadID(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} nm.namespaces = map[string]*namespace{ @@ -1961,8 +1997,8 @@ func TestResolveOperationByNamespacedIDBadID(t *testing.T) { } func TestResolveOperationByNamespacedIDNoOrchestrator(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() mo := &orchestratormocks.Orchestrator{} nm.namespaces = map[string]*namespace{ @@ -1978,25 +2014,21 @@ func TestResolveOperationByNamespacedIDNoOrchestrator(t *testing.T) { } func TestAuthorize(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) - mo := &orchestratormocks.Orchestrator{} - mo.On("Authorize", mock.Anything, mock.Anything).Return(nil) + nm, nmm, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + nmm.mo.On("Authorize", mock.Anything, mock.Anything).Return(nil) nm.namespaces["ns1"] = &namespace{ - orchestrator: mo, + orchestrator: nmm.mo, } - nm.utOrchestrator = mo err := nm.Authorize(context.Background(), &fftypes.AuthReq{ Namespace: "ns1", }) assert.NoError(t, err) - - mo.AssertExpectations(t) } func TestAuthorizeBadNamespace(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() err := nm.Authorize(context.Background(), &fftypes.AuthReq{ Namespace: "ns1", }) @@ -2004,9 +2036,10 @@ func TestAuthorizeBadNamespace(t *testing.T) { } func TestValidateNonMultipartyConfig(t *testing.T) { - nm := newTestNamespaceManager(true) - defer nm.cleanup(t) + nm, _, cleanup := newTestNamespaceManager(t, true) + defer cleanup() + coreconfig.Reset() viper.SetConfigType("yaml") err := viper.ReadConfig(strings.NewReader(` namespaces: @@ -2017,6 +2050,6 @@ func TestValidateNonMultipartyConfig(t *testing.T) { `)) assert.NoError(t, err) - _, err = nm.validateNonMultipartyConfig(context.Background(), "ns1", []string{"postgres", "erc721"}) + _, err = nm.loadNamespaces(context.Background(), nm.dumpRootConfig(), nm.plugins) assert.NoError(t, err) } diff --git a/mocks/namespacemocks/manager.go b/mocks/namespacemocks/manager.go index 059e082bd6..bcda5fbbad 100644 --- a/mocks/namespacemocks/manager.go +++ b/mocks/namespacemocks/manager.go @@ -80,13 +80,13 @@ func (_m *Manager) GetOperationByNamespacedID(ctx context.Context, nsOpID string return r0, r1 } -// Init provides a mock function with given fields: ctx, cancelCtx, reset -func (_m *Manager) Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool) error { - ret := _m.Called(ctx, cancelCtx, reset) +// Init provides a mock function with given fields: ctx, cancelCtx, reset, resetConfig +func (_m *Manager) Init(ctx context.Context, cancelCtx context.CancelFunc, reset chan bool, resetConfig func()) error { + ret := _m.Called(ctx, cancelCtx, reset, resetConfig) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, context.CancelFunc, chan bool) error); ok { - r0 = rf(ctx, cancelCtx, reset) + if rf, ok := ret.Get(0).(func(context.Context, context.CancelFunc, chan bool, func()) error); ok { + r0 = rf(ctx, cancelCtx, reset, resetConfig) } else { r0 = ret.Error(0) } @@ -134,8 +134,17 @@ func (_m *Manager) Orchestrator(ctx context.Context, ns string) (orchestrator.Or } // Reset provides a mock function with given fields: ctx -func (_m *Manager) Reset(ctx context.Context) { - _m.Called(ctx) +func (_m *Manager) Reset(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 } // ResolveOperationByNamespacedID provides a mock function with given fields: ctx, nsOpID, op