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