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 }