github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/common/getstatus.go (about)

     1  // Copyright 2015 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  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/state"
    14  	"github.com/juju/juju/status"
    15  )
    16  
    17  // ErrIsNotLeader is an error for operations that require for a
    18  // unit to be the leader but it was not the case.
    19  // TODO(fwereade) why do we have an alternative implementation of ErrPerm
    20  // that is exported (implying people will be able to meaningfully check it)
    21  // but not actually handled anywhere or converted into an error code by the
    22  // api server?
    23  var ErrIsNotLeader = errors.Errorf("this unit is not the leader")
    24  
    25  // StatusGetter implements a common Status method for use by
    26  // various facades.
    27  type StatusGetter struct {
    28  	st           state.EntityFinder
    29  	getCanAccess GetAuthFunc
    30  }
    31  
    32  // NewStatusGetter returns a new StatusGetter. The GetAuthFunc will be
    33  // used on each invocation of Status to determine current
    34  // permissions.
    35  func NewStatusGetter(st state.EntityFinder, getCanAccess GetAuthFunc) *StatusGetter {
    36  	return &StatusGetter{
    37  		st:           st,
    38  		getCanAccess: getCanAccess,
    39  	}
    40  }
    41  
    42  func (s *StatusGetter) getEntityStatus(tag names.Tag) params.StatusResult {
    43  	var result params.StatusResult
    44  	entity, err := s.st.FindEntity(tag)
    45  	if err != nil {
    46  		result.Error = ServerError(err)
    47  		return result
    48  	}
    49  	switch getter := entity.(type) {
    50  	case status.StatusGetter:
    51  		statusInfo, err := getter.Status()
    52  		result.Status = statusInfo.Status.String()
    53  		result.Info = statusInfo.Message
    54  		result.Data = statusInfo.Data
    55  		result.Since = statusInfo.Since
    56  		result.Error = ServerError(err)
    57  	default:
    58  		result.Error = ServerError(NotSupportedError(tag, fmt.Sprintf("getting status, %T", getter)))
    59  	}
    60  	return result
    61  }
    62  
    63  // Status returns the status of each given entity.
    64  func (s *StatusGetter) Status(args params.Entities) (params.StatusResults, error) {
    65  	result := params.StatusResults{
    66  		Results: make([]params.StatusResult, len(args.Entities)),
    67  	}
    68  	canAccess, err := s.getCanAccess()
    69  	if err != nil {
    70  		return params.StatusResults{}, err
    71  	}
    72  	for i, entity := range args.Entities {
    73  		tag, err := names.ParseTag(entity.Tag)
    74  		if err != nil {
    75  			result.Results[i].Error = ServerError(err)
    76  			continue
    77  		}
    78  		if !canAccess(tag) {
    79  			result.Results[i].Error = ServerError(ErrPerm)
    80  			continue
    81  		}
    82  		result.Results[i] = s.getEntityStatus(tag)
    83  	}
    84  	return result, nil
    85  }
    86  
    87  // ServiceStatusGetter is a StatusGetter for combined service and unit statuses.
    88  // TODO(fwereade) this is completely evil and should never have been created.
    89  // We have a perfectly adequate StatusGetter already, that accepts bulk args;
    90  // all this does is break the user model, break the api model, and lie about
    91  // unit statuses).
    92  type ServiceStatusGetter struct {
    93  	st           *state.State
    94  	getCanAccess GetAuthFunc
    95  }
    96  
    97  // NewServiceStatusGetter returns a ServiceStatusGetter.
    98  func NewServiceStatusGetter(st *state.State, getCanAccess GetAuthFunc) *ServiceStatusGetter {
    99  	return &ServiceStatusGetter{
   100  		st:           st,
   101  		getCanAccess: getCanAccess,
   102  	}
   103  }
   104  
   105  // Status returns the status of the Service for each given Unit tag.
   106  func (s *ServiceStatusGetter) Status(args params.Entities) (params.ApplicationStatusResults, error) {
   107  	result := params.ApplicationStatusResults{
   108  		Results: make([]params.ApplicationStatusResult, len(args.Entities)),
   109  	}
   110  	canAccess, err := s.getCanAccess()
   111  	if err != nil {
   112  		return params.ApplicationStatusResults{}, err
   113  	}
   114  
   115  	for i, arg := range args.Entities {
   116  		// TODO(fwereade): the auth is basically nonsense, and basically only
   117  		// works by coincidence (and is happening at the wrong layer anyway).
   118  		// Read carefully.
   119  
   120  		// We "know" that arg.Tag is either the calling unit or its service
   121  		// (because getCanAccess is authUnitOrService, and we'll fail out if
   122  		// it isn't); and, in practice, it's always going to be the calling
   123  		// unit (because, /sigh, we don't actually use service tags to refer
   124  		// to services in this method).
   125  		tag, err := names.ParseTag(arg.Tag)
   126  		if err != nil {
   127  			result.Results[i].Error = ServerError(err)
   128  			continue
   129  		}
   130  		if !canAccess(tag) {
   131  			result.Results[i].Error = ServerError(ErrPerm)
   132  			continue
   133  		}
   134  		unitTag, ok := tag.(names.UnitTag)
   135  		if !ok {
   136  			// No matter what the canAccess says, if this entity is not
   137  			// a unit, we say "NO".
   138  			result.Results[i].Error = ServerError(ErrPerm)
   139  			continue
   140  		}
   141  		unitId := unitTag.Id()
   142  
   143  		// Now we have the unit, we can get the service that should have been
   144  		// specified in the first place...
   145  		serviceId, err := names.UnitApplication(unitId)
   146  		if err != nil {
   147  			result.Results[i].Error = ServerError(err)
   148  			continue
   149  		}
   150  		service, err := s.st.Application(serviceId)
   151  		if err != nil {
   152  			result.Results[i].Error = ServerError(err)
   153  			continue
   154  		}
   155  
   156  		// ...so we can check the unit's service leadership...
   157  		checker := s.st.LeadershipChecker()
   158  		token := checker.LeadershipCheck(serviceId, unitId)
   159  		if err := token.Check(nil); err != nil {
   160  			// TODO(fwereade) this should probably be ErrPerm is certain cases,
   161  			// but I don't think I implemented an exported ErrNotLeader. I
   162  			// should have done, though.
   163  			result.Results[i].Error = ServerError(err)
   164  			continue
   165  		}
   166  
   167  		// ...and collect the results.
   168  		serviceStatus, unitStatuses, err := service.ServiceAndUnitsStatus()
   169  		if err != nil {
   170  			result.Results[i].Application.Error = ServerError(err)
   171  			result.Results[i].Error = ServerError(err)
   172  			continue
   173  		}
   174  		result.Results[i].Application.Status = serviceStatus.Status.String()
   175  		result.Results[i].Application.Info = serviceStatus.Message
   176  		result.Results[i].Application.Data = serviceStatus.Data
   177  		result.Results[i].Application.Since = serviceStatus.Since
   178  
   179  		result.Results[i].Units = make(map[string]params.StatusResult, len(unitStatuses))
   180  		for uTag, r := range unitStatuses {
   181  			ur := params.StatusResult{
   182  				Status: r.Status.String(),
   183  				Info:   r.Message,
   184  				Data:   r.Data,
   185  				Since:  r.Since,
   186  			}
   187  			result.Results[i].Units[uTag] = ur
   188  		}
   189  	}
   190  	return result, nil
   191  }
   192  
   193  // EntityStatusFromState converts a state.StatusInfo into a params.EntityStatus.
   194  func EntityStatusFromState(statusInfo status.StatusInfo) params.EntityStatus {
   195  	return params.EntityStatus{
   196  		Status: statusInfo.Status,
   197  		Info:   statusInfo.Message,
   198  		Data:   statusInfo.Data,
   199  		Since:  statusInfo.Since,
   200  	}
   201  }