github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/meterstatus/manifold.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package meterstatus 5 6 import ( 7 "os" 8 "path" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 "github.com/juju/worker/v3" 15 "github.com/juju/worker/v3/dependency" 16 17 "github.com/juju/juju/agent" 18 "github.com/juju/juju/api/agent/meterstatus" 19 "github.com/juju/juju/api/base" 20 "github.com/juju/juju/api/common" 21 "github.com/juju/juju/core/machinelock" 22 ) 23 24 // Logger is here to stop the desire of creating a package level Logger. 25 // Don't do this, instead use the logger passed into the manifold. 26 type logger interface{} 27 28 var _ logger = struct{}{} 29 30 // Logger represents the logging methods used in this package. 31 type Logger interface { 32 Errorf(string, ...interface{}) 33 Warningf(string, ...interface{}) 34 Infof(string, ...interface{}) 35 Debugf(string, ...interface{}) 36 Tracef(string, ...interface{}) 37 38 Root() loggo.Logger 39 } 40 41 // Clock defines the time methods used by this package. 42 type Clock interface { 43 Now() time.Time 44 After(time.Duration) <-chan time.Time 45 } 46 47 // ManifoldConfig identifies the resource names upon which the status manifold depends. 48 type ManifoldConfig struct { 49 AgentName string 50 APICallerName string 51 MachineLock machinelock.Lock 52 Clock Clock 53 Logger Logger 54 55 NewHookRunner func(HookRunnerConfig) HookRunner 56 NewMeterStatusAPIClient func(base.APICaller, names.UnitTag) meterstatus.MeterStatusClient 57 NewUniterStateAPIClient func(base.FacadeCaller, names.UnitTag) *common.UnitStateAPI 58 59 NewConnectedStatusWorker func(ConnectedConfig) (worker.Worker, error) 60 NewIsolatedStatusWorker func(IsolatedConfig) (worker.Worker, error) 61 } 62 63 // Manifold returns a status manifold. 64 func Manifold(config ManifoldConfig) dependency.Manifold { 65 return dependency.Manifold{ 66 Inputs: []string{ 67 config.AgentName, 68 config.APICallerName, 69 }, 70 Start: func(context dependency.Context) (worker.Worker, error) { 71 if config.Clock == nil { 72 return nil, errors.NotValidf("missing Clock") 73 } 74 if config.MachineLock == nil { 75 return nil, errors.NotValidf("missing MachineLock") 76 } 77 return newStatusWorker(config, context) 78 }, 79 } 80 } 81 82 func newStatusWorker(config ManifoldConfig, context dependency.Context) (worker.Worker, error) { 83 var agent agent.Agent 84 if err := context.Get(config.AgentName, &agent); err != nil { 85 return nil, err 86 } 87 88 tag := agent.CurrentConfig().Tag() 89 unitTag, ok := tag.(names.UnitTag) 90 if !ok { 91 return nil, errors.Errorf("expected unit tag, got %v", tag) 92 } 93 94 agentConfig := agent.CurrentConfig() 95 runner := config.NewHookRunner(HookRunnerConfig{ 96 MachineLock: config.MachineLock, 97 AgentConfig: agentConfig, 98 Tag: unitTag, 99 Clock: config.Clock, 100 Logger: config.Logger, 101 }) 102 localStateFile := path.Join(agentConfig.DataDir(), "meter-status.yaml") 103 104 // If we don't have a valid APICaller, start a meter status 105 // worker that works without an API connection. Since the worker 106 // cannot talk to the controller to persist its state, we will provide 107 // it with a disk-backed StateReadWriter and attempt to push the data 108 // back to the controller once we get a valid connection. 109 var apiCaller base.APICaller 110 err := context.Get(config.APICallerName, &apiCaller) 111 if errors.Cause(err) == dependency.ErrMissing { 112 config.Logger.Tracef("API caller dependency not available, starting isolated meter status worker.") 113 cfg := IsolatedConfig{ 114 Runner: runner, 115 StateReadWriter: NewDiskBackedState(localStateFile), 116 Clock: config.Clock, 117 Logger: config.Logger, 118 AmberGracePeriod: defaultAmberGracePeriod, 119 RedGracePeriod: defaultRedGracePeriod, 120 TriggerFactory: GetTriggers, 121 } 122 return config.NewIsolatedStatusWorker(cfg) 123 } else if err != nil { 124 return nil, err 125 } 126 config.Logger.Tracef("Starting connected meter status worker.") 127 status := config.NewMeterStatusAPIClient(apiCaller, unitTag) 128 stateReadWriter := NewControllerBackedState( 129 config.NewUniterStateAPIClient( 130 base.NewFacadeCaller(apiCaller, "MeterStatus"), 131 unitTag, 132 ), 133 ) 134 135 // Check if a local state file exists from a previous isolated worker 136 // instance. If one is found, migrate it to the controller and remove 137 // it from disk; this doubles as an auto-magic migration step. 138 priorState, err := NewDiskBackedState(localStateFile).Read() 139 if err != nil && !errors.IsNotFound(err) { 140 return nil, errors.Annotate(err, "reading locally persisted worker state") 141 } else if err == nil { 142 config.Logger.Infof("detected locally persisted worker state; migrating to the controller") 143 if err = stateReadWriter.Write(priorState); err != nil { 144 return nil, errors.Trace(err) 145 } 146 147 // We can now safely delete the state from disk. It's fine for 148 // the deletion attempt to fail; we simply log it as a warning 149 // as it's non-fatal. 150 if err = os.Remove(localStateFile); err != nil { 151 config.Logger.Warningf("unable to remove existing local state file: %v", err) 152 } 153 } 154 155 cfg := ConnectedConfig{ 156 Runner: runner, 157 StateReadWriter: stateReadWriter, 158 Status: status, 159 Logger: config.Logger, 160 } 161 return config.NewConnectedStatusWorker(cfg) 162 }