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