From 8d2396216e13a5d413ae2ea4003fdf06a77c4368 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Fri, 5 Apr 2024 15:34:26 +0100 Subject: [PATCH 1/3] Expose provider tracker to service factory The following exposes the provider tracker to the service factory. The service factory now takes a IAAS and CAAS provider tracker. Initially it was hoped that you could use the generic provider tracker directly, but for controllers with both IAAS and CAAS mode types this isn't possible. We do need to expose both at the service factory level. The service that requires a provider can then create a local interface that implements some functionality of an environ. For instance the network domain service could expose that it would like a provider that supports networking: type Provider interface { environs.Networking } The service then takes a `providertracker.ProviderGetter[Provider]`, which will hand out providers. If the service requires to work on both IAAS and CAAS, then two provider getters will needed as dependencies. To provide the provider getter to the service, the service factory needs to supply the right provider. providertracker.ProviderRunner[networkservice.Provider](s.providerFactory, s.modelUUID.String()) Using a provider runner we can late bind the provider for the model UUID, so the error surfaces on use of the service method. This is akin to the DB pattern we currently have. This ProviderRunner replaces the SupportsNetworking and other environs casting functions. Ideally the casting functions in the environs package can be removed. The only concern with this change, is that the core/providertracker package is bringing in the environs package. It's clear that it _should_ live there, it just breaks one of the rules of the core package. --- .../servicefactory_mock_test.go | 4 +- cmd/jujud-controller/agent/machine.go | 1 + .../agent/machine/manifolds.go | 36 ++- .../agent/machine/manifolds_test.go | 243 ++++++++++++++++- cmd/jujud-controller/agent/model/manifolds.go | 4 +- core/providertracker/package_test.go | 16 ++ core/providertracker/provider_mock_test.go | 256 +++++++++++++++++ core/providertracker/providertracker.go | 72 +++++ core/providertracker/providertracker_test.go | 84 ++++++ domain/network/service/space.go | 54 +++- domain/servicefactory/model.go | 24 +- domain/servicefactory/service.go | 8 + domain/servicefactory/testing/service.go | 2 +- domain/servicefactory/testing/suite.go | 10 + .../migration/servicefactory_mock_test.go | 4 +- internal/servicefactory/interface.go | 2 +- internal/worker/providertracker/manifold.go | 215 ++++++++++----- .../worker/providertracker/providerworker.go | 4 +- internal/worker/servicefactory/manifold.go | 67 ++++- .../worker/servicefactory/manifold_test.go | 45 ++- .../worker/servicefactory/package_test.go | 7 + .../providertracker_mock_test.go | 257 ++++++++++++++++++ .../servicefactory_mock_test.go | 8 +- internal/worker/servicefactory/worker.go | 31 ++- internal/worker/servicefactory/worker_test.go | 13 +- 25 files changed, 1328 insertions(+), 139 deletions(-) create mode 100644 core/providertracker/package_test.go create mode 100644 core/providertracker/provider_mock_test.go create mode 100644 core/providertracker/providertracker.go create mode 100644 core/providertracker/providertracker_test.go create mode 100644 internal/worker/servicefactory/providertracker_mock_test.go diff --git a/apiserver/facades/controller/migrationtarget/servicefactory_mock_test.go b/apiserver/facades/controller/migrationtarget/servicefactory_mock_test.go index 78d639fcc11..1a4d1b22996 100644 --- a/apiserver/facades/controller/migrationtarget/servicefactory_mock_test.go +++ b/apiserver/facades/controller/migrationtarget/servicefactory_mock_test.go @@ -338,10 +338,10 @@ func (mr *MockServiceFactoryMockRecorder) ModelInfo() *gomock.Call { } // Network mocks base method. -func (m *MockServiceFactory) Network() *service14.Service { +func (m *MockServiceFactory) Network() *service14.ProviderService { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") - ret0, _ := ret[0].(*service14.Service) + ret0, _ := ret[0].(*service14.ProviderService) return ret0 } diff --git a/cmd/jujud-controller/agent/machine.go b/cmd/jujud-controller/agent/machine.go index 1749b7d688f..8f3398089fd 100644 --- a/cmd/jujud-controller/agent/machine.go +++ b/cmd/jujud-controller/agent/machine.go @@ -669,6 +669,7 @@ func (a *MachineAgent) makeEngineCreator( CharmhubHTTPClient: charmhubHTTPClient, S3HTTPClient: s3HTTPClient, NewEnvironFunc: newEnvirons, + NewCAASBrokerFunc: newCAASBroker, } manifolds := iaasMachineManifolds(manifoldsCfg) if a.isCaasAgent { diff --git a/cmd/jujud-controller/agent/machine/manifolds.go b/cmd/jujud-controller/agent/machine/manifolds.go index 7b5cb261591..f44e8cf726d 100644 --- a/cmd/jujud-controller/agent/machine/manifolds.go +++ b/cmd/jujud-controller/agent/machine/manifolds.go @@ -26,6 +26,7 @@ import ( "github.com/juju/juju/api" "github.com/juju/juju/api/base" "github.com/juju/juju/api/controller/crosscontroller" + "github.com/juju/juju/caas" "github.com/juju/juju/cmd/jujud-controller/util" "github.com/juju/juju/core/instance" corelogger "github.com/juju/juju/core/logger" @@ -93,6 +94,7 @@ import ( "github.com/juju/juju/internal/worker/peergrouper" prworker "github.com/juju/juju/internal/worker/presence" "github.com/juju/juju/internal/worker/providerservicefactory" + "github.com/juju/juju/internal/worker/providertracker" "github.com/juju/juju/internal/worker/provisioner" "github.com/juju/juju/internal/worker/proxyupdater" psworker "github.com/juju/juju/internal/worker/pubsub" @@ -280,9 +282,13 @@ type ManifoldsConfig struct { // S3HTTPClient is the HTTP client used for S3 API requests. S3HTTPClient HTTPClient + // NewEnvironFunc is a function opens a provider "environment" // (typically environs.New). NewEnvironFunc func(context.Context, environs.OpenParams) (environs.Environ, error) + + // NewCAASBrokerFunc is a function opens a CAAS broker. + NewCAASBrokerFunc func(context.Context, environs.OpenParams) (caas.Broker, error) } type HTTPClient interface { @@ -722,11 +728,13 @@ func commonManifolds(config ManifoldsConfig) dependency.Manifolds { serviceFactoryName: workerservicefactory.Manifold(workerservicefactory.ManifoldConfig{ DBAccessorName: dbAccessorName, ChangeStreamName: changeStreamName, + ProviderFactoryName: iaasProviderTrackerName, + BrokerFactoryName: caasProviderTrackerName, Logger: workerservicefactory.NewLogger("juju.worker.servicefactory"), NewWorker: workerservicefactory.NewWorker, NewServiceFactoryGetter: workerservicefactory.NewServiceFactoryGetter, NewControllerServiceFactory: workerservicefactory.NewControllerServiceFactory, - NewModelServiceFactory: workerservicefactory.NewModelServiceFactory, + NewModelServiceFactory: workerservicefactory.NewProviderTrackerModelServiceFactory, }), providerServiceFactoryName: providerservicefactory.Manifold(providerservicefactory.ManifoldConfig{ @@ -865,6 +873,30 @@ func commonManifolds(config ManifoldsConfig) dependency.Manifolds { GetControllerConfigService: objectstores3caller.GetControllerConfigService, NewWorker: objectstores3caller.NewWorker, })), + + caasProviderTrackerName: ifDatabaseUpgradeComplete(providertracker.MultiTrackerManifold[caas.Broker](providertracker.ManifoldConfig[caas.Broker]{ + ProviderServiceFactoriesName: providerServiceFactoryName, + NewWorker: providertracker.NewWorker[caas.Broker], + NewTrackerWorker: providertracker.NewTrackerWorker[caas.Broker], + GetProviderServiceFactoryGetter: providertracker.GetProviderServiceFactoryGetter, + GetProvider: providertracker.CAASGetProvider(func(ctx context.Context, args environs.OpenParams) (caas.Broker, error) { + return config.NewCAASBrokerFunc(ctx, args) + }), + Logger: loggo.GetLogger("juju.worker.caasprovidertracker"), + Clock: config.Clock, + })), + + iaasProviderTrackerName: ifDatabaseUpgradeComplete(providertracker.MultiTrackerManifold[environs.Environ](providertracker.ManifoldConfig[environs.Environ]{ + ProviderServiceFactoriesName: providerServiceFactoryName, + NewWorker: providertracker.NewWorker[environs.Environ], + NewTrackerWorker: providertracker.NewTrackerWorker[environs.Environ], + GetProviderServiceFactoryGetter: providertracker.GetProviderServiceFactoryGetter, + GetProvider: providertracker.IAASGetProvider(func(ctx context.Context, args environs.OpenParams) (environs.Environ, error) { + return config.NewEnvironFunc(ctx, args) + }), + Logger: loggo.GetLogger("juju.worker.iaasprovidertracker"), + Clock: config.Clock, + })), } return manifolds @@ -1327,6 +1359,8 @@ const ( leaseManagerName = "lease-manager" stateConverterName = "state-converter" serviceFactoryName = "service-factory" + caasProviderTrackerName = "caas-provider-tracker" + iaasProviderTrackerName = "iaas-provider-tracker" providerServiceFactoryName = "provider-service-factory" lxdContainerProvisioner = "lxd-container-provisioner" controllerAgentConfigName = "controller-agent-config" diff --git a/cmd/jujud-controller/agent/machine/manifolds_test.go b/cmd/jujud-controller/agent/machine/manifolds_test.go index 05252a21a5d..ba7925a92ce 100644 --- a/cmd/jujud-controller/agent/machine/manifolds_test.go +++ b/cmd/jujud-controller/agent/machine/manifolds_test.go @@ -73,6 +73,7 @@ func (s *ManifoldsSuite) TestManifoldNamesIAAS(c *gc.C) { "audit-config-updater", "bootstrap", "broker-tracker", + "caas-provider-tracker", "central-hub", "certificate-updater", "certificate-watcher", @@ -91,6 +92,7 @@ func (s *ManifoldsSuite) TestManifoldNamesIAAS(c *gc.C) { "host-key-reporter", "http-server-args", "http-server", + "iaas-provider-tracker", "instance-mutater", "is-bootstrap-flag", "is-bootstrap-gate", @@ -161,6 +163,7 @@ func (s *ManifoldsSuite) TestManifoldNamesCAAS(c *gc.C) { "api-server", "audit-config-updater", "bootstrap", + "caas-provider-tracker", "caas-units-manager", "central-hub", "certificate-watcher", @@ -175,6 +178,7 @@ func (s *ManifoldsSuite) TestManifoldNamesCAAS(c *gc.C) { "file-notify-watcher", "http-server-args", "http-server", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -248,6 +252,7 @@ func (s *ManifoldsSuite) TestMigrationGuardsUsed(c *gc.C) { "api-server", "audit-config-updater", "bootstrap", + "caas-provider-tracker", "central-hub", "certificate-updater", "certificate-watcher", @@ -263,6 +268,7 @@ func (s *ManifoldsSuite) TestMigrationGuardsUsed(c *gc.C) { "global-clock-updater", "http-server-args", "http-server", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -521,6 +527,7 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "api-server": { "agent", "audit-config-updater", + "caas-provider-tracker", "change-stream", "charmhub-http-client", "clock", @@ -528,6 +535,7 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "db-accessor", "file-notify-watcher", "http-server-args", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -536,46 +544,57 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "multiwatcher", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, "audit-config-updater": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "bootstrap": { "agent", + "caas-provider-tracker", "change-stream", "charmhub-http-client", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-bootstrap-gate", "is-controller-flag", "lease-manager", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", "upgrade-database-flag", "upgrade-database-gate", @@ -593,22 +612,41 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "upgrade-steps-gate", }, + "caas-provider-tracker": { + "agent", + "change-stream", + "controller-agent-config", + "db-accessor", + "file-notify-watcher", + "is-controller-flag", + "provider-service-factory", + "query-logger", + "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", + }, + "central-hub": {"agent", "state-config-watcher"}, "certificate-updater": { "agent", + "caas-provider-tracker", "certificate-watcher", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", "upgrade-check-flag", "upgrade-check-gate", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-flag", "upgrade-steps-gate", }, @@ -647,15 +685,20 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "control-socket": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "controller-agent-config": { @@ -739,14 +782,11 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "upgrade-steps-gate", }, - "trace": { - "agent", - }, - "http-server": { "agent", "api-server", "audit-config-updater", + "caas-provider-tracker", "central-hub", "certificate-watcher", "change-stream", @@ -756,6 +796,7 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "db-accessor", "file-notify-watcher", "http-server-args", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -764,27 +805,49 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "multiwatcher", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, "http-server-args": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", + }, + + "iaas-provider-tracker": { + "agent", + "change-stream", + "controller-agent-config", + "db-accessor", + "file-notify-watcher", + "is-controller-flag", + "provider-service-factory", + "query-logger", + "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "instance-mutater": { @@ -881,15 +944,20 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "log-sink": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "machine-action-runner": { @@ -955,54 +1023,68 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "model-worker-manager": { "agent", + "caas-provider-tracker", "certificate-watcher", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", "log-sink", "provider-service-factory", "query-logger", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "upgrade-check-flag", "upgrade-check-gate", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-flag", "upgrade-steps-gate", }, "multiwatcher": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "peer-grouper": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", "upgrade-check-flag", "upgrade-check-gate", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-flag", "upgrade-steps-gate", }, @@ -1040,32 +1122,42 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "object-store": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", "lease-manager", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", }, "object-store-s3-caller": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "query-logger": { @@ -1109,13 +1201,18 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "service-factory": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "ssh-authkeys-updater": { @@ -1144,14 +1241,19 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "state": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "state-config-watcher": {"agent"}, @@ -1185,6 +1287,10 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "termination-signal-handler": {}, + "trace": { + "agent", + }, + "tools-version-checker": { "agent", "api-caller", @@ -1216,14 +1322,18 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "upgrade-database-runner": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", "upgrade-database-gate", }, @@ -1247,14 +1357,19 @@ var expectedMachineManifoldsWithDependenciesIAAS = map[string][]string{ "agent", "api-caller", "api-config-watcher", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, @@ -1299,6 +1414,7 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "api-server": { "agent", "audit-config-updater", + "caas-provider-tracker", "change-stream", "charmhub-http-client", "clock", @@ -1306,6 +1422,7 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "db-accessor", "file-notify-watcher", "http-server-args", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -1314,51 +1431,76 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "multiwatcher", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, "audit-config-updater": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "bootstrap": { "agent", + "caas-provider-tracker", "change-stream", "charmhub-http-client", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-bootstrap-gate", "is-controller-flag", "lease-manager", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", "upgrade-database-flag", "upgrade-database-gate", }, + "caas-provider-tracker": { + "agent", + "change-stream", + "controller-agent-config", + "db-accessor", + "file-notify-watcher", + "is-controller-flag", + "provider-service-factory", + "query-logger", + "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", + }, + "central-hub": {"agent", "state-config-watcher"}, "certificate-watcher": { @@ -1395,15 +1537,20 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "control-socket": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "controller-agent-config": { @@ -1449,6 +1596,7 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "agent", "api-server", "audit-config-updater", + "caas-provider-tracker", "central-hub", "certificate-watcher", "change-stream", @@ -1458,6 +1606,7 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "db-accessor", "file-notify-watcher", "http-server-args", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", @@ -1466,27 +1615,49 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "multiwatcher", "object-store", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, "http-server-args": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", + }, + + "iaas-provider-tracker": { + "agent", + "change-stream", + "controller-agent-config", + "db-accessor", + "file-notify-watcher", + "is-controller-flag", + "provider-service-factory", + "query-logger", + "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "is-bootstrap-flag": { @@ -1544,15 +1715,20 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "log-sink": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "logging-config-updater": { @@ -1593,54 +1769,68 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "model-worker-manager": { "agent", + "caas-provider-tracker", "certificate-watcher", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", "log-sink", "provider-service-factory", "query-logger", "service-factory", - "state-config-watcher", "state", + "state-config-watcher", "upgrade-check-flag", "upgrade-check-gate", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-flag", "upgrade-steps-gate", }, "multiwatcher": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-bootstrap-flag", "is-bootstrap-gate", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "peer-grouper": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state", "state-config-watcher", "upgrade-check-flag", "upgrade-check-gate", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-flag", "upgrade-steps-gate", }, @@ -1678,32 +1868,42 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "object-store": { "agent", + "caas-provider-tracker", "change-stream", "clock", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", "lease-manager", "object-store-s3-caller", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", "state-config-watcher", "trace", + "upgrade-database-flag", + "upgrade-database-gate", }, "object-store-s3-caller": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "s3-http-client", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "query-logger": { @@ -1735,13 +1935,18 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "service-factory": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "ssh-identity-writer": { @@ -1758,14 +1963,19 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "state": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", }, "state-config-watcher": {"agent"}, @@ -1791,14 +2001,18 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "upgrade-database-runner": { "agent", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", "upgrade-database-gate", }, @@ -1810,14 +2024,19 @@ var expectedMachineManifoldsWithDependenciesCAAS = map[string][]string{ "agent", "api-caller", "api-config-watcher", + "caas-provider-tracker", "change-stream", "controller-agent-config", "db-accessor", "file-notify-watcher", + "iaas-provider-tracker", "is-controller-flag", + "provider-service-factory", "query-logger", "service-factory", "state-config-watcher", + "upgrade-database-flag", + "upgrade-database-gate", "upgrade-steps-gate", }, diff --git a/cmd/jujud-controller/agent/model/manifolds.go b/cmd/jujud-controller/agent/model/manifolds.go index 461fe3621c5..ef4ffca8674 100644 --- a/cmd/jujud-controller/agent/model/manifolds.go +++ b/cmd/jujud-controller/agent/model/manifolds.go @@ -354,7 +354,7 @@ func IAASManifolds(config ManifoldsConfig) dependency.Manifolds { ProviderServiceFactoriesName: providerServiceFactoriesName, NewWorker: providertracker.NewWorker[environs.Environ], NewTrackerWorker: providertracker.NewTrackerWorker[environs.Environ], - GetProviderServiceFactoryGetter: providertracker.GetProviderServiceFactoryGetter, + GetProviderServiceFactoryGetter: providertracker.GetModelProviderServiceFactoryGetter, GetProvider: providertracker.IAASGetProvider(func(ctx context.Context, args environs.OpenParams) (environs.Environ, error) { return config.NewEnvironFunc(ctx, args) }), @@ -495,7 +495,7 @@ func CAASManifolds(config ManifoldsConfig) dependency.Manifolds { ProviderServiceFactoriesName: providerServiceFactoriesName, NewWorker: providertracker.NewWorker[caas.Broker], NewTrackerWorker: providertracker.NewTrackerWorker[caas.Broker], - GetProviderServiceFactoryGetter: providertracker.GetProviderServiceFactoryGetter, + GetProviderServiceFactoryGetter: providertracker.GetModelProviderServiceFactoryGetter, GetProvider: providertracker.CAASGetProvider(func(ctx context.Context, args environs.OpenParams) (caas.Broker, error) { return config.NewContainerBrokerFunc(ctx, args) }), diff --git a/core/providertracker/package_test.go b/core/providertracker/package_test.go new file mode 100644 index 00000000000..57fd1d54b34 --- /dev/null +++ b/core/providertracker/package_test.go @@ -0,0 +1,16 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package providertracker + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +//go:generate go run go.uber.org/mock/mockgen -package providertracker -destination provider_mock_test.go github.com/juju/juju/core/providertracker ProviderFactory,Provider + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff --git a/core/providertracker/provider_mock_test.go b/core/providertracker/provider_mock_test.go new file mode 100644 index 00000000000..ea7af041ccf --- /dev/null +++ b/core/providertracker/provider_mock_test.go @@ -0,0 +1,256 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/core/providertracker (interfaces: ProviderFactory,Provider) +// +// Generated by this command: +// +// mockgen -package providertracker -destination provider_mock_test.go github.com/juju/juju/core/providertracker ProviderFactory,Provider +// + +// Package providertracker is a generated GoMock package. +package providertracker + +import ( + context "context" + reflect "reflect" + + constraints "github.com/juju/juju/core/constraints" + environs "github.com/juju/juju/environs" + config "github.com/juju/juju/environs/config" + envcontext "github.com/juju/juju/environs/envcontext" + storage "github.com/juju/juju/internal/storage" + version "github.com/juju/version/v2" + gomock "go.uber.org/mock/gomock" +) + +// MockProviderFactory is a mock of ProviderFactory interface. +type MockProviderFactory struct { + ctrl *gomock.Controller + recorder *MockProviderFactoryMockRecorder +} + +// MockProviderFactoryMockRecorder is the mock recorder for MockProviderFactory. +type MockProviderFactoryMockRecorder struct { + mock *MockProviderFactory +} + +// NewMockProviderFactory creates a new mock instance. +func NewMockProviderFactory(ctrl *gomock.Controller) *MockProviderFactory { + mock := &MockProviderFactory{ctrl: ctrl} + mock.recorder = &MockProviderFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProviderFactory) EXPECT() *MockProviderFactoryMockRecorder { + return m.recorder +} + +// ProviderForModel mocks base method. +func (m *MockProviderFactory) ProviderForModel(arg0 context.Context, arg1 string) (Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProviderForModel", arg0, arg1) + ret0, _ := ret[0].(Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProviderForModel indicates an expected call of ProviderForModel. +func (mr *MockProviderFactoryMockRecorder) ProviderForModel(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProviderForModel", reflect.TypeOf((*MockProviderFactory)(nil).ProviderForModel), arg0, arg1) +} + +// MockProvider is a mock of Provider interface. +type MockProvider struct { + ctrl *gomock.Controller + recorder *MockProviderMockRecorder +} + +// MockProviderMockRecorder is the mock recorder for MockProvider. +type MockProviderMockRecorder struct { + mock *MockProvider +} + +// NewMockProvider creates a new mock instance. +func NewMockProvider(ctrl *gomock.Controller) *MockProvider { + mock := &MockProvider{ctrl: ctrl} + mock.recorder = &MockProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProvider) EXPECT() *MockProviderMockRecorder { + return m.recorder +} + +// AdoptResources mocks base method. +func (m *MockProvider) AdoptResources(arg0 envcontext.ProviderCallContext, arg1 string, arg2 version.Number) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AdoptResources", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// AdoptResources indicates an expected call of AdoptResources. +func (mr *MockProviderMockRecorder) AdoptResources(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdoptResources", reflect.TypeOf((*MockProvider)(nil).AdoptResources), arg0, arg1, arg2) +} + +// Bootstrap mocks base method. +func (m *MockProvider) Bootstrap(arg0 environs.BootstrapContext, arg1 envcontext.ProviderCallContext, arg2 environs.BootstrapParams) (*environs.BootstrapResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Bootstrap", arg0, arg1, arg2) + ret0, _ := ret[0].(*environs.BootstrapResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Bootstrap indicates an expected call of Bootstrap. +func (mr *MockProviderMockRecorder) Bootstrap(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bootstrap", reflect.TypeOf((*MockProvider)(nil).Bootstrap), arg0, arg1, arg2) +} + +// Config mocks base method. +func (m *MockProvider) Config() *config.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Config") + ret0, _ := ret[0].(*config.Config) + return ret0 +} + +// Config indicates an expected call of Config. +func (mr *MockProviderMockRecorder) Config() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Config", reflect.TypeOf((*MockProvider)(nil).Config)) +} + +// ConstraintsValidator mocks base method. +func (m *MockProvider) ConstraintsValidator(arg0 envcontext.ProviderCallContext) (constraints.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConstraintsValidator", arg0) + ret0, _ := ret[0].(constraints.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConstraintsValidator indicates an expected call of ConstraintsValidator. +func (mr *MockProviderMockRecorder) ConstraintsValidator(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConstraintsValidator", reflect.TypeOf((*MockProvider)(nil).ConstraintsValidator), arg0) +} + +// Create mocks base method. +func (m *MockProvider) Create(arg0 envcontext.ProviderCallContext, arg1 environs.CreateParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockProviderMockRecorder) Create(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockProvider)(nil).Create), arg0, arg1) +} + +// Destroy mocks base method. +func (m *MockProvider) Destroy(arg0 envcontext.ProviderCallContext) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Destroy", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Destroy indicates an expected call of Destroy. +func (mr *MockProviderMockRecorder) Destroy(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockProvider)(nil).Destroy), arg0) +} + +// DestroyController mocks base method. +func (m *MockProvider) DestroyController(arg0 envcontext.ProviderCallContext, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DestroyController", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DestroyController indicates an expected call of DestroyController. +func (mr *MockProviderMockRecorder) DestroyController(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestroyController", reflect.TypeOf((*MockProvider)(nil).DestroyController), arg0, arg1) +} + +// PrecheckInstance mocks base method. +func (m *MockProvider) PrecheckInstance(arg0 envcontext.ProviderCallContext, arg1 environs.PrecheckInstanceParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrecheckInstance", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrecheckInstance indicates an expected call of PrecheckInstance. +func (mr *MockProviderMockRecorder) PrecheckInstance(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrecheckInstance", reflect.TypeOf((*MockProvider)(nil).PrecheckInstance), arg0, arg1) +} + +// PrepareForBootstrap mocks base method. +func (m *MockProvider) PrepareForBootstrap(arg0 environs.BootstrapContext, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrepareForBootstrap", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrepareForBootstrap indicates an expected call of PrepareForBootstrap. +func (mr *MockProviderMockRecorder) PrepareForBootstrap(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareForBootstrap", reflect.TypeOf((*MockProvider)(nil).PrepareForBootstrap), arg0, arg1) +} + +// SetConfig mocks base method. +func (m *MockProvider) SetConfig(arg0 *config.Config) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetConfig", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetConfig indicates an expected call of SetConfig. +func (mr *MockProviderMockRecorder) SetConfig(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfig", reflect.TypeOf((*MockProvider)(nil).SetConfig), arg0) +} + +// StorageProvider mocks base method. +func (m *MockProvider) StorageProvider(arg0 storage.ProviderType) (storage.Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageProvider", arg0) + ret0, _ := ret[0].(storage.Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StorageProvider indicates an expected call of StorageProvider. +func (mr *MockProviderMockRecorder) StorageProvider(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageProvider", reflect.TypeOf((*MockProvider)(nil).StorageProvider), arg0) +} + +// StorageProviderTypes mocks base method. +func (m *MockProvider) StorageProviderTypes() ([]storage.ProviderType, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageProviderTypes") + ret0, _ := ret[0].([]storage.ProviderType) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StorageProviderTypes indicates an expected call of StorageProviderTypes. +func (mr *MockProviderMockRecorder) StorageProviderTypes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageProviderTypes", reflect.TypeOf((*MockProvider)(nil).StorageProviderTypes)) +} diff --git a/core/providertracker/providertracker.go b/core/providertracker/providertracker.go new file mode 100644 index 00000000000..2809022c244 --- /dev/null +++ b/core/providertracker/providertracker.go @@ -0,0 +1,72 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package providertracker + +import ( + "context" + + "github.com/juju/errors" + + "github.com/juju/juju/environs" +) + +// Provider in the intersection of a environs.Environ and a caas.Broker. +// +// We ideally don't want to import the environs package here, but I've not +// sure how to avoid it. +type Provider interface { + // InstancePrechecker provides a means of "prechecking" placement + // arguments before recording them in state. + environs.InstancePrechecker + + // BootstrapEnviron defines methods for bootstrapping a controller. + environs.BootstrapEnviron + + // ResourceAdopter defines methods for adopting resources. + environs.ResourceAdopter +} + +// ProviderFactory is an interface that provides a way to get a provider +// for a given model namespace. It will continue to be updated in the background +// for as long as the Worker continues to run. +type ProviderFactory interface { + // ProviderForModel returns the encapsulated provider for a given model + // namespace. It will continue to be updated in the background for as long + // as the Worker continues to run. If the worker is not a singular worker, + // then an error will be returned. + ProviderForModel(ctx context.Context, namespace string) (Provider, error) +} + +// GenericProviderFactory is an interface that provides a way to get a provider +// for a given model namespace. It will continue to be updated in the background +// for as long as the Worker continues to run. +type GenericProviderFactory[T Provider] interface { + // ProviderForModel returns the encapsulated provider for a given model + // namespace. It will continue to be updated in the background for as long + // as the Worker continues to run. If the worker is not a singular worker, + // then an error will be returned. + ProviderForModel(ctx context.Context, namespace string) (T, error) +} + +// ProviderGetter is a function that returns a provider for a given type. +// It's generic type any because it can return any type of provider, this should +// be used in conjunction with the ProviderRunner function. +type ProviderGetter[T any] func(ctx context.Context) (T, error) + +// ProviderRunner returns the ProviderGetter function for a given generic type. +// If the returned provider is not of the expected type, a not supported +// error will be returned. +func ProviderRunner[T any](providerFactory ProviderFactory, namespace string) func(context.Context) (T, error) { + var zero T + return func(ctx context.Context) (T, error) { + p, err := providerFactory.ProviderForModel(ctx, namespace) + if err != nil { + return zero, errors.Trace(err) + } + if v, ok := p.(T); ok { + return v, nil + } + return zero, errors.NotSupportedf("provider type %T", zero) + } +} diff --git a/core/providertracker/providertracker_test.go b/core/providertracker/providertracker_test.go new file mode 100644 index 00000000000..2f80047cfb3 --- /dev/null +++ b/core/providertracker/providertracker_test.go @@ -0,0 +1,84 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package providertracker + +import ( + "context" + + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "go.uber.org/mock/gomock" + gc "gopkg.in/check.v1" +) + +type providerSuite struct { + testing.IsolationSuite + + provider *MockProvider + providerFactory *MockProviderFactory +} + +var _ = gc.Suite(&providerSuite{}) + +func (s *providerSuite) TestProviderRunner(c *gc.C) { + defer s.setupMocks(c).Finish() + + s.providerFactory.EXPECT().ProviderForModel(gomock.Any(), "foo").Return(s.provider, nil) + + runner := ProviderRunner[Provider](s.providerFactory, "foo") + v, err := runner(context.Background()) + c.Assert(err, jc.ErrorIsNil) + c.Check(v, gc.DeepEquals, s.provider) +} + +func (s *providerSuite) TestProviderRunnerSubsetType(c *gc.C) { + defer s.setupMocks(c).Finish() + + provider := &fooProvider{Provider: s.provider} + + s.providerFactory.EXPECT().ProviderForModel(gomock.Any(), "foo").Return(provider, nil) + + runner := ProviderRunner[FooProvider](s.providerFactory, "foo") + v, err := runner(context.Background()) + c.Assert(err, jc.ErrorIsNil) + c.Check(v, gc.DeepEquals, provider) +} + +func (s *providerSuite) TestProviderRunnerIsNotSubsetType(c *gc.C) { + defer s.setupMocks(c).Finish() + + provider := &fooProvider{Provider: s.provider} + + s.providerFactory.EXPECT().ProviderForModel(gomock.Any(), "foo").Return(provider, nil) + + runner := ProviderRunner[BarProvider](s.providerFactory, "foo") + _, err := runner(context.Background()) + c.Assert(err, jc.ErrorIs, errors.NotSupported) +} + +func (s *providerSuite) setupMocks(c *gc.C) *gomock.Controller { + ctrl := gomock.NewController(c) + + s.provider = NewMockProvider(ctrl) + s.providerFactory = NewMockProviderFactory(ctrl) + + return ctrl +} + +type fooProvider struct { + Provider +} + +func (fooProvider) Hello() string { + return "Hello" +} + +type FooProvider interface { + Hello() string +} + +type BarProvider interface { + World() string +} diff --git a/domain/network/service/space.go b/domain/network/service/space.go index a00f1592304..246ad657508 100644 --- a/domain/network/service/space.go +++ b/domain/network/service/space.go @@ -13,6 +13,8 @@ import ( "github.com/juju/names/v5" "github.com/juju/juju/core/network" + "github.com/juju/juju/core/providertracker" + "github.com/juju/juju/environs" "github.com/juju/juju/internal/uuid" ) @@ -136,22 +138,21 @@ func (s *Service) SaveProviderSubnets( continue } fanSubnetID := generateFanSubnetID(subnetNet.String(), subnet.ProviderId.String()) - if overlaySegment != nil { - // Add the fan subnet to the upsert list. - fanSubnetToUpsert := subnet - fanSubnetToUpsert.ProviderId = network.Id(fanSubnetID) - fanSubnetToUpsert.SetFan(fanSubnetToUpsert.CIDR, fan.Overlay.String()) - fanSubnetToUpsert.SpaceID = spaceUUID.String() - - fanInfo := &network.FanCIDRs{ - FanLocalUnderlay: fanSubnetToUpsert.CIDR, - FanOverlay: fan.Overlay.String(), - } - fanSubnetToUpsert.FanInfo = fanInfo - fanSubnetToUpsert.CIDR = overlaySegment.String() - - subnetsToUpsert = append(subnetsToUpsert, fanSubnetToUpsert) + + // Add the fan subnet to the upsert list. + fanSubnetToUpsert := subnet + fanSubnetToUpsert.ProviderId = network.Id(fanSubnetID) + fanSubnetToUpsert.SetFan(fanSubnetToUpsert.CIDR, fan.Overlay.String()) + fanSubnetToUpsert.SpaceID = spaceUUID.String() + + fanInfo := &network.FanCIDRs{ + FanLocalUnderlay: fanSubnetToUpsert.CIDR, + FanOverlay: fan.Overlay.String(), } + fanSubnetToUpsert.FanInfo = fanInfo + fanSubnetToUpsert.CIDR = overlaySegment.String() + + subnetsToUpsert = append(subnetsToUpsert, fanSubnetToUpsert) } } @@ -169,3 +170,26 @@ func generateFanSubnetID(subnetNetwork, providerID string) string { subnetWithDashes := strings.Replace(strings.Replace(subnetNetwork, ".", "-", -1), "/", "-", -1) return fmt.Sprintf("%s-%s-%s", providerID, network.InFan, subnetWithDashes) } + +// Provider is the interface that the network service requires to be able to +// interact with the underlying provider. +type Provider interface { + environs.Networking +} + +// ProviderService provides the API for working with network spaces. +type ProviderService struct { + Service + provider func(context.Context) (Provider, error) +} + +// NewProviderService returns a new service reference wrapping the input state. +func NewProviderService(st State, provider providertracker.ProviderGetter[Provider], logger Logger) *ProviderService { + return &ProviderService{ + Service: Service{ + st: st, + logger: logger, + }, + provider: provider, + } +} diff --git a/domain/servicefactory/model.go b/domain/servicefactory/model.go index 002515201a3..1971219968a 100644 --- a/domain/servicefactory/model.go +++ b/domain/servicefactory/model.go @@ -5,6 +5,8 @@ package servicefactory import ( "github.com/juju/juju/core/changestream" + "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" "github.com/juju/juju/domain" annotationService "github.com/juju/juju/domain/annotation/service" annotationState "github.com/juju/juju/domain/annotation/state" @@ -33,19 +35,28 @@ import ( // ModelFactory provides access to the services required by the apiserver. type ModelFactory struct { - logger Logger - modelDB changestream.WatchableDBFactory + logger Logger + modelUUID model.UUID + modelDB changestream.WatchableDBFactory + providerFactory providertracker.ProviderFactory + brokerFactory providertracker.ProviderFactory } // NewModelFactory returns a new registry which uses the provided modelDB // function to obtain a model database. func NewModelFactory( + modelUUID model.UUID, modelDB changestream.WatchableDBFactory, + providerFactory providertracker.ProviderFactory, + brokerFactory providertracker.ProviderFactory, logger Logger, ) *ModelFactory { return &ModelFactory{ - logger: logger, - modelDB: modelDB, + logger: logger, + modelUUID: modelUUID, + modelDB: modelDB, + providerFactory: providerFactory, + brokerFactory: brokerFactory, } } @@ -110,9 +121,10 @@ func (s *ModelFactory) Unit() *unitservice.Service { } // Network returns the model's network service. -func (s *ModelFactory) Network() *networkservice.Service { - return networkservice.NewService( +func (s *ModelFactory) Network() *networkservice.ProviderService { + return networkservice.NewProviderService( networkstate.NewState(changestream.NewTxnRunnerFactory(s.modelDB)), + providertracker.ProviderRunner[networkservice.Provider](s.providerFactory, s.modelUUID.String()), s.logger.Child("network"), ) } diff --git a/domain/servicefactory/service.go b/domain/servicefactory/service.go index 8a189c7436f..6d885e71388 100644 --- a/domain/servicefactory/service.go +++ b/domain/servicefactory/service.go @@ -6,6 +6,8 @@ package servicefactory import ( "github.com/juju/juju/core/changestream" "github.com/juju/juju/core/database" + "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" ) // ServiceFactory provides access to the services required by the apiserver. @@ -18,15 +20,21 @@ type ServiceFactory struct { // get new services from. func NewServiceFactory( controllerDB changestream.WatchableDBFactory, + modelUUID model.UUID, modelDB changestream.WatchableDBFactory, deleterDB database.DBDeleter, + providerTracker providertracker.ProviderFactory, + brokerTracker providertracker.ProviderFactory, logger Logger, ) *ServiceFactory { controllerFactory := NewControllerFactory(controllerDB, deleterDB, logger) return &ServiceFactory{ ControllerFactory: controllerFactory, ModelFactory: NewModelFactory( + modelUUID, modelDB, + providerTracker, + brokerTracker, logger, ), } diff --git a/domain/servicefactory/testing/service.go b/domain/servicefactory/testing/service.go index 3929b71d94e..7140233d733 100644 --- a/domain/servicefactory/testing/service.go +++ b/domain/servicefactory/testing/service.go @@ -122,7 +122,7 @@ func (s *TestingServiceFactory) Machine() *machineservice.Service { } // Network returns the network service. -func (s *TestingServiceFactory) Network() *networkservice.Service { +func (s *TestingServiceFactory) Network() *networkservice.ProviderService { return nil } diff --git a/domain/servicefactory/testing/suite.go b/domain/servicefactory/testing/suite.go index fd9f12bb783..e87bc16cd5c 100644 --- a/domain/servicefactory/testing/suite.go +++ b/domain/servicefactory/testing/suite.go @@ -15,6 +15,7 @@ import ( coremodel "github.com/juju/juju/core/model" modeltesting "github.com/juju/juju/core/model/testing" "github.com/juju/juju/core/permission" + "github.com/juju/juju/core/providertracker" coreuser "github.com/juju/juju/core/user" userbootstrap "github.com/juju/juju/domain/access/bootstrap" cloudbootstrap "github.com/juju/juju/domain/cloud/bootstrap" @@ -54,6 +55,12 @@ type ServiceFactorySuite struct { // DefaultModelUUID is the unique id for the default model. If not set // will be set during test set up. DefaultModelUUID coremodel.UUID + + // ProviderTracker is the provider tracker to use in the service factory. + ProviderTracker providertracker.ProviderFactory + + // BrokerTracker is the broker tracker to use in the service factory. + BrokerTracker providertracker.ProviderFactory } type stubDBDeleter struct { @@ -170,8 +177,11 @@ func (s *ServiceFactorySuite) ServiceFactoryGetter(c *gc.C) ServiceFactoryGetter return func(modelUUID string) servicefactory.ServiceFactory { return domainservicefactory.NewServiceFactory( databasetesting.ConstFactory(s.TxnRunner()), + coremodel.UUID(modelUUID), databasetesting.ConstFactory(s.ModelTxnRunner(c, modelUUID)), stubDBDeleter{DB: s.DB()}, + s.ProviderTracker, + s.BrokerTracker, NewCheckLogger(c), ) } diff --git a/internal/migration/servicefactory_mock_test.go b/internal/migration/servicefactory_mock_test.go index 21eda7e8704..52fc5906c50 100644 --- a/internal/migration/servicefactory_mock_test.go +++ b/internal/migration/servicefactory_mock_test.go @@ -338,10 +338,10 @@ func (mr *MockServiceFactoryMockRecorder) ModelInfo() *gomock.Call { } // Network mocks base method. -func (m *MockServiceFactory) Network() *service14.Service { +func (m *MockServiceFactory) Network() *service14.ProviderService { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") - ret0, _ := ret[0].(*service14.Service) + ret0, _ := ret[0].(*service14.ProviderService) return ret0 } diff --git a/internal/servicefactory/interface.go b/internal/servicefactory/interface.go index f098657711a..8f8889bb43e 100644 --- a/internal/servicefactory/interface.go +++ b/internal/servicefactory/interface.go @@ -81,7 +81,7 @@ type ModelServiceFactory interface { // Unit returns the machine service. Unit() *unitservice.Service // Network returns the space service. - Network() *networkservice.Service + Network() *networkservice.ProviderService // Annotation returns the annotation service. Annotation() *annotationService.Service // Storage returns the storage service. diff --git a/internal/worker/providertracker/manifold.go b/internal/worker/providertracker/manifold.go index 9a530fe2dee..8502304f7cd 100644 --- a/internal/worker/providertracker/manifold.go +++ b/internal/worker/providertracker/manifold.go @@ -15,8 +15,10 @@ import ( "github.com/juju/juju/caas" coredependency "github.com/juju/juju/core/dependency" coremodel "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" "github.com/juju/juju/environs" environscloudspec "github.com/juju/juju/environs/cloudspec" + "github.com/juju/juju/internal/servicefactory" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/worker/modelworkermanager" ) @@ -24,7 +26,7 @@ import ( // Provider is an interface that represents a provider, this can either be // a CAAS broker or IAAS provider. type Provider interface { - environs.Configer + providertracker.Provider } // ProviderConfigGetter is an interface that extends @@ -155,53 +157,6 @@ func manifold[T Provider](trackerType TrackerType, config ManifoldConfig[T]) dep } } -// GetProviderServiceFactoryGetter is a helper function that gets a service from the -// manifold. -func GetProviderServiceFactoryGetter(getter dependency.Getter, name string) (ServiceFactoryGetter, error) { - return coredependency.GetDependencyByName(getter, name, func(factory modelworkermanager.ProviderServiceFactoryGetter) ServiceFactoryGetter { - return serviceFactoryGetter{ - factory: factory, - } - }) -} - -// serviceFactoryGetter is a simple implementation of ServiceFactoryGetter. -type serviceFactoryGetter struct { - factory modelworkermanager.ProviderServiceFactoryGetter -} - -// FactoryForModel returns a ProviderServiceFactory for the given model. -func (g serviceFactoryGetter) FactoryForModel(modelUUID string) ServiceFactory { - return serviceFactory{ - factory: g.factory.FactoryForModel(modelUUID), - } -} - -// serviceFactory is a simple implementation of ServiceFactory. -type serviceFactory struct { - factory modelworkermanager.ProviderServiceFactory -} - -// Model returns the provider model service. -func (f serviceFactory) Model() ModelService { - return f.factory.Model() -} - -// Cloud returns the provider cloud service. -func (f serviceFactory) Cloud() CloudService { - return f.factory.Cloud() -} - -// Config returns the provider config service. -func (f serviceFactory) Config() ConfigService { - return f.factory.Config() -} - -// Credential returns the provider credential service. -func (f serviceFactory) Credential() CredentialService { - return f.factory.Credential() -} - // IAASGetProvider creates a new provider from the given args. func IAASGetProvider(newProvider ProviderFunc[environs.Environ]) func(ctx context.Context, getter ProviderConfigGetter) (environs.Environ, environscloudspec.CloudSpec, error) { return func(ctx context.Context, getter ProviderConfigGetter) (environs.Environ, environscloudspec.CloudSpec, error) { @@ -244,7 +199,6 @@ func CAASGetProvider(newProvider ProviderFunc[caas.Broker]) func(ctx context.Con } func manifoldOutput[T Provider](in worker.Worker, out any) error { - // TODO (stickupkid): Handle non-singular provider interfaces. // In order to switch on the type of the provider, we need to use a type // assertion to get the underlying value. @@ -270,40 +224,165 @@ func manifoldOutput[T Provider](in worker.Worker, out any) error { // iaasOutput extracts an environs.Environ resource from a *Worker. func iaasOutput(in *providerWorker[environs.Environ], out any) error { - provider, err := in.Provider() - if err != nil { - return errors.Trace(err) - } - + var err error switch result := out.(type) { + case *providertracker.ProviderFactory: + *result = coerceProviderType[environs.Environ]{Provider: in} + case *providertracker.GenericProviderFactory[environs.Environ]: + *result = in case *environs.Environ: - *result = provider + *result, err = in.Provider() case *environs.CloudDestroyer: - *result = provider + *result, err = in.Provider() case *storage.ProviderRegistry: - *result = provider + *result, err = in.Provider() default: - return errors.Errorf("expected *environs.Environ, *storage.ProviderRegistry, or *environs.CloudDestroyer, got %T", out) + err = errors.Errorf("expected *environs.Environ, *storage.ProviderRegistry, or *environs.CloudDestroyer, got %T", out) } - return nil + return errors.Trace(err) } // caasOutput extracts a caas.Broker resource from a *Worker. func caasOutput(in *providerWorker[caas.Broker], out any) error { - provider, err := in.Provider() - if err != nil { - return errors.Trace(err) - } - + var err error switch result := out.(type) { + case *providertracker.ProviderFactory: + *result = coerceProviderType[caas.Broker]{Provider: in} + case *providertracker.GenericProviderFactory[caas.Broker]: + *result = in case *caas.Broker: - *result = provider + *result, err = in.Provider() case *environs.CloudDestroyer: - *result = provider + *result, err = in.Provider() case *storage.ProviderRegistry: - *result = provider + *result, err = in.Provider() default: return errors.Errorf("expected *caas.Broker, *storage.ProviderRegistry or *environs.CloudDestroyer, got %T", out) } - return nil + return errors.Trace(err) +} + +// coerceProviderType forces the type of the provider type to be cast to the +// expected type. +type coerceProviderType[T Provider] struct { + Provider *providerWorker[T] +} + +// ProviderForModel returns the encapsulated provider for a given model +// namespace. It will continue to be updated in the background for as long as +// the Worker continues to run. If the worker is not a singular worker, then an +// error will be returned. +func (w coerceProviderType[T]) ProviderForModel(ctx context.Context, namespace string) (providertracker.Provider, error) { + provider, err := w.Provider.ProviderForModel(ctx, namespace) + if err != nil { + return nil, errors.Trace(err) + } + if p, ok := any(provider).(providertracker.Provider); ok { + return p, nil + } + return nil, errors.Errorf("expected providertracker.Provider, got %T", provider) +} + +// GetProviderServiceFactoryGetter is a helper function that gets a service from the +// manifold. +// This returns a ServiceFactoryGetter that is constructed directly from the +// service factory. +func GetProviderServiceFactoryGetter(getter dependency.Getter, name string) (ServiceFactoryGetter, error) { + return coredependency.GetDependencyByName(getter, name, func(factory servicefactory.ProviderServiceFactoryGetter) ServiceFactoryGetter { + return serviceFactoryGetter{ + factory: factory, + } + }) +} + +// serviceFactoryGetter is a simple implementation of ServiceFactoryGetter. +type serviceFactoryGetter struct { + factory servicefactory.ProviderServiceFactoryGetter +} + +// FactoryForModel returns a ProviderServiceFactory for the given model. +func (g serviceFactoryGetter) FactoryForModel(modelUUID string) ServiceFactory { + return serviceFactory{ + factory: g.factory.FactoryForModel(modelUUID), + } +} + +// serviceFactory is a simple implementation of ServiceFactory. +type serviceFactory struct { + factory servicefactory.ProviderServiceFactory +} + +// Model returns the provider model service. +func (f serviceFactory) Model() ModelService { + return f.factory.Model() +} + +// Cloud returns the provider cloud service. +func (f serviceFactory) Cloud() CloudService { + return f.factory.Cloud() +} + +// Config returns the provider config service. +func (f serviceFactory) Config() ConfigService { + return f.factory.Config() +} + +// Credential returns the provider credential service. +func (f serviceFactory) Credential() CredentialService { + return f.factory.Credential() +} + +// GetModelProviderServiceFactoryGetter is a helper function that gets a service +// from the manifold. +// This is a model specific version of GetProviderServiceFactoryGetter. As +// the service factory is plucked out of the provider service factory already, +// we have to use a different getter. Ideally we would use the servicefactory +// directly, but that's plumbed through the model worker manager config. +// We can't use generics here, as although the types are the same, the nested +// interfaces are not (invariance). +// If the provider service factory returned interfaces, we could just point the +// getter at the service factory directly. +func GetModelProviderServiceFactoryGetter(getter dependency.Getter, name string) (ServiceFactoryGetter, error) { + return coredependency.GetDependencyByName(getter, name, func(factory modelworkermanager.ProviderServiceFactoryGetter) ServiceFactoryGetter { + return modelServiceFactoryGetter{ + factory: factory, + } + }) +} + +// modelServiceFactoryGetter is a simple implementation of ServiceFactoryGetter. +type modelServiceFactoryGetter struct { + factory modelworkermanager.ProviderServiceFactoryGetter +} + +// FactoryForModel returns a ProviderServiceFactory for the given model. +func (g modelServiceFactoryGetter) FactoryForModel(modelUUID string) ServiceFactory { + return modelServiceFactory{ + factory: g.factory.FactoryForModel(modelUUID), + } +} + +// modelServiceFactory is a simple implementation of ServiceFactory. +type modelServiceFactory struct { + factory modelworkermanager.ProviderServiceFactory +} + +// Model returns the provider model service. +func (f modelServiceFactory) Model() ModelService { + return f.factory.Model() +} + +// Cloud returns the provider cloud service. +func (f modelServiceFactory) Cloud() CloudService { + return f.factory.Cloud() +} + +// Config returns the provider config service. +func (f modelServiceFactory) Config() ConfigService { + return f.factory.Config() +} + +// Credential returns the provider credential service. +func (f modelServiceFactory) Credential() CredentialService { + return f.factory.Credential() } diff --git a/internal/worker/providertracker/providerworker.go b/internal/worker/providertracker/providerworker.go index 93524bc6259..0c71f0b4aad 100644 --- a/internal/worker/providertracker/providerworker.go +++ b/internal/worker/providertracker/providerworker.go @@ -106,7 +106,7 @@ func newWorker[T Provider](config Config[T], internalStates chan string) (*provi return w, nil } -// Provider returns the encapsulated Environ. It will continue to be updated in +// Provider returns the encapsulated provider. It will continue to be updated in // the background for as long as the Worker continues to run. If the worker // is not a singular worker, then an error will be returned. func (w *providerWorker[T]) Provider() (res T, err error) { @@ -137,7 +137,7 @@ func (w *providerWorker[T]) Provider() (res T, err error) { } } -// ProviderForModel returns the encapsulated Environ for a given model +// ProviderForModel returns the encapsulated provider for a given model // namespace. It will continue to be updated in the background for as long as // the Worker continues to run. If the worker is not a singular worker, then an // error will be returned. diff --git a/internal/worker/servicefactory/manifold.go b/internal/worker/servicefactory/manifold.go index dea294e146a..2ed928c2fb8 100644 --- a/internal/worker/servicefactory/manifold.go +++ b/internal/worker/servicefactory/manifold.go @@ -13,6 +13,7 @@ import ( "github.com/juju/juju/core/changestream" coredatabase "github.com/juju/juju/core/database" coremodel "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" domainservicefactory "github.com/juju/juju/domain/servicefactory" "github.com/juju/juju/internal/servicefactory" "github.com/juju/juju/internal/worker/common" @@ -31,6 +32,8 @@ type Logger interface { type ManifoldConfig struct { DBAccessorName string ChangeStreamName string + ProviderFactoryName string + BrokerFactoryName string Logger Logger NewWorker func(Config) (worker.Worker, error) NewServiceFactoryGetter ServiceFactoryGetterFn @@ -44,6 +47,8 @@ type ServiceFactoryGetterFn func( changestream.WatchableDBGetter, Logger, ModelServiceFactoryFn, + providertracker.ProviderFactory, + providertracker.ProviderFactory, ) servicefactory.ServiceFactoryGetter // ControllerServiceFactoryFn is a function that returns a controller service @@ -58,6 +63,8 @@ type ControllerServiceFactoryFn func( type ModelServiceFactoryFn func( coremodel.UUID, changestream.WatchableDBGetter, + providertracker.ProviderFactory, + providertracker.ProviderFactory, Logger, ) servicefactory.ModelServiceFactory @@ -69,6 +76,12 @@ func (config ManifoldConfig) Validate() error { if config.ChangeStreamName == "" { return errors.NotValidf("empty ChangeStreamName") } + if config.ProviderFactoryName == "" { + return errors.NotValidf("empty ProviderFactoryName") + } + if config.BrokerFactoryName == "" { + return errors.NotValidf("empty BrokerFactoryName") + } if config.NewWorker == nil { return errors.NotValidf("nil NewWorker") } @@ -87,14 +100,15 @@ func (config ManifoldConfig) Validate() error { return nil } -// Manifold returns a dependency.Manifold that will run an apiserver -// worker. The manifold outputs an *apiserverhttp.Mux, for other workers -// to register handlers against. +// Manifold returns a dependency.Manifold that will run a service factory +// worker. func Manifold(config ManifoldConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{ config.ChangeStreamName, config.DBAccessorName, + config.ProviderFactoryName, + config.BrokerFactoryName, }, Start: config.start, Output: config.output, @@ -117,9 +131,21 @@ func (config ManifoldConfig) start(context context.Context, getter dependency.Ge return nil, errors.Trace(err) } + var providerFactory providertracker.ProviderFactory + if err := getter.Get(config.ProviderFactoryName, &providerFactory); err != nil { + return nil, errors.Trace(err) + } + + var brokerFactory providertracker.ProviderFactory + if err := getter.Get(config.BrokerFactoryName, &brokerFactory); err != nil { + return nil, errors.Trace(err) + } + return config.NewWorker(Config{ DBGetter: dbGetter, DBDeleter: dbDeleter, + ProviderFactory: providerFactory, + BrokerFactory: brokerFactory, Logger: config.Logger, NewServiceFactoryGetter: config.NewServiceFactoryGetter, NewControllerServiceFactory: config.NewControllerServiceFactory, @@ -164,14 +190,39 @@ func NewControllerServiceFactory( ) } +// NewProviderTrackerModelServiceFactory returns a new model service factory +// with a provider tracker. +func NewProviderTrackerModelServiceFactory( + modelUUID coremodel.UUID, + dbGetter changestream.WatchableDBGetter, + providerFactory providertracker.ProviderFactory, + brokerFactory providertracker.ProviderFactory, + logger Logger, +) servicefactory.ModelServiceFactory { + return domainservicefactory.NewModelFactory( + modelUUID, + changestream.NewWatchableDBFactoryForNamespace(dbGetter.GetWatchableDB, modelUUID.String()), + providerFactory, + brokerFactory, + serviceFactoryLogger{ + Logger: logger, + }, + ) +} + // NewModelServiceFactory returns a new model service factory. +// This creates a model service factory without a provider tracker. The provider +// tracker will return not supported errors for all methods. func NewModelServiceFactory( modelUUID coremodel.UUID, dbGetter changestream.WatchableDBGetter, logger Logger, ) servicefactory.ModelServiceFactory { return domainservicefactory.NewModelFactory( + modelUUID, changestream.NewWatchableDBFactoryForNamespace(dbGetter.GetWatchableDB, modelUUID.String()), + NoopProviderFactory{}, + NoopProviderFactory{}, serviceFactoryLogger{ Logger: logger, }, @@ -184,11 +235,21 @@ func NewServiceFactoryGetter( dbGetter changestream.WatchableDBGetter, logger Logger, newModelServiceFactory ModelServiceFactoryFn, + providerFactory providertracker.ProviderFactory, + brokerFactory providertracker.ProviderFactory, ) servicefactory.ServiceFactoryGetter { return &serviceFactoryGetter{ ctrlFactory: ctrlFactory, dbGetter: dbGetter, logger: logger, newModelServiceFactory: newModelServiceFactory, + providerFactory: providerFactory, + brokerFactory: brokerFactory, } } + +type NoopProviderFactory struct{} + +func (NoopProviderFactory) ProviderForModel(ctx context.Context, namespace string) (providertracker.Provider, error) { + return nil, errors.NotSupportedf("provider") +} diff --git a/internal/worker/servicefactory/manifold_test.go b/internal/worker/servicefactory/manifold_test.go index 036ba3abf3e..2448f4313a0 100644 --- a/internal/worker/servicefactory/manifold_test.go +++ b/internal/worker/servicefactory/manifold_test.go @@ -16,6 +16,7 @@ import ( "github.com/juju/juju/core/changestream" coredatabase "github.com/juju/juju/core/database" coremodel "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" "github.com/juju/juju/internal/servicefactory" ) @@ -39,6 +40,14 @@ func (s *manifoldSuite) TestValidateConfig(c *gc.C) { cfg.DBAccessorName = "" c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) + cfg = s.getConfig() + cfg.ProviderFactoryName = "" + c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) + + cfg = s.getConfig() + cfg.BrokerFactoryName = "" + c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) + cfg = s.getConfig() cfg.ChangeStreamName = "" c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) @@ -62,18 +71,22 @@ func (s *manifoldSuite) TestValidateConfig(c *gc.C) { func (s *manifoldSuite) TestStart(c *gc.C) { getter := map[string]any{ - "dbaccessor": s.dbDeleter, - "changestream": s.dbGetter, + "dbaccessor": s.dbDeleter, + "changestream": s.dbGetter, + "providerfactory": s.providerFactory, + "brokerfactory": s.providerFactory, } manifold := Manifold(ManifoldConfig{ DBAccessorName: "dbaccessor", ChangeStreamName: "changestream", + ProviderFactoryName: "providerfactory", + BrokerFactoryName: "brokerfactory", Logger: s.logger, NewWorker: NewWorker, NewServiceFactoryGetter: NewServiceFactoryGetter, NewControllerServiceFactory: NewControllerServiceFactory, - NewModelServiceFactory: NewModelServiceFactory, + NewModelServiceFactory: NewProviderTrackerModelServiceFactory, }) w, err := manifold.Start(context.Background(), dt.StubGetter(getter)) c.Assert(err, jc.ErrorIsNil) @@ -87,9 +100,11 @@ func (s *manifoldSuite) TestOutputControllerServiceFactory(c *gc.C) { DBDeleter: s.dbDeleter, DBGetter: s.dbGetter, Logger: s.logger, + ProviderFactory: s.providerFactory, + BrokerFactory: s.providerFactory, NewServiceFactoryGetter: NewServiceFactoryGetter, NewControllerServiceFactory: NewControllerServiceFactory, - NewModelServiceFactory: NewModelServiceFactory, + NewModelServiceFactory: NewProviderTrackerModelServiceFactory, }) c.Assert(err, jc.ErrorIsNil) defer workertest.DirtyKill(c, w) @@ -106,9 +121,11 @@ func (s *manifoldSuite) TestOutputServiceFactoryGetter(c *gc.C) { DBDeleter: s.dbDeleter, DBGetter: s.dbGetter, Logger: s.logger, + ProviderFactory: s.providerFactory, + BrokerFactory: s.providerFactory, NewServiceFactoryGetter: NewServiceFactoryGetter, NewControllerServiceFactory: NewControllerServiceFactory, - NewModelServiceFactory: NewModelServiceFactory, + NewModelServiceFactory: NewProviderTrackerModelServiceFactory, }) c.Assert(err, jc.ErrorIsNil) defer workertest.DirtyKill(c, w) @@ -125,9 +142,11 @@ func (s *manifoldSuite) TestOutputInvalid(c *gc.C) { DBDeleter: s.dbDeleter, DBGetter: s.dbGetter, Logger: s.logger, + ProviderFactory: s.providerFactory, + BrokerFactory: s.providerFactory, NewServiceFactoryGetter: NewServiceFactoryGetter, NewControllerServiceFactory: NewControllerServiceFactory, - NewModelServiceFactory: NewModelServiceFactory, + NewModelServiceFactory: NewProviderTrackerModelServiceFactory, }) c.Assert(err, jc.ErrorIsNil) defer workertest.DirtyKill(c, w) @@ -155,7 +174,7 @@ func (s *manifoldSuite) TestNewModelServiceFactory(c *gc.C) { func (s *manifoldSuite) TestNewServiceFactoryGetter(c *gc.C) { ctrlFactory := NewControllerServiceFactory(s.dbGetter, s.dbDeleter, s.logger) - factory := NewServiceFactoryGetter(ctrlFactory, s.dbGetter, s.logger, NewModelServiceFactory) + factory := NewServiceFactoryGetter(ctrlFactory, s.dbGetter, s.logger, NewProviderTrackerModelServiceFactory, nil, nil) c.Assert(factory, gc.NotNil) modelFactory := factory.FactoryForModel("model") @@ -164,19 +183,21 @@ func (s *manifoldSuite) TestNewServiceFactoryGetter(c *gc.C) { func (s *manifoldSuite) getConfig() ManifoldConfig { return ManifoldConfig{ - DBAccessorName: "dbaccessor", - ChangeStreamName: "changestream", - Logger: s.logger, + DBAccessorName: "dbaccessor", + ChangeStreamName: "changestream", + ProviderFactoryName: "providerfactory", + BrokerFactoryName: "brokerfactory", + Logger: s.logger, NewWorker: func(Config) (worker.Worker, error) { return nil, nil }, - NewServiceFactoryGetter: func(servicefactory.ControllerServiceFactory, changestream.WatchableDBGetter, Logger, ModelServiceFactoryFn) servicefactory.ServiceFactoryGetter { + NewServiceFactoryGetter: func(servicefactory.ControllerServiceFactory, changestream.WatchableDBGetter, Logger, ModelServiceFactoryFn, providertracker.ProviderFactory, providertracker.ProviderFactory) servicefactory.ServiceFactoryGetter { return nil }, NewControllerServiceFactory: func(changestream.WatchableDBGetter, coredatabase.DBDeleter, Logger) servicefactory.ControllerServiceFactory { return nil }, - NewModelServiceFactory: func(coremodel.UUID, changestream.WatchableDBGetter, Logger) servicefactory.ModelServiceFactory { + NewModelServiceFactory: func(coremodel.UUID, changestream.WatchableDBGetter, providertracker.ProviderFactory, providertracker.ProviderFactory, Logger) servicefactory.ModelServiceFactory { return nil }, } diff --git a/internal/worker/servicefactory/package_test.go b/internal/worker/servicefactory/package_test.go index 386d8414c8b..3392e2ba43d 100644 --- a/internal/worker/servicefactory/package_test.go +++ b/internal/worker/servicefactory/package_test.go @@ -16,6 +16,7 @@ import ( //go:generate go run go.uber.org/mock/mockgen -package servicefactory -destination servicefactory_logger_mock_test.go github.com/juju/juju/internal/worker/servicefactory Logger //go:generate go run go.uber.org/mock/mockgen -package servicefactory -destination database_mock_test.go github.com/juju/juju/core/database DBDeleter //go:generate go run go.uber.org/mock/mockgen -package servicefactory -destination changestream_mock_test.go github.com/juju/juju/core/changestream WatchableDBGetter +//go:generate go run go.uber.org/mock/mockgen -package servicefactory -destination providertracker_mock_test.go github.com/juju/juju/core/providertracker Provider,ProviderFactory func TestPackage(t *testing.T) { gc.TestingT(t) @@ -31,6 +32,9 @@ type baseSuite struct { serviceFactoryGetter *MockServiceFactoryGetter controllerServiceFactory *MockControllerServiceFactory modelServiceFactory *MockModelServiceFactory + + provider *MockProvider + providerFactory *MockProviderFactory } func (s *baseSuite) setupMocks(c *gc.C) *gomock.Controller { @@ -44,5 +48,8 @@ func (s *baseSuite) setupMocks(c *gc.C) *gomock.Controller { s.controllerServiceFactory = NewMockControllerServiceFactory(ctrl) s.modelServiceFactory = NewMockModelServiceFactory(ctrl) + s.provider = NewMockProvider(ctrl) + s.providerFactory = NewMockProviderFactory(ctrl) + return ctrl } diff --git a/internal/worker/servicefactory/providertracker_mock_test.go b/internal/worker/servicefactory/providertracker_mock_test.go new file mode 100644 index 00000000000..35a9dc7fa7a --- /dev/null +++ b/internal/worker/servicefactory/providertracker_mock_test.go @@ -0,0 +1,257 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/core/providertracker (interfaces: Provider,ProviderFactory) +// +// Generated by this command: +// +// mockgen -package servicefactory -destination providertracker_mock_test.go github.com/juju/juju/core/providertracker Provider,ProviderFactory +// + +// Package servicefactory is a generated GoMock package. +package servicefactory + +import ( + context "context" + reflect "reflect" + + constraints "github.com/juju/juju/core/constraints" + providertracker "github.com/juju/juju/core/providertracker" + environs "github.com/juju/juju/environs" + config "github.com/juju/juju/environs/config" + envcontext "github.com/juju/juju/environs/envcontext" + storage "github.com/juju/juju/internal/storage" + version "github.com/juju/version/v2" + gomock "go.uber.org/mock/gomock" +) + +// MockProvider is a mock of Provider interface. +type MockProvider struct { + ctrl *gomock.Controller + recorder *MockProviderMockRecorder +} + +// MockProviderMockRecorder is the mock recorder for MockProvider. +type MockProviderMockRecorder struct { + mock *MockProvider +} + +// NewMockProvider creates a new mock instance. +func NewMockProvider(ctrl *gomock.Controller) *MockProvider { + mock := &MockProvider{ctrl: ctrl} + mock.recorder = &MockProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProvider) EXPECT() *MockProviderMockRecorder { + return m.recorder +} + +// AdoptResources mocks base method. +func (m *MockProvider) AdoptResources(arg0 envcontext.ProviderCallContext, arg1 string, arg2 version.Number) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AdoptResources", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// AdoptResources indicates an expected call of AdoptResources. +func (mr *MockProviderMockRecorder) AdoptResources(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdoptResources", reflect.TypeOf((*MockProvider)(nil).AdoptResources), arg0, arg1, arg2) +} + +// Bootstrap mocks base method. +func (m *MockProvider) Bootstrap(arg0 environs.BootstrapContext, arg1 envcontext.ProviderCallContext, arg2 environs.BootstrapParams) (*environs.BootstrapResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Bootstrap", arg0, arg1, arg2) + ret0, _ := ret[0].(*environs.BootstrapResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Bootstrap indicates an expected call of Bootstrap. +func (mr *MockProviderMockRecorder) Bootstrap(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bootstrap", reflect.TypeOf((*MockProvider)(nil).Bootstrap), arg0, arg1, arg2) +} + +// Config mocks base method. +func (m *MockProvider) Config() *config.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Config") + ret0, _ := ret[0].(*config.Config) + return ret0 +} + +// Config indicates an expected call of Config. +func (mr *MockProviderMockRecorder) Config() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Config", reflect.TypeOf((*MockProvider)(nil).Config)) +} + +// ConstraintsValidator mocks base method. +func (m *MockProvider) ConstraintsValidator(arg0 envcontext.ProviderCallContext) (constraints.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConstraintsValidator", arg0) + ret0, _ := ret[0].(constraints.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConstraintsValidator indicates an expected call of ConstraintsValidator. +func (mr *MockProviderMockRecorder) ConstraintsValidator(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConstraintsValidator", reflect.TypeOf((*MockProvider)(nil).ConstraintsValidator), arg0) +} + +// Create mocks base method. +func (m *MockProvider) Create(arg0 envcontext.ProviderCallContext, arg1 environs.CreateParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockProviderMockRecorder) Create(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockProvider)(nil).Create), arg0, arg1) +} + +// Destroy mocks base method. +func (m *MockProvider) Destroy(arg0 envcontext.ProviderCallContext) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Destroy", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Destroy indicates an expected call of Destroy. +func (mr *MockProviderMockRecorder) Destroy(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockProvider)(nil).Destroy), arg0) +} + +// DestroyController mocks base method. +func (m *MockProvider) DestroyController(arg0 envcontext.ProviderCallContext, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DestroyController", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DestroyController indicates an expected call of DestroyController. +func (mr *MockProviderMockRecorder) DestroyController(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestroyController", reflect.TypeOf((*MockProvider)(nil).DestroyController), arg0, arg1) +} + +// PrecheckInstance mocks base method. +func (m *MockProvider) PrecheckInstance(arg0 envcontext.ProviderCallContext, arg1 environs.PrecheckInstanceParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrecheckInstance", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrecheckInstance indicates an expected call of PrecheckInstance. +func (mr *MockProviderMockRecorder) PrecheckInstance(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrecheckInstance", reflect.TypeOf((*MockProvider)(nil).PrecheckInstance), arg0, arg1) +} + +// PrepareForBootstrap mocks base method. +func (m *MockProvider) PrepareForBootstrap(arg0 environs.BootstrapContext, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrepareForBootstrap", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrepareForBootstrap indicates an expected call of PrepareForBootstrap. +func (mr *MockProviderMockRecorder) PrepareForBootstrap(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareForBootstrap", reflect.TypeOf((*MockProvider)(nil).PrepareForBootstrap), arg0, arg1) +} + +// SetConfig mocks base method. +func (m *MockProvider) SetConfig(arg0 *config.Config) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetConfig", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetConfig indicates an expected call of SetConfig. +func (mr *MockProviderMockRecorder) SetConfig(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfig", reflect.TypeOf((*MockProvider)(nil).SetConfig), arg0) +} + +// StorageProvider mocks base method. +func (m *MockProvider) StorageProvider(arg0 storage.ProviderType) (storage.Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageProvider", arg0) + ret0, _ := ret[0].(storage.Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StorageProvider indicates an expected call of StorageProvider. +func (mr *MockProviderMockRecorder) StorageProvider(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageProvider", reflect.TypeOf((*MockProvider)(nil).StorageProvider), arg0) +} + +// StorageProviderTypes mocks base method. +func (m *MockProvider) StorageProviderTypes() ([]storage.ProviderType, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageProviderTypes") + ret0, _ := ret[0].([]storage.ProviderType) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StorageProviderTypes indicates an expected call of StorageProviderTypes. +func (mr *MockProviderMockRecorder) StorageProviderTypes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageProviderTypes", reflect.TypeOf((*MockProvider)(nil).StorageProviderTypes)) +} + +// MockProviderFactory is a mock of ProviderFactory interface. +type MockProviderFactory struct { + ctrl *gomock.Controller + recorder *MockProviderFactoryMockRecorder +} + +// MockProviderFactoryMockRecorder is the mock recorder for MockProviderFactory. +type MockProviderFactoryMockRecorder struct { + mock *MockProviderFactory +} + +// NewMockProviderFactory creates a new mock instance. +func NewMockProviderFactory(ctrl *gomock.Controller) *MockProviderFactory { + mock := &MockProviderFactory{ctrl: ctrl} + mock.recorder = &MockProviderFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProviderFactory) EXPECT() *MockProviderFactoryMockRecorder { + return m.recorder +} + +// ProviderForModel mocks base method. +func (m *MockProviderFactory) ProviderForModel(arg0 context.Context, arg1 string) (providertracker.Provider, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProviderForModel", arg0, arg1) + ret0, _ := ret[0].(providertracker.Provider) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProviderForModel indicates an expected call of ProviderForModel. +func (mr *MockProviderFactoryMockRecorder) ProviderForModel(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProviderForModel", reflect.TypeOf((*MockProviderFactory)(nil).ProviderForModel), arg0, arg1) +} diff --git a/internal/worker/servicefactory/servicefactory_mock_test.go b/internal/worker/servicefactory/servicefactory_mock_test.go index 155369c850a..f08f8e7f260 100644 --- a/internal/worker/servicefactory/servicefactory_mock_test.go +++ b/internal/worker/servicefactory/servicefactory_mock_test.go @@ -352,10 +352,10 @@ func (mr *MockModelServiceFactoryMockRecorder) ModelInfo() *gomock.Call { } // Network mocks base method. -func (m *MockModelServiceFactory) Network() *service14.Service { +func (m *MockModelServiceFactory) Network() *service14.ProviderService { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") - ret0, _ := ret[0].(*service14.Service) + ret0, _ := ret[0].(*service14.ProviderService) return ret0 } @@ -683,10 +683,10 @@ func (mr *MockServiceFactoryMockRecorder) ModelInfo() *gomock.Call { } // Network mocks base method. -func (m *MockServiceFactory) Network() *service14.Service { +func (m *MockServiceFactory) Network() *service14.ProviderService { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") - ret0, _ := ret[0].(*service14.Service) + ret0, _ := ret[0].(*service14.ProviderService) return ret0 } diff --git a/internal/worker/servicefactory/worker.go b/internal/worker/servicefactory/worker.go index 6195f34d89c..acc50368182 100644 --- a/internal/worker/servicefactory/worker.go +++ b/internal/worker/servicefactory/worker.go @@ -12,6 +12,7 @@ import ( "github.com/juju/juju/core/changestream" coredatabase "github.com/juju/juju/core/database" coremodel "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" domainservicefactory "github.com/juju/juju/domain/servicefactory" "github.com/juju/juju/internal/servicefactory" ) @@ -24,6 +25,13 @@ type Config struct { // DBGetter supplies WatchableDB implementations by namespace. DBGetter changestream.WatchableDBGetter + // ProviderFactory is used to get provider instances. + ProviderFactory providertracker.ProviderFactory + + // BrokerFactory is used to get broker instances. + BrokerFactory providertracker.ProviderFactory + + // Logger is used to log messages. Logger Logger NewServiceFactoryGetter ServiceFactoryGetterFn @@ -39,6 +47,12 @@ func (config Config) Validate() error { if config.DBGetter == nil { return errors.NotValidf("nil DBGetter") } + if config.ProviderFactory == nil { + return errors.NotValidf("nil ProviderFactory") + } + if config.BrokerFactory == nil { + return errors.NotValidf("nil BrokerFactory") + } if config.Logger == nil { return errors.NotValidf("nil Logger") } @@ -62,8 +76,15 @@ func NewWorker(config Config) (worker.Worker, error) { ctrlFactory := config.NewControllerServiceFactory(config.DBGetter, config.DBDeleter, config.Logger) w := &serviceFactoryWorker{ - ctrlFactory: ctrlFactory, - factoryGetter: config.NewServiceFactoryGetter(ctrlFactory, config.DBGetter, config.Logger, config.NewModelServiceFactory), + ctrlFactory: ctrlFactory, + factoryGetter: config.NewServiceFactoryGetter( + ctrlFactory, + config.DBGetter, + config.Logger, + config.NewModelServiceFactory, + config.ProviderFactory, + config.BrokerFactory, + ), } w.tomb.Go(func() error { <-w.tomb.Dying() @@ -123,6 +144,8 @@ type serviceFactoryGetter struct { dbGetter changestream.WatchableDBGetter logger Logger newModelServiceFactory ModelServiceFactoryFn + providerFactory providertracker.ProviderFactory + brokerFactory providertracker.ProviderFactory } // FactoryForModel returns a service factory for the given model uuid. @@ -131,7 +154,9 @@ func (s *serviceFactoryGetter) FactoryForModel(modelUUID string) servicefactory. return &serviceFactory{ ControllerServiceFactory: s.ctrlFactory, ModelServiceFactory: s.newModelServiceFactory( - coremodel.UUID(modelUUID), s.dbGetter, s.logger, + coremodel.UUID(modelUUID), s.dbGetter, + s.providerFactory, s.brokerFactory, + s.logger, ), } } diff --git a/internal/worker/servicefactory/worker_test.go b/internal/worker/servicefactory/worker_test.go index a999e7a8fdd..c657c45cc7b 100644 --- a/internal/worker/servicefactory/worker_test.go +++ b/internal/worker/servicefactory/worker_test.go @@ -13,6 +13,7 @@ import ( "github.com/juju/juju/core/changestream" coredatabase "github.com/juju/juju/core/database" coremodel "github.com/juju/juju/core/model" + "github.com/juju/juju/core/providertracker" "github.com/juju/juju/internal/servicefactory" ) @@ -55,16 +56,18 @@ func (s *workerSuite) TestValidateConfig(c *gc.C) { func (s *workerSuite) getConfig() Config { return Config{ - DBGetter: s.dbGetter, - DBDeleter: s.dbDeleter, - Logger: s.logger, - NewServiceFactoryGetter: func(servicefactory.ControllerServiceFactory, changestream.WatchableDBGetter, Logger, ModelServiceFactoryFn) servicefactory.ServiceFactoryGetter { + DBGetter: s.dbGetter, + DBDeleter: s.dbDeleter, + ProviderFactory: s.providerFactory, + BrokerFactory: s.providerFactory, + Logger: s.logger, + NewServiceFactoryGetter: func(_ servicefactory.ControllerServiceFactory, _ changestream.WatchableDBGetter, _ Logger, _ ModelServiceFactoryFn, _, _ providertracker.ProviderFactory) servicefactory.ServiceFactoryGetter { return s.serviceFactoryGetter }, NewControllerServiceFactory: func(changestream.WatchableDBGetter, coredatabase.DBDeleter, Logger) servicefactory.ControllerServiceFactory { return s.controllerServiceFactory }, - NewModelServiceFactory: func(coremodel.UUID, changestream.WatchableDBGetter, Logger) servicefactory.ModelServiceFactory { + NewModelServiceFactory: func(_ coremodel.UUID, _ changestream.WatchableDBGetter, _, _ providertracker.ProviderFactory, _ Logger) servicefactory.ModelServiceFactory { return s.modelServiceFactory }, } From 52a543a88d92afcd653f3331498c9b081197c43f Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Wed, 10 Apr 2024 11:45:58 +0100 Subject: [PATCH 2/3] Add servicefactory domain doc.go It make it easier to understand why we have a provider and a service factory, I've added a doc the explain the basic concept. --- domain/servicefactory/doc.go | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 domain/servicefactory/doc.go diff --git a/domain/servicefactory/doc.go b/domain/servicefactory/doc.go new file mode 100644 index 00000000000..e383187261d --- /dev/null +++ b/domain/servicefactory/doc.go @@ -0,0 +1,83 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// Package servicefactory provides functionality to manage service factories. +// Service types (controller, model and provider) wrap database access. Services +// encapsulate domain verticals, which can be used to encapsulate business and +// persistence logic. +// +// The controller service wraps the global "controller" namespace database. +// Everything in the controller namespace is global and shared across all +// models. It can generally be accessed by any consumer of the model service +// factory (referred to as "service factory"). +// +// The model service factory ("service factory") wraps a model namespace +// database. The model namespace is specific to a particular model. The model +// service factory can be accessed by any the knowledge of the namespace. +// +// The provider service factory is a special case, whereby it takes a very +// small subset of the controller and the model service factories and offers +// methods for the sole purpose of managing providers (environs and brokers). +// The provider service factory has very few dependencies and is only used +// by the provider tracker. The provider tracker caches providers for both +// IAAS and CAAS model types. Ensuring that any model configuration, cloud +// configuration or credential changes update the cached providers, without +// the need to restart the controller. +// +// The service factory can therefore consume additional dependencies from other +// dependency engine outputs without the worry of circular dependencies. +// +// ┌────────────────┐ +// │ │ +// │ │ +// │ DBACCESSOR │ +// │ │ +// │ │ +// └───────┬────────┘ +// │ +// ┌──────────────────┤ +// │ │ +// ┌───────▼───────┐ │ +// │ │ │ +// │ PROVIDER │ │ +// │ SERVICE │ │ +// │ FACTORY │ │ +// │ │ │ +// └───────┬───────┘ │ +// │ │ +// │ │ +// │ │ +// ┌─────────▼─────┐ │ +// │ │ │ +// │ ┌───────────────┐ │ +// │ │ │ │ +// │ │ ┌───────────────┐ │ +// │ │ │ │ │ +// │ │ │ │ │ +// └─│ │ PROVIDER │ │ +// │ │ TRACKER(S) │ │ +// └──│ │ │ +// │ │ │ +// └────┬──────────┘ │ +// │ │ +// └────────────────┐ │ +// │ │ +// ┌──────▼─▼────┐ +// │ │ +// │ │ +// │ SERVICE │ +// │ FACTORY │ +// │ │ +// │ │ +// └──────┬──────┘ +// │ +// │ +// ┌──────▼──────┐ +// │ │ +// │ │ +// │ OUTPUT │ +// │ │ +// │ │ +// └─────────────┘ + +package servicefactory From 48f44b26c3d2be8a45b9445d06adf39202849782 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Wed, 10 Apr 2024 12:15:20 +0100 Subject: [PATCH 3/3] Fix intermittent test around cancelled context --- rpc/client.go | 8 ++++++++ rpc/rpc_test.go | 19 ++++--------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index 69c70cb8773..1ee1644f05a 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -201,6 +201,14 @@ func (call *Call) done() { // The params value may be nil if no parameters are provided; the response value // may be nil to indicate that any result should be discarded. func (conn *Conn) Call(ctx context.Context, req Request, params, response interface{}) error { + // Before sending the request, check if the context has been canceled. + // This is done to prevent any unnecessary work from being done if the + // context has been canceled. + if ctx.Err() != nil { + return ctx.Err() + } + + // Extract the tracing information from the context. traceID, spanID, traceFlags := TracingFromContext(ctx) call := &Call{ Request: req, diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index 93e696188e1..90cfbdb8ecf 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -888,15 +888,8 @@ func (*rpcSuite) TestServerWaitsForOutstandingCalls(c *gc.C) { } func (*rpcSuite) TestClientCallCancelled(c *gc.C) { - ready := make(chan struct{}) - start := make(chan string) root := &Root{ - delayed: map[string]*DelayedMethods{ - "1": { - ready: ready, - done: start, - }, - }, + simple: make(map[string]*SimpleMethods), } client, _, srvDone, _ := newRPCClientServer(c, root, nil, false) defer closeClient(c, client, srvDone) @@ -906,15 +899,11 @@ func (*rpcSuite) TestClientCallCancelled(c *gc.C) { ctx, cancel := context.WithCancel(context.Background()) cancel() - var r stringVal - err := client.Call(ctx, rpc.Request{Type: "DelayedMethods", Version: 0, Id: "1", Action: "Delay"}, nil, &r) - c.Check(errors.Cause(err), jc.ErrorIs, context.Canceled) + err := client.Call(ctx, rpc.Request{Type: "SimpleMethods", Version: 0, Id: "0", Action: "Call"}, nil, nil) + c.Check(err, jc.ErrorIs, context.Canceled) - done <- struct{}{} + close(done) }() - chanRead(c, ready, "DelayedMethods.Delay ready") - - start <- "xxx" select { case <-done: