github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/apicaller/manifold.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apicaller
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/worker/v3"
     9  	"github.com/juju/worker/v3/dependency"
    10  
    11  	"github.com/juju/juju/agent"
    12  	"github.com/juju/juju/api"
    13  	"github.com/juju/juju/api/base"
    14  )
    15  
    16  // Logger represents the methods used by the worker to log details.
    17  type Logger interface {
    18  	Debugf(string, ...interface{})
    19  	Infof(string, ...interface{})
    20  	Errorf(string, ...interface{})
    21  }
    22  
    23  // ConnectFunc is responsible for making and validating an API connection
    24  // on behalf of an agent.
    25  type ConnectFunc func(agent.Agent, api.OpenFunc, Logger) (api.Connection, error)
    26  
    27  // ManifoldConfig defines a Manifold's dependencies.
    28  type ManifoldConfig struct {
    29  
    30  	// AgentName is the name of the Agent resource that supplies
    31  	// connection information.
    32  	AgentName string
    33  
    34  	// APIConfigWatcherName identifies a resource that will be
    35  	// invalidated when api configuration changes. It's not really
    36  	// fundamental, because it's not used directly, except to create
    37  	// Inputs; it would be perfectly reasonable to wrap a Manifold
    38  	// to report an extra Input instead.
    39  	APIConfigWatcherName string
    40  
    41  	// APIOpen is passed into NewConnection, and should be used to
    42  	// create an API connection. You should probably just set it to
    43  	// the local APIOpen func.
    44  	APIOpen api.OpenFunc
    45  
    46  	// NewConnection is responsible for getting a connection from an
    47  	// agent, and may be responsible for other things that need to be
    48  	// done before anyone else gets to see the connection.
    49  	//
    50  	// You should probably set it to ScaryConnect when running a
    51  	// machine agent, and to OnlyConnect when running a model agent
    52  	// (which doesn't have its own process). Unit agents should use
    53  	// ScaryConnect at the moment; and probably switch to OnlyConnect
    54  	// when they move into machine agent processes.
    55  	NewConnection ConnectFunc
    56  
    57  	// Filter is used to specialize responses to connection errors
    58  	// made on behalf of different kinds of agent.
    59  	Filter dependency.FilterFunc
    60  
    61  	// Logger is used to write logging statements for the worker.
    62  	Logger Logger
    63  }
    64  
    65  // Manifold returns a manifold whose worker wraps an API connection
    66  // made as configured.
    67  func Manifold(config ManifoldConfig) dependency.Manifold {
    68  	inputs := []string{config.AgentName}
    69  	if config.APIConfigWatcherName != "" {
    70  		// config.APIConfigWatcherName is only applicable to unit
    71  		// and machine scoped manifold.
    72  		// It will be empty for model manifolds.
    73  		inputs = append(inputs, config.APIConfigWatcherName)
    74  	}
    75  	return dependency.Manifold{
    76  		Inputs: inputs,
    77  		Output: outputFunc,
    78  		Start:  config.startFunc(),
    79  		Filter: config.Filter,
    80  	}
    81  }
    82  
    83  // startFunc returns a StartFunc that creates a connection based on the
    84  // supplied manifold config and wraps it in a worker.
    85  func (config ManifoldConfig) startFunc() dependency.StartFunc {
    86  	return func(context dependency.Context) (worker.Worker, error) {
    87  		var agent agent.Agent
    88  		if err := context.Get(config.AgentName, &agent); err != nil {
    89  			return nil, err
    90  		}
    91  
    92  		conn, err := config.NewConnection(agent, config.APIOpen, config.Logger)
    93  		if errors.Cause(err) == ErrChangedPassword {
    94  			return nil, dependency.ErrBounce
    95  		} else if err != nil {
    96  			cfg := agent.CurrentConfig()
    97  			return nil, errors.Annotatef(err, "[%s] %q cannot open api",
    98  				shortModelUUID(cfg.Model()), cfg.Tag().String())
    99  		}
   100  		return newAPIConnWorker(conn), nil
   101  	}
   102  }
   103  
   104  // outputFunc extracts an API connection from a *apiConnWorker.
   105  func outputFunc(in worker.Worker, out interface{}) error {
   106  	inWorker, _ := in.(*apiConnWorker)
   107  	if inWorker == nil {
   108  		return errors.Errorf("in should be a %T; got %T", inWorker, in)
   109  	}
   110  
   111  	switch outPointer := out.(type) {
   112  	case *base.APICaller:
   113  		*outPointer = inWorker.conn
   114  	case *api.Connection:
   115  		// Using api.Connection is strongly discouraged as consumers
   116  		// of this API connection should not be able to close it. This
   117  		// option is only available to support legacy upgrade steps.
   118  		*outPointer = inWorker.conn
   119  	default:
   120  		return errors.Errorf("out should be *base.APICaller or *api.Connection; got %T", out)
   121  	}
   122  	return nil
   123  }