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