diff --git a/caas/kubernetes/provider/bootstrap.go b/caas/kubernetes/provider/bootstrap.go index 963e8b1b9b2..96a0f7340e9 100644 --- a/caas/kubernetes/provider/bootstrap.go +++ b/caas/kubernetes/provider/bootstrap.go @@ -1574,9 +1574,8 @@ func (c *controllerStack) buildContainerSpecForController() (*core.PodSpec, erro if c.pcfg.ControllerId == agent.BootstrapControllerId { // only do bootstrap-state on the bootstrap controller - controller-0. bootstrapStateCmd := fmt.Sprintf( - "%s bootstrap-state %s --data-dir $JUJU_DATA_DIR %s --timeout %s", + "%s bootstrap-state --data-dir $JUJU_DATA_DIR %s --timeout %s", c.pathJoin("$JUJU_TOOLS_DIR", "jujud"), - c.pathJoin("$JUJU_DATA_DIR", cloudconfig.FileNameBootstrapParams), loggingOption, c.timeout.String(), ) diff --git a/caas/kubernetes/provider/bootstrap_test.go b/caas/kubernetes/provider/bootstrap_test.go index f71e3edb744..fee97075fd2 100644 --- a/caas/kubernetes/provider/bootstrap_test.go +++ b/caas/kubernetes/provider/bootstrap_test.go @@ -835,7 +835,7 @@ export JUJU_TOOLS_DIR=$JUJU_DATA_DIR/tools mkdir -p $JUJU_TOOLS_DIR cp /opt/jujud $JUJU_TOOLS_DIR/jujud -test -e $JUJU_DATA_DIR/agents/controller-0/agent.conf || JUJU_DEV_FEATURE_FLAGS=developer-mode $JUJU_TOOLS_DIR/jujud bootstrap-state $JUJU_DATA_DIR/bootstrap-params --data-dir $JUJU_DATA_DIR --debug --timeout 10m0s +test -e $JUJU_DATA_DIR/agents/controller-0/agent.conf || JUJU_DEV_FEATURE_FLAGS=developer-mode $JUJU_TOOLS_DIR/jujud bootstrap-state --data-dir $JUJU_DATA_DIR --debug --timeout 10m0s mkdir -p /var/lib/pebble/default/layers cat > /var/lib/pebble/default/layers/001-jujud.yaml < '/var/lib/juju/bootstrap-params' echo 'Installing Juju machine agent'.* -/var/lib/juju/tools/1\.2\.3-ubuntu-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params' +/var/lib/juju/tools/1\.2\.3-ubuntu-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug /sbin/remove-juju-services rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-ubuntu-amd64\.sha256 `, @@ -383,7 +383,7 @@ chmod 0600 '/var/lib/juju/agents/machine-0/agent\.conf' install -D -m 600 /dev/null '/var/lib/juju/bootstrap-params' echo '.*' > '/var/lib/juju/bootstrap-params' echo 'Installing Juju machine agent'.* -/var/lib/juju/tools/1\.2\.3\.123-ubuntu-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params' +/var/lib/juju/tools/1\.2\.3\.123-ubuntu-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3\.123-ubuntu-amd64\.sha256 `, }, @@ -837,7 +837,7 @@ postruncmd: func (s *cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) { scripts := s.bootstrapConfigScripts(c) - expected := "jujud bootstrap-state .* --show-log .*" + expected := "jujud bootstrap-state .* --show-log" assertScriptMatch(c, scripts, expected, false) } diff --git a/internal/cloudconfig/userdatacfg_unix.go b/internal/cloudconfig/userdatacfg_unix.go index d9b1cdb49ab..a74a0d210d9 100644 --- a/internal/cloudconfig/userdatacfg_unix.go +++ b/internal/cloudconfig/userdatacfg_unix.go @@ -474,7 +474,6 @@ func (w *unixConfigure) configureBootstrap() error { "--timeout", w.icfg.Bootstrap.Timeout.String(), "--data-dir", shquote(w.icfg.DataDir), loggingOption, - shquote(bootstrapParamsFile), } w.conf.AddRunCmd(cloudinit.LogProgressCmd("Installing Juju machine agent")) w.conf.AddScripts(strings.Join(bootstrapAgentArgs, " ")) diff --git a/worker/bootstrap/agent_mock_test.go b/worker/bootstrap/agent_mock_test.go new file mode 100644 index 00000000000..2ec785557fe --- /dev/null +++ b/worker/bootstrap/agent_mock_test.go @@ -0,0 +1,562 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/agent (interfaces: Agent,Config) + +// Package bootstrap is a generated GoMock package. +package bootstrap + +import ( + reflect "reflect" + time "time" + + agent "github.com/juju/juju/agent" + api "github.com/juju/juju/api" + controller "github.com/juju/juju/controller" + model "github.com/juju/juju/core/model" + mongo "github.com/juju/juju/internal/mongo" + names "github.com/juju/names/v4" + shell "github.com/juju/utils/v3/shell" + version "github.com/juju/version/v2" + gomock "go.uber.org/mock/gomock" +) + +// MockAgent is a mock of Agent interface. +type MockAgent struct { + ctrl *gomock.Controller + recorder *MockAgentMockRecorder +} + +// MockAgentMockRecorder is the mock recorder for MockAgent. +type MockAgentMockRecorder struct { + mock *MockAgent +} + +// NewMockAgent creates a new mock instance. +func NewMockAgent(ctrl *gomock.Controller) *MockAgent { + mock := &MockAgent{ctrl: ctrl} + mock.recorder = &MockAgentMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAgent) EXPECT() *MockAgentMockRecorder { + return m.recorder +} + +// ChangeConfig mocks base method. +func (m *MockAgent) ChangeConfig(arg0 agent.ConfigMutator) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChangeConfig", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChangeConfig indicates an expected call of ChangeConfig. +func (mr *MockAgentMockRecorder) ChangeConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeConfig", reflect.TypeOf((*MockAgent)(nil).ChangeConfig), arg0) +} + +// CurrentConfig mocks base method. +func (m *MockAgent) CurrentConfig() agent.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentConfig") + ret0, _ := ret[0].(agent.Config) + return ret0 +} + +// CurrentConfig indicates an expected call of CurrentConfig. +func (mr *MockAgentMockRecorder) CurrentConfig() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentConfig", reflect.TypeOf((*MockAgent)(nil).CurrentConfig)) +} + +// MockConfig is a mock of Config interface. +type MockConfig struct { + ctrl *gomock.Controller + recorder *MockConfigMockRecorder +} + +// MockConfigMockRecorder is the mock recorder for MockConfig. +type MockConfigMockRecorder struct { + mock *MockConfig +} + +// NewMockConfig creates a new mock instance. +func NewMockConfig(ctrl *gomock.Controller) *MockConfig { + mock := &MockConfig{ctrl: ctrl} + mock.recorder = &MockConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfig) EXPECT() *MockConfigMockRecorder { + return m.recorder +} + +// APIAddresses mocks base method. +func (m *MockConfig) APIAddresses() ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "APIAddresses") + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// APIAddresses indicates an expected call of APIAddresses. +func (mr *MockConfigMockRecorder) APIAddresses() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "APIAddresses", reflect.TypeOf((*MockConfig)(nil).APIAddresses)) +} + +// APIInfo mocks base method. +func (m *MockConfig) APIInfo() (*api.Info, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "APIInfo") + ret0, _ := ret[0].(*api.Info) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// APIInfo indicates an expected call of APIInfo. +func (mr *MockConfigMockRecorder) APIInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "APIInfo", reflect.TypeOf((*MockConfig)(nil).APIInfo)) +} + +// AgentLogfileMaxBackups mocks base method. +func (m *MockConfig) AgentLogfileMaxBackups() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AgentLogfileMaxBackups") + ret0, _ := ret[0].(int) + return ret0 +} + +// AgentLogfileMaxBackups indicates an expected call of AgentLogfileMaxBackups. +func (mr *MockConfigMockRecorder) AgentLogfileMaxBackups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AgentLogfileMaxBackups", reflect.TypeOf((*MockConfig)(nil).AgentLogfileMaxBackups)) +} + +// AgentLogfileMaxSizeMB mocks base method. +func (m *MockConfig) AgentLogfileMaxSizeMB() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AgentLogfileMaxSizeMB") + ret0, _ := ret[0].(int) + return ret0 +} + +// AgentLogfileMaxSizeMB indicates an expected call of AgentLogfileMaxSizeMB. +func (mr *MockConfigMockRecorder) AgentLogfileMaxSizeMB() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AgentLogfileMaxSizeMB", reflect.TypeOf((*MockConfig)(nil).AgentLogfileMaxSizeMB)) +} + +// CACert mocks base method. +func (m *MockConfig) CACert() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CACert") + ret0, _ := ret[0].(string) + return ret0 +} + +// CACert indicates an expected call of CACert. +func (mr *MockConfigMockRecorder) CACert() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CACert", reflect.TypeOf((*MockConfig)(nil).CACert)) +} + +// Controller mocks base method. +func (m *MockConfig) Controller() names.ControllerTag { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Controller") + ret0, _ := ret[0].(names.ControllerTag) + return ret0 +} + +// Controller indicates an expected call of Controller. +func (mr *MockConfigMockRecorder) Controller() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Controller", reflect.TypeOf((*MockConfig)(nil).Controller)) +} + +// DataDir mocks base method. +func (m *MockConfig) DataDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DataDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// DataDir indicates an expected call of DataDir. +func (mr *MockConfigMockRecorder) DataDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DataDir", reflect.TypeOf((*MockConfig)(nil).DataDir)) +} + +// Dir mocks base method. +func (m *MockConfig) Dir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Dir") + ret0, _ := ret[0].(string) + return ret0 +} + +// Dir indicates an expected call of Dir. +func (mr *MockConfigMockRecorder) Dir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dir", reflect.TypeOf((*MockConfig)(nil).Dir)) +} + +// DqlitePort mocks base method. +func (m *MockConfig) DqlitePort() (int, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DqlitePort") + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// DqlitePort indicates an expected call of DqlitePort. +func (mr *MockConfigMockRecorder) DqlitePort() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DqlitePort", reflect.TypeOf((*MockConfig)(nil).DqlitePort)) +} + +// Jobs mocks base method. +func (m *MockConfig) Jobs() []model.MachineJob { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Jobs") + ret0, _ := ret[0].([]model.MachineJob) + return ret0 +} + +// Jobs indicates an expected call of Jobs. +func (mr *MockConfigMockRecorder) Jobs() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Jobs", reflect.TypeOf((*MockConfig)(nil).Jobs)) +} + +// JujuDBSnapChannel mocks base method. +func (m *MockConfig) JujuDBSnapChannel() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "JujuDBSnapChannel") + ret0, _ := ret[0].(string) + return ret0 +} + +// JujuDBSnapChannel indicates an expected call of JujuDBSnapChannel. +func (mr *MockConfigMockRecorder) JujuDBSnapChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "JujuDBSnapChannel", reflect.TypeOf((*MockConfig)(nil).JujuDBSnapChannel)) +} + +// LogDir mocks base method. +func (m *MockConfig) LogDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// LogDir indicates an expected call of LogDir. +func (mr *MockConfigMockRecorder) LogDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogDir", reflect.TypeOf((*MockConfig)(nil).LogDir)) +} + +// LoggingConfig mocks base method. +func (m *MockConfig) LoggingConfig() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoggingConfig") + ret0, _ := ret[0].(string) + return ret0 +} + +// LoggingConfig indicates an expected call of LoggingConfig. +func (mr *MockConfigMockRecorder) LoggingConfig() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoggingConfig", reflect.TypeOf((*MockConfig)(nil).LoggingConfig)) +} + +// MetricsSpoolDir mocks base method. +func (m *MockConfig) MetricsSpoolDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MetricsSpoolDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// MetricsSpoolDir indicates an expected call of MetricsSpoolDir. +func (mr *MockConfigMockRecorder) MetricsSpoolDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MetricsSpoolDir", reflect.TypeOf((*MockConfig)(nil).MetricsSpoolDir)) +} + +// Model mocks base method. +func (m *MockConfig) Model() names.ModelTag { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Model") + ret0, _ := ret[0].(names.ModelTag) + return ret0 +} + +// Model indicates an expected call of Model. +func (mr *MockConfigMockRecorder) Model() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Model", reflect.TypeOf((*MockConfig)(nil).Model)) +} + +// MongoInfo mocks base method. +func (m *MockConfig) MongoInfo() (*mongo.MongoInfo, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MongoInfo") + ret0, _ := ret[0].(*mongo.MongoInfo) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// MongoInfo indicates an expected call of MongoInfo. +func (mr *MockConfigMockRecorder) MongoInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MongoInfo", reflect.TypeOf((*MockConfig)(nil).MongoInfo)) +} + +// MongoMemoryProfile mocks base method. +func (m *MockConfig) MongoMemoryProfile() mongo.MemoryProfile { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MongoMemoryProfile") + ret0, _ := ret[0].(mongo.MemoryProfile) + return ret0 +} + +// MongoMemoryProfile indicates an expected call of MongoMemoryProfile. +func (mr *MockConfigMockRecorder) MongoMemoryProfile() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MongoMemoryProfile", reflect.TypeOf((*MockConfig)(nil).MongoMemoryProfile)) +} + +// Nonce mocks base method. +func (m *MockConfig) Nonce() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Nonce") + ret0, _ := ret[0].(string) + return ret0 +} + +// Nonce indicates an expected call of Nonce. +func (mr *MockConfigMockRecorder) Nonce() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Nonce", reflect.TypeOf((*MockConfig)(nil).Nonce)) +} + +// OldPassword mocks base method. +func (m *MockConfig) OldPassword() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OldPassword") + ret0, _ := ret[0].(string) + return ret0 +} + +// OldPassword indicates an expected call of OldPassword. +func (mr *MockConfigMockRecorder) OldPassword() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OldPassword", reflect.TypeOf((*MockConfig)(nil).OldPassword)) +} + +// OpenTelemetryEnabled mocks base method. +func (m *MockConfig) OpenTelemetryEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenTelemetryEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// OpenTelemetryEnabled indicates an expected call of OpenTelemetryEnabled. +func (mr *MockConfigMockRecorder) OpenTelemetryEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenTelemetryEnabled", reflect.TypeOf((*MockConfig)(nil).OpenTelemetryEnabled)) +} + +// OpenTelemetryEndpoint mocks base method. +func (m *MockConfig) OpenTelemetryEndpoint() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenTelemetryEndpoint") + ret0, _ := ret[0].(string) + return ret0 +} + +// OpenTelemetryEndpoint indicates an expected call of OpenTelemetryEndpoint. +func (mr *MockConfigMockRecorder) OpenTelemetryEndpoint() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenTelemetryEndpoint", reflect.TypeOf((*MockConfig)(nil).OpenTelemetryEndpoint)) +} + +// OpenTelemetryInsecure mocks base method. +func (m *MockConfig) OpenTelemetryInsecure() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenTelemetryInsecure") + ret0, _ := ret[0].(bool) + return ret0 +} + +// OpenTelemetryInsecure indicates an expected call of OpenTelemetryInsecure. +func (mr *MockConfigMockRecorder) OpenTelemetryInsecure() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenTelemetryInsecure", reflect.TypeOf((*MockConfig)(nil).OpenTelemetryInsecure)) +} + +// OpenTelemetrySampleRatio mocks base method. +func (m *MockConfig) OpenTelemetrySampleRatio() float64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenTelemetrySampleRatio") + ret0, _ := ret[0].(float64) + return ret0 +} + +// OpenTelemetrySampleRatio indicates an expected call of OpenTelemetrySampleRatio. +func (mr *MockConfigMockRecorder) OpenTelemetrySampleRatio() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenTelemetrySampleRatio", reflect.TypeOf((*MockConfig)(nil).OpenTelemetrySampleRatio)) +} + +// OpenTelemetryStackTraces mocks base method. +func (m *MockConfig) OpenTelemetryStackTraces() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenTelemetryStackTraces") + ret0, _ := ret[0].(bool) + return ret0 +} + +// OpenTelemetryStackTraces indicates an expected call of OpenTelemetryStackTraces. +func (mr *MockConfigMockRecorder) OpenTelemetryStackTraces() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenTelemetryStackTraces", reflect.TypeOf((*MockConfig)(nil).OpenTelemetryStackTraces)) +} + +// QueryTracingEnabled mocks base method. +func (m *MockConfig) QueryTracingEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryTracingEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// QueryTracingEnabled indicates an expected call of QueryTracingEnabled. +func (mr *MockConfigMockRecorder) QueryTracingEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryTracingEnabled", reflect.TypeOf((*MockConfig)(nil).QueryTracingEnabled)) +} + +// QueryTracingThreshold mocks base method. +func (m *MockConfig) QueryTracingThreshold() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryTracingThreshold") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// QueryTracingThreshold indicates an expected call of QueryTracingThreshold. +func (mr *MockConfigMockRecorder) QueryTracingThreshold() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryTracingThreshold", reflect.TypeOf((*MockConfig)(nil).QueryTracingThreshold)) +} + +// StateServingInfo mocks base method. +func (m *MockConfig) StateServingInfo() (controller.StateServingInfo, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateServingInfo") + ret0, _ := ret[0].(controller.StateServingInfo) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// StateServingInfo indicates an expected call of StateServingInfo. +func (mr *MockConfigMockRecorder) StateServingInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateServingInfo", reflect.TypeOf((*MockConfig)(nil).StateServingInfo)) +} + +// SystemIdentityPath mocks base method. +func (m *MockConfig) SystemIdentityPath() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SystemIdentityPath") + ret0, _ := ret[0].(string) + return ret0 +} + +// SystemIdentityPath indicates an expected call of SystemIdentityPath. +func (mr *MockConfigMockRecorder) SystemIdentityPath() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SystemIdentityPath", reflect.TypeOf((*MockConfig)(nil).SystemIdentityPath)) +} + +// Tag mocks base method. +func (m *MockConfig) Tag() names.Tag { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Tag") + ret0, _ := ret[0].(names.Tag) + return ret0 +} + +// Tag indicates an expected call of Tag. +func (mr *MockConfigMockRecorder) Tag() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tag", reflect.TypeOf((*MockConfig)(nil).Tag)) +} + +// TransientDataDir mocks base method. +func (m *MockConfig) TransientDataDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TransientDataDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// TransientDataDir indicates an expected call of TransientDataDir. +func (mr *MockConfigMockRecorder) TransientDataDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransientDataDir", reflect.TypeOf((*MockConfig)(nil).TransientDataDir)) +} + +// UpgradedToVersion mocks base method. +func (m *MockConfig) UpgradedToVersion() version.Number { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpgradedToVersion") + ret0, _ := ret[0].(version.Number) + return ret0 +} + +// UpgradedToVersion indicates an expected call of UpgradedToVersion. +func (mr *MockConfigMockRecorder) UpgradedToVersion() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpgradedToVersion", reflect.TypeOf((*MockConfig)(nil).UpgradedToVersion)) +} + +// Value mocks base method. +func (m *MockConfig) Value(arg0 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Value", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// Value indicates an expected call of Value. +func (mr *MockConfigMockRecorder) Value(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockConfig)(nil).Value), arg0) +} + +// WriteCommands mocks base method. +func (m *MockConfig) WriteCommands(arg0 shell.Renderer) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteCommands", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteCommands indicates an expected call of WriteCommands. +func (mr *MockConfigMockRecorder) WriteCommands(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteCommands", reflect.TypeOf((*MockConfig)(nil).WriteCommands), arg0) +} diff --git a/worker/bootstrap/doc.go b/worker/bootstrap/doc.go new file mode 100644 index 00000000000..7e1dd91d3e1 --- /dev/null +++ b/worker/bootstrap/doc.go @@ -0,0 +1,15 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// Package bootstrap ensures that when the initial bootstrap process +// has started that we seed the following: +// +// 1. The agent binary is seeded into the objectstore. +// 2. The controller application has been created, along with the supporting +// machine and spaces. +// 3. The controller charm is seeded into the objectstore. +// +// The intention is that the worker is started as soon as possible during +// bootstrap and that it will uninstall itself once the bootstrap process +// has completed. +package bootstrap diff --git a/worker/bootstrap/lock_mock_test.go b/worker/bootstrap/lock_mock_test.go new file mode 100644 index 00000000000..c8c0862e0ca --- /dev/null +++ b/worker/bootstrap/lock_mock_test.go @@ -0,0 +1,46 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/worker/gate (interfaces: Unlocker) + +// Package bootstrap is a generated GoMock package. +package bootstrap + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockUnlocker is a mock of Unlocker interface. +type MockUnlocker struct { + ctrl *gomock.Controller + recorder *MockUnlockerMockRecorder +} + +// MockUnlockerMockRecorder is the mock recorder for MockUnlocker. +type MockUnlockerMockRecorder struct { + mock *MockUnlocker +} + +// NewMockUnlocker creates a new mock instance. +func NewMockUnlocker(ctrl *gomock.Controller) *MockUnlocker { + mock := &MockUnlocker{ctrl: ctrl} + mock.recorder = &MockUnlockerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUnlocker) EXPECT() *MockUnlockerMockRecorder { + return m.recorder +} + +// Unlock mocks base method. +func (m *MockUnlocker) Unlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Unlock") +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockUnlockerMockRecorder) Unlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockUnlocker)(nil).Unlock)) +} diff --git a/worker/bootstrap/manifold.go b/worker/bootstrap/manifold.go new file mode 100644 index 00000000000..4edf7fb0e06 --- /dev/null +++ b/worker/bootstrap/manifold.go @@ -0,0 +1,183 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package bootstrap + +import ( + "github.com/juju/errors" + "github.com/juju/worker/v3" + "github.com/juju/worker/v3/dependency" + + "github.com/juju/juju/agent" + "github.com/juju/juju/core/application" + "github.com/juju/juju/core/objectstore" + st "github.com/juju/juju/state" + "github.com/juju/juju/worker/common" + "github.com/juju/juju/worker/gate" + "github.com/juju/juju/worker/state" +) + +// Logger represents the logging methods called. +type Logger interface { + Errorf(message string, args ...any) + Warningf(message string, args ...any) + Infof(message string, args ...any) + Debugf(message string, args ...any) +} + +// AgentBinaryBootstrapFunc is the function that is used to populate the tools. +type AgentBinaryBootstrapFunc func() error + +// RequiresBootstrapFunc is the function that is used to check if the controller +// application exists. +type RequiresBootstrapFunc func(appService ApplicationService) (bool, error) + +// ManifoldConfig defines the configuration for the trace manifold. +type ManifoldConfig struct { + AgentName string + StateName string + ObjectStoreName string + BootstrapGateName string + + Logger Logger + AgentBinarySeeder AgentBinaryBootstrapFunc + RequiresBootstrap RequiresBootstrapFunc +} + +// Validate validates the manifold configuration. +func (cfg ManifoldConfig) Validate() error { + if cfg.AgentName == "" { + return errors.NotValidf("empty AgentName") + } + if cfg.ObjectStoreName == "" { + return errors.NotValidf("empty ObjectStoreName") + } + if cfg.StateName == "" { + return errors.NotValidf("empty StateName") + } + if cfg.BootstrapGateName == "" { + return errors.NotValidf("empty BootstrapGateName") + } + if cfg.Logger == nil { + return errors.NotValidf("nil Logger") + } + if cfg.AgentBinarySeeder == nil { + return errors.NotValidf("nil AgentBinarySeeder") + } + return nil +} + +// Manifold returns a dependency manifold that runs the trace worker. +func Manifold(config ManifoldConfig) dependency.Manifold { + return dependency.Manifold{ + Inputs: []string{ + config.AgentName, + config.StateName, + config.ObjectStoreName, + config.BootstrapGateName, + }, + Start: func(context dependency.Context) (worker.Worker, error) { + if err := config.Validate(); err != nil { + return nil, errors.Trace(err) + } + + var a agent.Agent + if err := context.Get(config.AgentName, &a); err != nil { + return nil, err + } + + var bootstrapUnlocker gate.Unlocker + if err := context.Get(config.BootstrapGateName, &bootstrapUnlocker); err != nil { + return nil, errors.Trace(err) + } + + var stTracker state.StateTracker + if err := context.Get(config.StateName, &stTracker); err != nil { + return nil, errors.Trace(err) + } + + // Get the state pool after grabbing dependencies so we don't need + // to remember to call Done on it if they're not running yet. + _, st, err := stTracker.Use() + if err != nil { + return nil, errors.Trace(err) + } + + // If the controller application exists, then we don't need to + // bootstrap. Uninstall the worker, as we don't need it running + // anymore. + if ok, err := config.RequiresBootstrap(&applicationStateService{st: st}); err != nil { + return nil, errors.Trace(err) + } else if !ok { + _ = stTracker.Done() + bootstrapUnlocker.Unlock() + return nil, dependency.ErrUninstall + } + + var objectStore objectstore.ObjectStore + if err := context.Get(config.ObjectStoreName, &objectStore); err != nil { + _ = stTracker.Done() + return nil, errors.Trace(err) + } + + w, err := NewWorker(WorkerConfig{ + Agent: a, + ObjectStore: objectStore, + State: st, + BootstrapUnlocker: bootstrapUnlocker, + AgentBinarySeeder: config.AgentBinarySeeder, + Logger: config.Logger, + }) + if err != nil { + _ = stTracker.Done() + return nil, errors.Trace(err) + } + return common.NewCleanupWorker(w, func() { + // Ensure we clean up the state pool. + _ = stTracker.Done() + }), nil + }, + } +} + +// CAASAgentBinarySeeder is the function that is used to populate the tools +// for CAAS. +func CAASAgentBinarySeeder() error { + return nil +} + +// IAASAgentBinarySeeder is the function that is used to populate the tools +// for IAAS. +func IAASAgentBinarySeeder() error { + return nil +} + +// ApplicationService is the interface that is used to check if an application +// exists. +type ApplicationService interface { + ApplicationExists(name string) (bool, error) +} + +// RequiresBootstrap returns true if the controller application does not exist. +func RequiresBootstrap(appService ApplicationService) (bool, error) { + ok, err := appService.ApplicationExists(application.ControllerApplicationName) + if err != nil { + return false, errors.Trace(err) + } + return !ok, nil +} + +type applicationStateService struct { + st *st.State +} + +func (s *applicationStateService) ApplicationExists(name string) (bool, error) { + _, err := s.st.Application(name) + if err == nil { + return true, nil + } + if errors.Is(err, errors.NotFound) { + return false, nil + } + return false, errors.Annotatef(err, "application exists") +} diff --git a/worker/bootstrap/manifold_test.go b/worker/bootstrap/manifold_test.go new file mode 100644 index 00000000000..528227d0ee7 --- /dev/null +++ b/worker/bootstrap/manifold_test.go @@ -0,0 +1,80 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package bootstrap + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + "github.com/juju/worker/v3/dependency" + dependencytesting "github.com/juju/worker/v3/dependency/testing" + gc "gopkg.in/check.v1" + + state "github.com/juju/juju/state" +) + +type manifoldSuite struct { + baseSuite +} + +var _ = gc.Suite(&manifoldSuite{}) + +func (s *manifoldSuite) TestValidateConfig(c *gc.C) { + defer s.setupMocks(c).Finish() + + cfg := s.getConfig() + c.Check(cfg.Validate(), jc.ErrorIsNil) + + cfg.AgentName = "" + c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) + + cfg.StateName = "" + c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) + + cfg = s.getConfig() + cfg.Logger = nil + c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid) +} + +func (s *manifoldSuite) getConfig() ManifoldConfig { + return ManifoldConfig{ + AgentName: "agent", + ObjectStoreName: "object-store", + StateName: "state", + BootstrapGateName: "bootstrap-gate", + Logger: s.logger, + AgentBinarySeeder: func() error { return nil }, + RequiresBootstrap: func(appService ApplicationService) (bool, error) { return false, nil }, + } +} + +func (s *manifoldSuite) getContext() dependency.Context { + resources := map[string]any{ + "agent": s.agent, + "state": s.stateTracker, + "object-store": s.objectStore, + "bootstrap-gate": s.bootstrapUnlocker, + } + return dependencytesting.StubContext(nil, resources) +} + +var expectedInputs = []string{"agent", "state", "object-store", "bootstrap-gate"} + +func (s *manifoldSuite) TestInputs(c *gc.C) { + c.Assert(Manifold(s.getConfig()).Inputs, jc.SameContents, expectedInputs) +} + +func (s *manifoldSuite) TestStartAlreadyBootstrapped(c *gc.C) { + defer s.setupMocks(c).Finish() + + s.expectStateTracker() + s.expectGateUnlock() + + _, err := Manifold(s.getConfig()).Start(s.getContext()) + c.Assert(err, jc.ErrorIs, dependency.ErrUninstall) +} + +func (s *manifoldSuite) expectStateTracker() { + s.stateTracker.EXPECT().Use().Return(&state.StatePool{}, &state.State{}, nil) + s.stateTracker.EXPECT().Done() +} diff --git a/worker/bootstrap/objectstore_mock_test.go b/worker/bootstrap/objectstore_mock_test.go new file mode 100644 index 00000000000..4d5dd69bd5a --- /dev/null +++ b/worker/bootstrap/objectstore_mock_test.go @@ -0,0 +1,94 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/core/objectstore (interfaces: ObjectStore) + +// Package bootstrap is a generated GoMock package. +package bootstrap + +import ( + context "context" + io "io" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockObjectStore is a mock of ObjectStore interface. +type MockObjectStore struct { + ctrl *gomock.Controller + recorder *MockObjectStoreMockRecorder +} + +// MockObjectStoreMockRecorder is the mock recorder for MockObjectStore. +type MockObjectStoreMockRecorder struct { + mock *MockObjectStore +} + +// NewMockObjectStore creates a new mock instance. +func NewMockObjectStore(ctrl *gomock.Controller) *MockObjectStore { + mock := &MockObjectStore{ctrl: ctrl} + mock.recorder = &MockObjectStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObjectStore) EXPECT() *MockObjectStoreMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockObjectStore) Get(arg0 context.Context, arg1 string) (io.ReadCloser, int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockObjectStoreMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockObjectStore)(nil).Get), arg0, arg1) +} + +// Put mocks base method. +func (m *MockObjectStore) Put(arg0 context.Context, arg1 string, arg2 io.Reader, arg3 int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Put", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// Put indicates an expected call of Put. +func (mr *MockObjectStoreMockRecorder) Put(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockObjectStore)(nil).Put), arg0, arg1, arg2, arg3) +} + +// PutAndCheckHash mocks base method. +func (m *MockObjectStore) PutAndCheckHash(arg0 context.Context, arg1 string, arg2 io.Reader, arg3 int64, arg4 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutAndCheckHash", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutAndCheckHash indicates an expected call of PutAndCheckHash. +func (mr *MockObjectStoreMockRecorder) PutAndCheckHash(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutAndCheckHash", reflect.TypeOf((*MockObjectStore)(nil).PutAndCheckHash), arg0, arg1, arg2, arg3, arg4) +} + +// Remove mocks base method. +func (m *MockObjectStore) Remove(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Remove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Remove indicates an expected call of Remove. +func (mr *MockObjectStoreMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockObjectStore)(nil).Remove), arg0, arg1) +} diff --git a/worker/bootstrap/package_test.go b/worker/bootstrap/package_test.go new file mode 100644 index 00000000000..2c677827c2b --- /dev/null +++ b/worker/bootstrap/package_test.go @@ -0,0 +1,51 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package bootstrap + +import ( + "testing" + + jujutesting "github.com/juju/testing" + "go.uber.org/mock/gomock" + gc "gopkg.in/check.v1" + + jujujujutesting "github.com/juju/juju/testing" +) + +//go:generate go run go.uber.org/mock/mockgen -package bootstrap -destination agent_mock_test.go github.com/juju/juju/agent Agent,Config +//go:generate go run go.uber.org/mock/mockgen -package bootstrap -destination state_mock_test.go github.com/juju/juju/worker/state StateTracker +//go:generate go run go.uber.org/mock/mockgen -package bootstrap -destination objectstore_mock_test.go github.com/juju/juju/core/objectstore ObjectStore +//go:generate go run go.uber.org/mock/mockgen -package bootstrap -destination lock_mock_test.go github.com/juju/juju/worker/gate Unlocker + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} + +type baseSuite struct { + jujutesting.IsolationSuite + + agent *MockAgent + stateTracker *MockStateTracker + objectStore *MockObjectStore + bootstrapUnlocker *MockUnlocker + + logger Logger +} + +func (s *baseSuite) setupMocks(c *gc.C) *gomock.Controller { + ctrl := gomock.NewController(c) + + s.agent = NewMockAgent(ctrl) + s.stateTracker = NewMockStateTracker(ctrl) + s.objectStore = NewMockObjectStore(ctrl) + s.bootstrapUnlocker = NewMockUnlocker(ctrl) + + s.logger = jujujujutesting.NewCheckLogger(c) + + return ctrl +} + +func (s *baseSuite) expectGateUnlock() { + s.bootstrapUnlocker.EXPECT().Unlock() +} diff --git a/worker/bootstrap/state_mock_test.go b/worker/bootstrap/state_mock_test.go new file mode 100644 index 00000000000..31fc0cc36b9 --- /dev/null +++ b/worker/bootstrap/state_mock_test.go @@ -0,0 +1,79 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/juju/juju/worker/state (interfaces: StateTracker) + +// Package bootstrap is a generated GoMock package. +package bootstrap + +import ( + reflect "reflect" + + state "github.com/juju/juju/state" + gomock "go.uber.org/mock/gomock" +) + +// MockStateTracker is a mock of StateTracker interface. +type MockStateTracker struct { + ctrl *gomock.Controller + recorder *MockStateTrackerMockRecorder +} + +// MockStateTrackerMockRecorder is the mock recorder for MockStateTracker. +type MockStateTrackerMockRecorder struct { + mock *MockStateTracker +} + +// NewMockStateTracker creates a new mock instance. +func NewMockStateTracker(ctrl *gomock.Controller) *MockStateTracker { + mock := &MockStateTracker{ctrl: ctrl} + mock.recorder = &MockStateTrackerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateTracker) EXPECT() *MockStateTrackerMockRecorder { + return m.recorder +} + +// Done mocks base method. +func (m *MockStateTracker) Done() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Done") + ret0, _ := ret[0].(error) + return ret0 +} + +// Done indicates an expected call of Done. +func (mr *MockStateTrackerMockRecorder) Done() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Done", reflect.TypeOf((*MockStateTracker)(nil).Done)) +} + +// Report mocks base method. +func (m *MockStateTracker) Report() map[string]interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Report") + ret0, _ := ret[0].(map[string]interface{}) + return ret0 +} + +// Report indicates an expected call of Report. +func (mr *MockStateTrackerMockRecorder) Report() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Report", reflect.TypeOf((*MockStateTracker)(nil).Report)) +} + +// Use mocks base method. +func (m *MockStateTracker) Use() (*state.StatePool, *state.State, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Use") + ret0, _ := ret[0].(*state.StatePool) + ret1, _ := ret[1].(*state.State) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Use indicates an expected call of Use. +func (mr *MockStateTrackerMockRecorder) Use() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockStateTracker)(nil).Use)) +} diff --git a/worker/bootstrap/worker.go b/worker/bootstrap/worker.go new file mode 100644 index 00000000000..7485bde4d8d --- /dev/null +++ b/worker/bootstrap/worker.go @@ -0,0 +1,113 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package bootstrap + +import ( + "github.com/juju/errors" + "gopkg.in/tomb.v2" + + "github.com/juju/juju/agent" + "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/state" + "github.com/juju/juju/worker/gate" +) + +const ( + // States which report the state of the worker. + stateStarted = "started" +) + +// WorkerConfig encapsulates the configuration options for the +// bootstrap worker. +type WorkerConfig struct { + Agent agent.Agent + ObjectStore objectstore.ObjectStore + BootstrapUnlocker gate.Unlocker + AgentBinarySeeder AgentBinaryBootstrapFunc + + // Deprecated: This is only here, until we can remove the state layer. + State *state.State + + Logger Logger +} + +// Validate ensures that the config values are valid. +func (c *WorkerConfig) Validate() error { + if c.Agent == nil { + return errors.NotValidf("nil Agent") + } + if c.ObjectStore == nil { + return errors.NotValidf("nil ObjectStore") + } + if c.BootstrapUnlocker == nil { + return errors.NotValidf("nil BootstrapUnlocker") + } + if c.AgentBinarySeeder == nil { + return errors.NotValidf("nil AgentBinarySeeder") + } + if c.Logger == nil { + return errors.NotValidf("nil Logger") + } + if c.State == nil { + return errors.NotValidf("nil State") + } + return nil +} + +type bootstrapWorker struct { + internalStates chan string + cfg WorkerConfig + tomb tomb.Tomb +} + +// NewWorker creates a new bootstrap worker. +func NewWorker(cfg WorkerConfig) (*bootstrapWorker, error) { + return newWorker(cfg, nil) +} + +func newWorker(cfg WorkerConfig, internalStates chan string) (*bootstrapWorker, error) { + var err error + if err = cfg.Validate(); err != nil { + return nil, errors.Trace(err) + } + + w := &bootstrapWorker{ + internalStates: internalStates, + cfg: cfg, + } + w.tomb.Go(w.loop) + return w, nil +} + +// Kill stops the worker. +func (w *bootstrapWorker) Kill() { + w.tomb.Kill(nil) +} + +// Wait waits for the worker to stop and then returns the reason it was +// killed. +func (w *bootstrapWorker) Wait() error { + return w.tomb.Wait() +} + +func (w *bootstrapWorker) loop() error { + // Report the initial started state. + w.reportInternalState(stateStarted) + + // Perform the bootstrap. + // 1. Read bootstrap-params + // 2. Access the object store + // 3. Populate tools + // 4. Deploy the controller charm using state + w.cfg.BootstrapUnlocker.Unlock() + return nil +} + +func (w *bootstrapWorker) reportInternalState(state string) { + select { + case <-w.tomb.Dying(): + case w.internalStates <- state: + default: + } +} diff --git a/worker/bootstrap/worker_test.go b/worker/bootstrap/worker_test.go new file mode 100644 index 00000000000..ac82dbff048 --- /dev/null +++ b/worker/bootstrap/worker_test.go @@ -0,0 +1,69 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package bootstrap + +import ( + time "time" + + jc "github.com/juju/testing/checkers" + "github.com/juju/worker/v3" + "github.com/juju/worker/v3/workertest" + gomock "go.uber.org/mock/gomock" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +type workerSuite struct { + baseSuite + + states chan string +} + +var _ = gc.Suite(&workerSuite{}) + +func (s *workerSuite) TestKilled(c *gc.C) { + defer s.setupMocks(c).Finish() + + w := s.newWorker(c) + defer workertest.DirtyKill(c, w) + + s.expectGateUnlock() + s.ensureStartup(c) + + workertest.CleanKill(c, w) +} + +func (s *workerSuite) newWorker(c *gc.C) worker.Worker { + w, err := newWorker(WorkerConfig{ + Logger: s.logger, + Agent: s.agent, + ObjectStore: s.objectStore, + BootstrapUnlocker: s.bootstrapUnlocker, + AgentBinarySeeder: func() error { return nil }, + State: &state.State{}, + }, s.states) + c.Assert(err, jc.ErrorIsNil) + return w +} + +func (s *workerSuite) setupMocks(c *gc.C) *gomock.Controller { + // Ensure we buffer the channel, this is because we might miss the + // event if we're too quick at starting up. + s.states = make(chan string, 1) + + ctrl := s.baseSuite.setupMocks(c) + + return ctrl +} + +func (s *workerSuite) ensureStartup(c *gc.C) { + select { + case state := <-s.states: + c.Assert(state, gc.Equals, stateStarted) + case <-time.After(testing.ShortWait * 10): + c.Fatalf("timed out waiting for startup") + } +}