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  }