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 }