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

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/collections/transform"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  
    13  	"github.com/juju/juju/api"
    14  	"github.com/juju/juju/api/base"
    15  	"github.com/juju/juju/api/common"
    16  	apiwatcher "github.com/juju/juju/api/watcher"
    17  	apiservererrors "github.com/juju/juju/apiserver/errors"
    18  	"github.com/juju/juju/core/application"
    19  	"github.com/juju/juju/core/life"
    20  	"github.com/juju/juju/core/model"
    21  	"github.com/juju/juju/core/network"
    22  	"github.com/juju/juju/core/relation"
    23  	"github.com/juju/juju/core/watcher"
    24  	"github.com/juju/juju/rpc/params"
    25  )
    26  
    27  const uniterFacade = "Uniter"
    28  
    29  // State provides access to the Uniter API facade.
    30  type State struct {
    31  	*common.ModelWatcher
    32  	*common.APIAddresser
    33  	*common.UpgradeSeriesAPI
    34  	*common.UnitStateAPI
    35  	*StorageAccessor
    36  
    37  	LeadershipSettings *LeadershipSettingsAccessor
    38  	facade             base.FacadeCaller
    39  	// unitTag contains the authenticated unit's tag.
    40  	unitTag names.UnitTag
    41  }
    42  
    43  // NewState creates a new client-side Uniter facade.
    44  func NewState(
    45  	caller base.APICaller,
    46  	authTag names.UnitTag,
    47  ) *State {
    48  	facadeCaller := base.NewFacadeCaller(
    49  		caller,
    50  		uniterFacade,
    51  	)
    52  	state := &State{
    53  		ModelWatcher:     common.NewModelWatcher(facadeCaller),
    54  		APIAddresser:     common.NewAPIAddresser(facadeCaller),
    55  		UpgradeSeriesAPI: common.NewUpgradeSeriesAPI(facadeCaller, authTag),
    56  		UnitStateAPI:     common.NewUniterStateAPI(facadeCaller, authTag),
    57  		StorageAccessor:  NewStorageAccessor(facadeCaller),
    58  		facade:           facadeCaller,
    59  		unitTag:          authTag,
    60  	}
    61  
    62  	newWatcher := func(result params.NotifyWatchResult) watcher.NotifyWatcher {
    63  		return apiwatcher.NewNotifyWatcher(caller, result)
    64  	}
    65  	state.LeadershipSettings = NewLeadershipSettingsAccessor(
    66  		facadeCaller.FacadeCall,
    67  		newWatcher,
    68  	)
    69  	return state
    70  }
    71  
    72  // NewFromConnection returns a version of the Connection that provides
    73  // functionality required by the uniter worker if possible else a non-nil error.
    74  func NewFromConnection(c api.Connection) (*State, error) {
    75  	authTag := c.AuthTag()
    76  	unitTag, ok := authTag.(names.UnitTag)
    77  	if !ok {
    78  		return nil, errors.Errorf("expected UnitTag, got %T %v", authTag, authTag)
    79  	}
    80  	return NewState(c, unitTag), nil
    81  }
    82  
    83  // BestAPIVersion returns the API version that we were able to
    84  // determine is supported by both the client and the API Server.
    85  func (st *State) BestAPIVersion() int {
    86  	return st.facade.BestAPIVersion()
    87  }
    88  
    89  // Facade returns the current facade.
    90  func (st *State) Facade() base.FacadeCaller {
    91  	return st.facade
    92  }
    93  
    94  // life requests the lifecycle of the given entity from the server.
    95  func (st *State) life(tag names.Tag) (life.Value, error) {
    96  	return common.OneLife(st.facade, tag)
    97  }
    98  
    99  // relation requests relation information from the server.
   100  func (st *State) relation(relationTag, unitTag names.Tag) (params.RelationResult, error) {
   101  	nothing := params.RelationResult{}
   102  	var result params.RelationResults
   103  	args := params.RelationUnits{
   104  		RelationUnits: []params.RelationUnit{
   105  			{Relation: relationTag.String(), Unit: unitTag.String()},
   106  		},
   107  	}
   108  	err := st.facade.FacadeCall("Relation", args, &result)
   109  	if err != nil {
   110  		return nothing, err
   111  	}
   112  	if len(result.Results) != 1 {
   113  		return nothing, fmt.Errorf("expected 1 result, got %d", len(result.Results))
   114  	}
   115  	if err := result.Results[0].Error; err != nil {
   116  		return nothing, err
   117  	}
   118  	return result.Results[0], nil
   119  }
   120  
   121  func (st *State) setRelationStatus(id int, status relation.Status) error {
   122  	args := params.RelationStatusArgs{
   123  		Args: []params.RelationStatusArg{{
   124  			UnitTag:    st.unitTag.String(),
   125  			RelationId: id,
   126  			Status:     params.RelationStatusValue(status),
   127  		}},
   128  	}
   129  	var results params.ErrorResults
   130  	if err := st.facade.FacadeCall("SetRelationStatus", args, &results); err != nil {
   131  		return errors.Trace(err)
   132  	}
   133  	return results.OneError()
   134  }
   135  
   136  // getOneAction retrieves a single Action from the controller.
   137  func (st *State) getOneAction(tag *names.ActionTag) (params.ActionResult, error) {
   138  	nothing := params.ActionResult{}
   139  
   140  	args := params.Entities{
   141  		Entities: []params.Entity{
   142  			{Tag: tag.String()},
   143  		},
   144  	}
   145  
   146  	var results params.ActionResults
   147  	err := st.facade.FacadeCall("Actions", args, &results)
   148  	if err != nil {
   149  		return nothing, err
   150  	}
   151  
   152  	if len(results.Results) > 1 {
   153  		return nothing, fmt.Errorf("expected only 1 action query result, got %d", len(results.Results))
   154  	}
   155  
   156  	// handle server errors
   157  	result := results.Results[0]
   158  	if err := result.Error; err != nil {
   159  		return nothing, err
   160  	}
   161  
   162  	return result, nil
   163  }
   164  
   165  // ActionStatus provides the status of a single action.
   166  func (st *State) ActionStatus(tag names.ActionTag) (string, error) {
   167  	args := params.Entities{
   168  		Entities: []params.Entity{
   169  			{Tag: tag.String()},
   170  		},
   171  	}
   172  
   173  	var results params.StringResults
   174  	err := st.facade.FacadeCall("ActionStatus", args, &results)
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  
   179  	if len(results.Results) > 1 {
   180  		return "", fmt.Errorf("expected only 1 action query result, got %d", len(results.Results))
   181  	}
   182  
   183  	// handle server errors
   184  	result := results.Results[0]
   185  	if err := result.Error; err != nil {
   186  		return "", err
   187  	}
   188  
   189  	return result.Result, nil
   190  }
   191  
   192  // Unit provides access to methods of a state.Unit through the facade.
   193  func (st *State) Unit(tag names.UnitTag) (*Unit, error) {
   194  	unit := &Unit{
   195  		tag: tag,
   196  		st:  st,
   197  	}
   198  	err := unit.Refresh()
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return unit, nil
   203  }
   204  
   205  // Application returns an application state by tag.
   206  func (st *State) Application(tag names.ApplicationTag) (*Application, error) {
   207  	life, err := st.life(tag)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	return &Application{
   212  		tag:  tag,
   213  		life: life,
   214  		st:   st,
   215  	}, nil
   216  }
   217  
   218  // ProviderType returns a provider type used by the current juju model.
   219  //
   220  // TODO(dimitern): We might be able to drop this, once we have machine
   221  // addresses implemented fully. See also LP bug 1221798.
   222  func (st *State) ProviderType() (string, error) {
   223  	var result params.StringResult
   224  	err := st.facade.FacadeCall("ProviderType", nil, &result)
   225  	if err != nil {
   226  		return "", err
   227  	}
   228  	if err := result.Error; err != nil {
   229  		return "", err
   230  	}
   231  	return result.Result, nil
   232  }
   233  
   234  // Charm returns the charm with the given URL.
   235  func (st *State) Charm(curl string) (*Charm, error) {
   236  	if curl == "" {
   237  		return nil, fmt.Errorf("charm url cannot be empty")
   238  	}
   239  	return &Charm{
   240  		st:   st,
   241  		curl: curl,
   242  	}, nil
   243  }
   244  
   245  // Relation returns the existing relation with the given tag.
   246  func (st *State) Relation(relationTag names.RelationTag) (*Relation, error) {
   247  	result, err := st.relation(relationTag, st.unitTag)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	return &Relation{
   252  		id:        result.Id,
   253  		tag:       relationTag,
   254  		life:      result.Life,
   255  		suspended: result.Suspended,
   256  		st:        st,
   257  		otherApp:  result.OtherApplication,
   258  	}, nil
   259  }
   260  
   261  // Action returns the Action with the given tag.
   262  func (st *State) Action(tag names.ActionTag) (*Action, error) {
   263  	result, err := st.getOneAction(&tag)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	a := &Action{
   268  		id:     tag.Id(),
   269  		name:   result.Action.Name,
   270  		params: result.Action.Parameters,
   271  	}
   272  	if result.Action.Parallel != nil {
   273  		a.parallel = *result.Action.Parallel
   274  	}
   275  	if result.Action.ExecutionGroup != nil {
   276  		a.executionGroup = *result.Action.ExecutionGroup
   277  	}
   278  	return a, nil
   279  }
   280  
   281  // ActionBegin marks an action as running.
   282  func (st *State) ActionBegin(tag names.ActionTag) error {
   283  	var outcome params.ErrorResults
   284  
   285  	args := params.Entities{
   286  		Entities: []params.Entity{
   287  			{Tag: tag.String()},
   288  		},
   289  	}
   290  
   291  	err := st.facade.FacadeCall("BeginActions", args, &outcome)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	if len(outcome.Results) != 1 {
   296  		return fmt.Errorf("expected 1 result, got %d", len(outcome.Results))
   297  	}
   298  	result := outcome.Results[0]
   299  	if result.Error != nil {
   300  		return result.Error
   301  	}
   302  	return nil
   303  }
   304  
   305  // ActionFinish captures the structured output of an action.
   306  func (st *State) ActionFinish(tag names.ActionTag, status string, results map[string]interface{}, message string) error {
   307  	var outcome params.ErrorResults
   308  
   309  	args := params.ActionExecutionResults{
   310  		Results: []params.ActionExecutionResult{
   311  			{
   312  				ActionTag: tag.String(),
   313  				Status:    status,
   314  				Results:   results,
   315  				Message:   message,
   316  			},
   317  		},
   318  	}
   319  
   320  	err := st.facade.FacadeCall("FinishActions", args, &outcome)
   321  	if err != nil {
   322  		return err
   323  	}
   324  	if len(outcome.Results) != 1 {
   325  		return fmt.Errorf("expected 1 result, got %d", len(outcome.Results))
   326  	}
   327  	result := outcome.Results[0]
   328  	if result.Error != nil {
   329  		return result.Error
   330  	}
   331  	return nil
   332  }
   333  
   334  // RelationById returns the existing relation with the given id.
   335  func (st *State) RelationById(id int) (*Relation, error) {
   336  	var results params.RelationResults
   337  	args := params.RelationIds{
   338  		RelationIds: []int{id},
   339  	}
   340  
   341  	err := st.facade.FacadeCall("RelationById", args, &results)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	if len(results.Results) != 1 {
   346  		return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))
   347  	}
   348  	result := results.Results[0]
   349  	if err := result.Error; err != nil {
   350  		return nil, err
   351  	}
   352  	relationTag := names.NewRelationTag(result.Key)
   353  	return &Relation{
   354  		id:        result.Id,
   355  		tag:       relationTag,
   356  		life:      result.Life,
   357  		suspended: result.Suspended,
   358  		st:        st,
   359  		otherApp:  result.OtherApplication,
   360  	}, nil
   361  }
   362  
   363  // Model returns the model entity.
   364  func (st *State) Model() (*model.Model, error) {
   365  	var result params.ModelResult
   366  	err := st.facade.FacadeCall("CurrentModel", nil, &result)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	if err := result.Error; err != nil {
   371  		return nil, err
   372  	}
   373  	modelType := model.ModelType(result.Type)
   374  	if modelType == "" {
   375  		modelType = model.IAAS
   376  	}
   377  	return &model.Model{
   378  		Name:      result.Name,
   379  		UUID:      result.UUID,
   380  		ModelType: modelType,
   381  	}, nil
   382  }
   383  
   384  func processOpenPortRangesByEndpointResults(results params.OpenPortRangesByEndpointResults, tag names.Tag) (map[names.UnitTag]network.GroupedPortRanges, error) {
   385  	if len(results.Results) != 1 {
   386  		return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))
   387  	}
   388  	result := results.Results[0]
   389  	if result.Error != nil {
   390  		err := apiservererrors.RestoreError(result.Error)
   391  		return nil, errors.Annotatef(err, "unable to fetch opened ports for %s", tag)
   392  	}
   393  
   394  	portRangeMap := make(map[names.UnitTag]network.GroupedPortRanges)
   395  	for unitTagStr, unitPortRanges := range result.UnitPortRanges {
   396  		unitTag, err := names.ParseUnitTag(unitTagStr)
   397  		if err != nil {
   398  			return nil, errors.Trace(err)
   399  		}
   400  		portRangeMap[unitTag] = make(network.GroupedPortRanges)
   401  		for _, group := range unitPortRanges {
   402  			portRangeMap[unitTag][group.Endpoint] = transform.Slice(group.PortRanges, func(pr params.PortRange) network.PortRange {
   403  				return pr.NetworkPortRange()
   404  			})
   405  		}
   406  	}
   407  	return portRangeMap, nil
   408  }
   409  
   410  // OpenedMachinePortRangesByEndpoint returns all port ranges currently open on the given
   411  // machine, grouped by unit tag and application endpoint.
   412  func (st *State) OpenedMachinePortRangesByEndpoint(machineTag names.MachineTag) (map[names.UnitTag]network.GroupedPortRanges, error) {
   413  	var results params.OpenPortRangesByEndpointResults
   414  	args := params.Entities{
   415  		Entities: []params.Entity{{Tag: machineTag.String()}},
   416  	}
   417  	err := st.facade.FacadeCall("OpenedMachinePortRangesByEndpoint", args, &results)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  	return processOpenPortRangesByEndpointResults(results, machineTag)
   422  }
   423  
   424  // OpenedPortRangesByEndpoint returns all port ranges currently opened grouped by unit tag and application endpoint.
   425  func (st *State) OpenedPortRangesByEndpoint() (map[names.UnitTag]network.GroupedPortRanges, error) {
   426  	if st.BestAPIVersion() < 18 {
   427  		// OpenedPortRangesByEndpoint() was introduced in UniterAPIV18.
   428  		return nil, errors.NotImplementedf("OpenedPortRangesByEndpoint() (need V18+)")
   429  	}
   430  	var results params.OpenPortRangesByEndpointResults
   431  	if err := st.facade.FacadeCall("OpenedPortRangesByEndpoint", nil, &results); err != nil {
   432  		return nil, errors.Trace(err)
   433  	}
   434  	return processOpenPortRangesByEndpointResults(results, st.unitTag)
   435  }
   436  
   437  // WatchRelationUnits returns a watcher that notifies of changes to the
   438  // counterpart units in the relation for the given unit.
   439  func (st *State) WatchRelationUnits(
   440  	relationTag names.RelationTag,
   441  	unitTag names.UnitTag,
   442  ) (watcher.RelationUnitsWatcher, error) {
   443  	var results params.RelationUnitsWatchResults
   444  	args := params.RelationUnits{
   445  		RelationUnits: []params.RelationUnit{{
   446  			Relation: relationTag.String(),
   447  			Unit:     unitTag.String(),
   448  		}},
   449  	}
   450  	err := st.facade.FacadeCall("WatchRelationUnits", args, &results)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  	if len(results.Results) != 1 {
   455  		return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))
   456  	}
   457  	result := results.Results[0]
   458  	if result.Error != nil {
   459  		return nil, result.Error
   460  	}
   461  	w := apiwatcher.NewRelationUnitsWatcher(st.facade.RawAPICaller(), result)
   462  	return w, nil
   463  }
   464  
   465  // SLALevel returns the SLA level set on the model.
   466  func (st *State) SLALevel() (string, error) {
   467  	var result params.StringResult
   468  	err := st.facade.FacadeCall("SLALevel", nil, &result)
   469  	if err != nil {
   470  		return "", errors.Trace(err)
   471  	}
   472  	if err := result.Error; err != nil {
   473  		return "", errors.Trace(err)
   474  	}
   475  	return result.Result, nil
   476  }
   477  
   478  // CloudAPIVersion returns the API version of the cloud, if known.
   479  func (st *State) CloudAPIVersion() (string, error) {
   480  	var result params.StringResult
   481  	err := st.facade.FacadeCall("CloudAPIVersion", nil, &result)
   482  	if err != nil {
   483  		return "", errors.Trace(err)
   484  	}
   485  	if err := result.Error; err != nil {
   486  		return "", errors.Trace(err)
   487  	}
   488  	return result.Result, nil
   489  }
   490  
   491  // GoalState returns a GoalState struct with the charm's
   492  // peers and related units information.
   493  func (st *State) GoalState() (application.GoalState, error) {
   494  	var result params.GoalStateResults
   495  
   496  	gs := application.GoalState{}
   497  
   498  	args := params.Entities{
   499  		Entities: []params.Entity{
   500  			{Tag: st.unitTag.String()},
   501  		},
   502  	}
   503  
   504  	err := st.facade.FacadeCall("GoalStates", args, &result)
   505  	if err != nil {
   506  		return gs, err
   507  	}
   508  	if len(result.Results) != 1 {
   509  		return gs, errors.Errorf("expected 1 result, got %d", len(result.Results))
   510  	}
   511  	if err := result.Results[0].Error; err != nil {
   512  		return gs, err
   513  	}
   514  	gs = goalStateFromParams(result.Results[0].Result)
   515  	return gs, nil
   516  }
   517  
   518  func goalStateFromParams(paramsGoalState *params.GoalState) application.GoalState {
   519  	goalState := application.GoalState{}
   520  
   521  	copyUnits := func(units params.UnitsGoalState) application.UnitsGoalState {
   522  		copiedUnits := application.UnitsGoalState{}
   523  		for name, gs := range units {
   524  			copiedUnits[name] = application.GoalStateStatus{
   525  				Status: gs.Status,
   526  				Since:  gs.Since,
   527  			}
   528  		}
   529  		return copiedUnits
   530  	}
   531  
   532  	goalState.Units = copyUnits(paramsGoalState.Units)
   533  
   534  	if paramsGoalState.Relations != nil {
   535  		goalState.Relations = make(map[string]application.UnitsGoalState)
   536  		for relation, relationUnits := range paramsGoalState.Relations {
   537  			goalState.Relations[relation] = copyUnits(relationUnits)
   538  		}
   539  	}
   540  
   541  	return goalState
   542  }
   543  
   544  // GetPodSpec gets the pod spec of the specified application.
   545  func (st *State) GetPodSpec(appName string) (string, error) {
   546  	if !names.IsValidApplication(appName) {
   547  		return "", errors.NotValidf("application name %q", appName)
   548  	}
   549  	tag := names.NewApplicationTag(appName)
   550  	var result params.StringResults
   551  	args := params.Entities{
   552  		Entities: []params.Entity{{
   553  			Tag: tag.String(),
   554  		}},
   555  	}
   556  	if err := st.facade.FacadeCall("GetPodSpec", args, &result); err != nil {
   557  		return "", errors.Trace(err)
   558  	}
   559  	if len(result.Results) != 1 {
   560  		return "", fmt.Errorf("expected 1 result, got %d", len(result.Results))
   561  	}
   562  	if err := result.Results[0].Error; err != nil {
   563  		if params.IsCodeNotFound(result.Results[0].Error) {
   564  			return "", errors.NotFoundf("podspec for application %s", appName)
   565  		}
   566  		return "", err
   567  	}
   568  	return result.Results[0].Result, nil
   569  }
   570  
   571  // GetRawK8sSpec gets the raw k8s spec of the specified application.
   572  func (st *State) GetRawK8sSpec(appName string) (string, error) {
   573  	if !names.IsValidApplication(appName) {
   574  		return "", errors.NotValidf("application name %q", appName)
   575  	}
   576  	tag := names.NewApplicationTag(appName)
   577  	var result params.StringResults
   578  	args := params.Entities{
   579  		Entities: []params.Entity{{
   580  			Tag: tag.String(),
   581  		}},
   582  	}
   583  	if err := st.facade.FacadeCall("GetRawK8sSpec", args, &result); err != nil {
   584  		return "", errors.Trace(err)
   585  	}
   586  	if len(result.Results) != 1 {
   587  		return "", fmt.Errorf("expected 1 result, got %d", len(result.Results))
   588  	}
   589  	if err := result.Results[0].Error; err != nil {
   590  		if params.IsCodeNotFound(result.Results[0].Error) {
   591  			return "", errors.NotFoundf("raw k8s spec for application %s", appName)
   592  		}
   593  		return "", err
   594  	}
   595  	return result.Results[0].Result, nil
   596  }
   597  
   598  // CloudSpec returns the cloud spec for the model that calling unit or
   599  // application resides in.
   600  // If the application has not been authorised to access its cloud spec,
   601  // then an authorisation error will be returned.
   602  func (st *State) CloudSpec() (*params.CloudSpec, error) {
   603  	var result params.CloudSpecResult
   604  
   605  	err := st.facade.FacadeCall("CloudSpec", nil, &result)
   606  	if err != nil {
   607  		return nil, err
   608  	}
   609  	if err := result.Error; err != nil {
   610  		return nil, err
   611  	}
   612  	return result.Result, nil
   613  }
   614  
   615  // UnitWorkloadVersion returns the version of the workload reported by
   616  // the specified unit.
   617  func (st *State) UnitWorkloadVersion(tag names.UnitTag) (string, error) {
   618  	var results params.StringResults
   619  	args := params.Entities{
   620  		Entities: []params.Entity{{Tag: tag.String()}},
   621  	}
   622  	err := st.facade.FacadeCall("WorkloadVersion", args, &results)
   623  	if err != nil {
   624  		return "", err
   625  	}
   626  	if len(results.Results) != 1 {
   627  		return "", fmt.Errorf("expected 1 result, got %d", len(results.Results))
   628  	}
   629  	result := results.Results[0]
   630  	if result.Error != nil {
   631  		return "", result.Error
   632  	}
   633  	return result.Result, nil
   634  }
   635  
   636  // SetUnitWorkloadVersion sets the specified unit's workload version to
   637  // the provided value.
   638  func (st *State) SetUnitWorkloadVersion(tag names.UnitTag, version string) error {
   639  	var result params.ErrorResults
   640  	args := params.EntityWorkloadVersions{
   641  		Entities: []params.EntityWorkloadVersion{
   642  			{Tag: tag.String(), WorkloadVersion: version},
   643  		},
   644  	}
   645  	err := st.facade.FacadeCall("SetWorkloadVersion", args, &result)
   646  	if err != nil {
   647  		return err
   648  	}
   649  	return result.OneError()
   650  }