github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/setstatus.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/core/status"
    15  	"github.com/juju/juju/state"
    16  )
    17  
    18  // ApplicationStatusSetter implements a SetApplicationStatus method to be
    19  // used by facades that can change an application status.
    20  // This is only slightly less evil than ApplicationStatusGetter. We have
    21  // StatusSetter already; all this does is set the status for the wrong
    22  // entity, and render the auth so confused as to be ~worthless.
    23  type ApplicationStatusSetter struct {
    24  	st           *state.State
    25  	getCanModify GetAuthFunc
    26  }
    27  
    28  // NewServiceStatusSetter returns a ServiceStatusSetter.
    29  func NewServiceStatusSetter(st *state.State, getCanModify GetAuthFunc) *ApplicationStatusSetter {
    30  	return &ApplicationStatusSetter{
    31  		st:           st,
    32  		getCanModify: getCanModify,
    33  	}
    34  }
    35  
    36  // SetStatus sets the status on the service given by the unit in args if the unit is the leader.
    37  func (s *ApplicationStatusSetter) SetStatus(args params.SetStatus) (params.ErrorResults, error) {
    38  	result := params.ErrorResults{
    39  		Results: make([]params.ErrorResult, len(args.Entities)),
    40  	}
    41  	if len(args.Entities) == 0 {
    42  		return result, nil
    43  	}
    44  
    45  	canModify, err := s.getCanModify()
    46  	if err != nil {
    47  		return params.ErrorResults{}, err
    48  	}
    49  
    50  	for i, arg := range args.Entities {
    51  
    52  		// TODO(fwereade): the auth is basically nonsense, and basically only
    53  		// works by coincidence. Read carefully.
    54  
    55  		// We "know" that arg.Tag is either the calling unit or its service
    56  		// (because getCanModify is authUnitOrService, and we'll fail out if
    57  		// it isn't); and, in practice, it's always going to be the calling
    58  		// unit (because, /sigh, we don't actually use service tags to refer
    59  		// to services in this method).
    60  		tag, err := names.ParseTag(arg.Tag)
    61  		if err != nil {
    62  			result.Results[i].Error = ServerError(err)
    63  			continue
    64  		}
    65  		if !canModify(tag) {
    66  			result.Results[i].Error = ServerError(ErrPerm)
    67  			continue
    68  		}
    69  		unitTag, ok := tag.(names.UnitTag)
    70  		if !ok {
    71  			// No matter what the canModify says, if this entity is not
    72  			// a unit, we say "NO".
    73  			result.Results[i].Error = ServerError(ErrPerm)
    74  			continue
    75  		}
    76  		unitId := unitTag.Id()
    77  
    78  		// Now we have the unit, we can get the service that should have been
    79  		// specified in the first place...
    80  		serviceId, err := names.UnitApplication(unitId)
    81  		if err != nil {
    82  			result.Results[i].Error = ServerError(err)
    83  			continue
    84  		}
    85  		service, err := s.st.Application(serviceId)
    86  		if err != nil {
    87  			result.Results[i].Error = ServerError(err)
    88  			continue
    89  		}
    90  
    91  		// ...and set the status, conditional on the unit being (and remaining)
    92  		// service leader.
    93  		checker := s.st.LeadershipChecker()
    94  		token := checker.LeadershipCheck(serviceId, unitId)
    95  
    96  		// TODO(fwereade) pass token into SetStatus instead of checking here.
    97  		if err := token.Check(0, nil); err != nil {
    98  			// TODO(fwereade) this should probably be ErrPerm is certain cases,
    99  			// but I don't think I implemented an exported ErrNotLeader. I
   100  			// should have done, though.
   101  			result.Results[i].Error = ServerError(err)
   102  			continue
   103  		}
   104  		// TODO(perrito666) 2016-05-02 lp:1558657
   105  		now := time.Now()
   106  		sInfo := status.StatusInfo{
   107  			Status:  status.Status(arg.Status),
   108  			Message: arg.Info,
   109  			Data:    arg.Data,
   110  			Since:   &now,
   111  		}
   112  		if err := service.SetStatus(sInfo); err != nil {
   113  			result.Results[i].Error = ServerError(err)
   114  		}
   115  
   116  	}
   117  	return result, nil
   118  }
   119  
   120  // StatusSetter implements a common SetStatus method for use by
   121  // various facades.
   122  type StatusSetter struct {
   123  	st           state.EntityFinder
   124  	getCanModify GetAuthFunc
   125  }
   126  
   127  // NewStatusSetter returns a new StatusSetter. The GetAuthFunc will be
   128  // used on each invocation of SetStatus to determine current
   129  // permissions.
   130  func NewStatusSetter(st state.EntityFinder, getCanModify GetAuthFunc) *StatusSetter {
   131  	return &StatusSetter{
   132  		st:           st,
   133  		getCanModify: getCanModify,
   134  	}
   135  }
   136  
   137  func (s *StatusSetter) setEntityStatus(tag names.Tag, entityStatus status.Status, info string, data map[string]interface{}, updated *time.Time) error {
   138  	entity, err := s.st.FindEntity(tag)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	switch entity := entity.(type) {
   143  	case *state.Application:
   144  		return ErrPerm
   145  	case status.StatusSetter:
   146  		sInfo := status.StatusInfo{
   147  			Status:  entityStatus,
   148  			Message: info,
   149  			Data:    data,
   150  			Since:   updated,
   151  		}
   152  		return entity.SetStatus(sInfo)
   153  	default:
   154  		return NotSupportedError(tag, fmt.Sprintf("setting status, %T", entity))
   155  	}
   156  }
   157  
   158  // SetStatus sets the status of each given entity.
   159  func (s *StatusSetter) SetStatus(args params.SetStatus) (params.ErrorResults, error) {
   160  	result := params.ErrorResults{
   161  		Results: make([]params.ErrorResult, len(args.Entities)),
   162  	}
   163  	if len(args.Entities) == 0 {
   164  		return result, nil
   165  	}
   166  	canModify, err := s.getCanModify()
   167  	if err != nil {
   168  		return params.ErrorResults{}, err
   169  	}
   170  	// TODO(perrito666) 2016-05-02 lp:1558657
   171  	now := time.Now()
   172  	for i, arg := range args.Entities {
   173  		tag, err := names.ParseTag(arg.Tag)
   174  		if err != nil {
   175  			result.Results[i].Error = ServerError(err)
   176  			continue
   177  		}
   178  		err = ErrPerm
   179  		if canModify(tag) {
   180  			err = s.setEntityStatus(tag, status.Status(arg.Status), arg.Info, arg.Data, &now)
   181  		}
   182  		result.Results[i].Error = ServerError(err)
   183  	}
   184  	return result, nil
   185  }
   186  
   187  func (s *StatusSetter) updateEntityStatusData(tag names.Tag, data map[string]interface{}) error {
   188  	entity0, err := s.st.FindEntity(tag)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	statusGetter, ok := entity0.(status.StatusGetter)
   193  	if !ok {
   194  		return NotSupportedError(tag, "getting status")
   195  	}
   196  	existingStatusInfo, err := statusGetter.Status()
   197  	if err != nil {
   198  		return err
   199  	}
   200  	newData := existingStatusInfo.Data
   201  	if newData == nil {
   202  		newData = data
   203  	} else {
   204  		for k, v := range data {
   205  			newData[k] = v
   206  		}
   207  	}
   208  	entity, ok := entity0.(status.StatusSetter)
   209  	if !ok {
   210  		return NotSupportedError(tag, "updating status")
   211  	}
   212  	if len(newData) > 0 && existingStatusInfo.Status != status.Error {
   213  		return fmt.Errorf("%s is not in an error state", names.ReadableString(tag))
   214  	}
   215  	// TODO(perrito666) 2016-05-02 lp:1558657
   216  	now := time.Now()
   217  	sInfo := status.StatusInfo{
   218  		Status:  existingStatusInfo.Status,
   219  		Message: existingStatusInfo.Message,
   220  		Data:    newData,
   221  		Since:   &now,
   222  	}
   223  	return entity.SetStatus(sInfo)
   224  }
   225  
   226  // UpdateStatus updates the status data of each given entity.
   227  // TODO(fwereade): WTF. This method exists *only* for the convenience of the
   228  // *client* API -- and is itself completely broken -- but we still expose it
   229  // in every facade with a StatusSetter? FFS.
   230  func (s *StatusSetter) UpdateStatus(args params.SetStatus) (params.ErrorResults, error) {
   231  	result := params.ErrorResults{
   232  		Results: make([]params.ErrorResult, len(args.Entities)),
   233  	}
   234  	if len(args.Entities) == 0 {
   235  		return result, nil
   236  	}
   237  	canModify, err := s.getCanModify()
   238  	if err != nil {
   239  		return params.ErrorResults{}, errors.Trace(err)
   240  	}
   241  	for i, arg := range args.Entities {
   242  		tag, err := names.ParseTag(arg.Tag)
   243  		if err != nil {
   244  			result.Results[i].Error = ServerError(ErrPerm)
   245  			continue
   246  		}
   247  		err = ErrPerm
   248  		if canModify(tag) {
   249  			err = s.updateEntityStatusData(tag, arg.Data)
   250  		}
   251  		result.Results[i].Error = ServerError(err)
   252  	}
   253  	return result, nil
   254  }
   255  
   256  // UnitAgentFinder is a state.EntityFinder that finds unit agents.
   257  type UnitAgentFinder struct {
   258  	state.EntityFinder
   259  }
   260  
   261  // FindEntity implements state.EntityFinder and returns unit agents.
   262  func (ua *UnitAgentFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   263  	_, ok := tag.(names.UnitTag)
   264  	if !ok {
   265  		return nil, errors.Errorf("unsupported tag %T", tag)
   266  	}
   267  	entity, err := ua.EntityFinder.FindEntity(tag)
   268  	if err != nil {
   269  		return nil, errors.Trace(err)
   270  	}
   271  	// this returns a state.Unit, but for testing we just cast to the minimal
   272  	// interface we need.
   273  	return entity.(hasAgent).Agent(), nil
   274  }
   275  
   276  type hasAgent interface {
   277  	Agent() *state.UnitAgent
   278  }