github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/agent/upgradeseries/upgradeseries.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package upgradeseries
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names/v5"
    11  
    12  	"github.com/juju/juju/api/base"
    13  	"github.com/juju/juju/api/common"
    14  	corebase "github.com/juju/juju/core/base"
    15  	"github.com/juju/juju/core/model"
    16  	"github.com/juju/juju/core/status"
    17  	"github.com/juju/juju/rpc/params"
    18  )
    19  
    20  const upgradeSeriesFacade = "UpgradeSeries"
    21  
    22  // Client provides access to the UpgradeSeries API facade.
    23  type Client struct {
    24  	*common.UpgradeSeriesAPI
    25  	*common.LeadershipPinningAPI
    26  
    27  	facade base.FacadeCaller
    28  	// authTag contains the authenticated unit/machine tag.
    29  	authTag names.Tag
    30  }
    31  
    32  // NewClient Constructs an API caller.
    33  func NewClient(caller base.APICaller, authTag names.Tag) *Client {
    34  	facadeCaller := base.NewFacadeCaller(
    35  		caller,
    36  		upgradeSeriesFacade,
    37  	)
    38  	return &Client{
    39  		facade:               facadeCaller,
    40  		authTag:              authTag,
    41  		UpgradeSeriesAPI:     common.NewUpgradeSeriesAPI(facadeCaller, authTag),
    42  		LeadershipPinningAPI: common.NewLeadershipPinningAPIFromFacade(facadeCaller),
    43  	}
    44  }
    45  
    46  // MachineStatus status retrieves the machine status from remote state.
    47  func (s *Client) MachineStatus() (model.UpgradeSeriesStatus, error) {
    48  	var results params.UpgradeSeriesStatusResults
    49  	args := params.Entities{
    50  		Entities: []params.Entity{{Tag: s.authTag.String()}},
    51  	}
    52  
    53  	err := s.facade.FacadeCall("MachineStatus", args, &results)
    54  	if err != nil {
    55  		return "", errors.Trace(err)
    56  	}
    57  	if len(results.Results) != 1 {
    58  		return "", errors.Errorf("expected 1 result, got %d", len(results.Results))
    59  	}
    60  
    61  	r := results.Results[0]
    62  	if r.Error == nil {
    63  		return r.Status, nil
    64  	}
    65  
    66  	if params.IsCodeNotFound(r.Error) {
    67  		return "", errors.NewNotFound(r.Error, "")
    68  	}
    69  	return "", errors.Trace(r.Error)
    70  }
    71  
    72  // UnitsPrepared returns the units running on this machine that have
    73  // completed their upgrade-machine preparation, and are ready to be stopped and
    74  // have their unit agent services converted for the target series.
    75  func (s *Client) UnitsPrepared() ([]names.UnitTag, error) {
    76  	units, err := s.unitsInState("UnitsPrepared")
    77  	return units, errors.Trace(err)
    78  }
    79  
    80  // UnitsCompleted returns the units running on this machine that have completed
    81  // the upgrade-machine workflow and are in their normal running state.
    82  func (s *Client) UnitsCompleted() ([]names.UnitTag, error) {
    83  	units, err := s.unitsInState("UnitsCompleted")
    84  	return units, errors.Trace(err)
    85  }
    86  
    87  func (s *Client) unitsInState(facadeMethod string) ([]names.UnitTag, error) {
    88  	var results params.EntitiesResults
    89  	args := params.Entities{
    90  		Entities: []params.Entity{{Tag: s.authTag.String()}},
    91  	}
    92  
    93  	err := s.facade.FacadeCall(facadeMethod, args, &results)
    94  	if err != nil {
    95  		return nil, errors.Trace(err)
    96  	}
    97  	if len(results.Results) != 1 {
    98  		return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
    99  	}
   100  
   101  	r := results.Results[0]
   102  	if r.Error == nil {
   103  		tags := make([]names.UnitTag, len(r.Entities))
   104  		for i, e := range r.Entities {
   105  			tag, err := names.ParseUnitTag(e.Tag)
   106  			if err != nil {
   107  				return nil, errors.Trace(err)
   108  			}
   109  			tags[i] = tag
   110  		}
   111  		return tags, nil
   112  	}
   113  
   114  	if params.IsCodeNotFound(r.Error) {
   115  		return nil, errors.NewNotFound(r.Error, "")
   116  	}
   117  	return nil, errors.Trace(r.Error)
   118  }
   119  
   120  // SetMachineStatus sets the series upgrade status in remote state.
   121  func (s *Client) SetMachineStatus(status model.UpgradeSeriesStatus, reason string) error {
   122  	var results params.ErrorResults
   123  	args := params.UpgradeSeriesStatusParams{
   124  		Params: []params.UpgradeSeriesStatusParam{{
   125  			Entity:  params.Entity{Tag: s.authTag.String()},
   126  			Status:  status,
   127  			Message: reason,
   128  		}},
   129  	}
   130  
   131  	err := s.facade.FacadeCall("SetMachineStatus", args, &results)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	if len(results.Results) != 1 {
   136  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   137  	}
   138  
   139  	result := results.Results[0]
   140  	if result.Error != nil {
   141  		return result.Error
   142  	}
   143  	return nil
   144  }
   145  
   146  // StartUnitCompletion starts the complete phase for all subordinate units.
   147  func (s *Client) StartUnitCompletion(reason string) error {
   148  	var results params.ErrorResults
   149  	args := params.UpgradeSeriesStartUnitCompletionParam{
   150  		Entities: []params.Entity{{Tag: s.authTag.String()}},
   151  		Message:  reason,
   152  	}
   153  
   154  	err := s.facade.FacadeCall("StartUnitCompletion", args, &results)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	if len(results.Results) != 1 {
   159  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   160  	}
   161  
   162  	result := results.Results[0]
   163  	if result.Error != nil {
   164  		return result.Error
   165  	}
   166  	return nil
   167  }
   168  
   169  // FinishUpgradeSeries notifies the controller that the upgrade process is
   170  // completely finished, passing the current host OS series.
   171  // We use the name "Finish" to distinguish this method from the various
   172  // "Complete" phases.
   173  func (s *Client) FinishUpgradeSeries(hostBase corebase.Base) error {
   174  	var results params.ErrorResults
   175  	args := params.UpdateChannelArgs{Args: []params.UpdateChannelArg{{
   176  		Entity:  params.Entity{Tag: s.authTag.String()},
   177  		Channel: hostBase.Channel.Track,
   178  	}}}
   179  
   180  	err := s.facade.FacadeCall("FinishUpgradeSeries", args, &results)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	if len(results.Results) != 1 {
   185  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   186  	}
   187  
   188  	result := results.Results[0]
   189  	if result.Error != nil {
   190  		return result.Error
   191  	}
   192  	return nil
   193  }
   194  
   195  // SetInstanceStatus sets the machine status in remote state.
   196  func (s *Client) SetInstanceStatus(sts model.UpgradeSeriesStatus, msg string) error {
   197  	var results params.ErrorResults
   198  	args := params.SetStatus{
   199  		Entities: []params.EntityStatusArgs{{
   200  			Tag:    s.authTag.String(),
   201  			Status: string(status.Running),
   202  			Info:   strings.Join([]string{"series upgrade ", string(sts), ": ", msg}, ""),
   203  		}},
   204  	}
   205  
   206  	err := s.facade.FacadeCall("SetInstanceStatus", args, &results)
   207  	if err != nil {
   208  		return err
   209  	}
   210  	if len(results.Results) != 1 {
   211  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   212  	}
   213  
   214  	result := results.Results[0]
   215  	if result.Error != nil {
   216  		return result.Error
   217  	}
   218  	return nil
   219  }