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