github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_debug.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "sort" 27 "time" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/overlord/assertstate" 31 "github.com/snapcore/snapd/overlord/auth" 32 "github.com/snapcore/snapd/overlord/devicestate" 33 "github.com/snapcore/snapd/overlord/snapstate" 34 "github.com/snapcore/snapd/overlord/state" 35 "github.com/snapcore/snapd/timings" 36 ) 37 38 var debugCmd = &Command{ 39 Path: "/v2/debug", 40 UserOK: true, 41 GET: getDebug, 42 POST: postDebug, 43 } 44 45 type debugAction struct { 46 Action string `json:"action"` 47 Message string `json:"message"` 48 Params struct { 49 ChgID string `json:"chg-id"` 50 } `json:"params"` 51 } 52 53 type ConnectivityStatus struct { 54 Connectivity bool `json:"connectivity"` 55 Unreachable []string `json:"unreachable,omitempty"` 56 } 57 58 func getBaseDeclaration(st *state.State) Response { 59 bd, err := assertstate.BaseDeclaration(st) 60 if err != nil { 61 return InternalError("cannot get base declaration: %s", err) 62 } 63 return SyncResponse(map[string]interface{}{ 64 "base-declaration": string(asserts.Encode(bd)), 65 }, nil) 66 } 67 68 func checkConnectivity(st *state.State) Response { 69 theStore := snapstate.Store(st, nil) 70 st.Unlock() 71 defer st.Lock() 72 checkResult, err := theStore.ConnectivityCheck() 73 if err != nil { 74 return InternalError("cannot run connectivity check: %v", err) 75 } 76 status := ConnectivityStatus{Connectivity: true} 77 for host, reachable := range checkResult { 78 if !reachable { 79 status.Connectivity = false 80 status.Unreachable = append(status.Unreachable, host) 81 } 82 } 83 sort.Strings(status.Unreachable) 84 85 return SyncResponse(status, nil) 86 } 87 88 type changeTimings struct { 89 Status string `json:"status,omitempty"` 90 Kind string `json:"kind,omitempty"` 91 Summary string `json:"summary,omitempty"` 92 Lane int `json:"lane,omitempty"` 93 ReadyTime time.Time `json:"ready-time,omitempty"` 94 DoingTime time.Duration `json:"doing-time,omitempty"` 95 UndoingTime time.Duration `json:"undoing-time,omitempty"` 96 DoingTimings []*timings.TimingJSON `json:"doing-timings,omitempty"` 97 UndoingTimings []*timings.TimingJSON `json:"undoing-timings,omitempty"` 98 } 99 100 type debugTimings struct { 101 ChangeID string `json:"change-id"` 102 // total duration of the activity - present for ensure and startup timings only 103 TotalDuration time.Duration `json:"total-duration,omitempty"` 104 EnsureTimings []*timings.TimingJSON `json:"ensure-timings,omitempty"` 105 StartupTimings []*timings.TimingJSON `json:"startup-timings,omitempty"` 106 // ChangeTimings are indexed by task id 107 ChangeTimings map[string]*changeTimings `json:"change-timings,omitempty"` 108 } 109 110 // minLane determines the lowest lane number for the task 111 func minLane(t *state.Task) int { 112 lanes := t.Lanes() 113 minLane := lanes[0] 114 for _, l := range lanes[1:] { 115 if l < minLane { 116 minLane = l 117 } 118 } 119 return minLane 120 } 121 122 func collectChangeTimings(st *state.State, changeID string) (map[string]*changeTimings, error) { 123 chg := st.Change(changeID) 124 if chg == nil { 125 return nil, fmt.Errorf("cannot find change: %v", changeID) 126 } 127 128 // collect "timings" for tasks of given change 129 stateTimings, err := timings.Get(st, -1, func(tags map[string]string) bool { return tags["change-id"] == changeID }) 130 if err != nil { 131 return nil, fmt.Errorf("cannot get timings of change %s: %v", changeID, err) 132 } 133 134 doingTimingsByTask := make(map[string][]*timings.TimingJSON) 135 undoingTimingsByTask := make(map[string][]*timings.TimingJSON) 136 for _, tm := range stateTimings { 137 taskID := tm.Tags["task-id"] 138 if status, ok := tm.Tags["task-status"]; ok { 139 switch { 140 case status == state.DoingStatus.String(): 141 doingTimingsByTask[taskID] = tm.NestedTimings 142 case status == state.UndoingStatus.String(): 143 undoingTimingsByTask[taskID] = tm.NestedTimings 144 default: 145 return nil, fmt.Errorf("unexpected task status %q for timing of task %s", status, taskID) 146 } 147 } 148 } 149 150 m := map[string]*changeTimings{} 151 for _, t := range chg.Tasks() { 152 m[t.ID()] = &changeTimings{ 153 Kind: t.Kind(), 154 Status: t.Status().String(), 155 Summary: t.Summary(), 156 Lane: minLane(t), 157 ReadyTime: t.ReadyTime(), 158 DoingTime: t.DoingTime(), 159 UndoingTime: t.UndoingTime(), 160 DoingTimings: doingTimingsByTask[t.ID()], 161 UndoingTimings: undoingTimingsByTask[t.ID()], 162 } 163 } 164 return m, nil 165 } 166 167 func collectEnsureTimings(st *state.State, ensureTag string, allEnsures bool) ([]*debugTimings, error) { 168 ensures, err := timings.Get(st, -1, func(tags map[string]string) bool { 169 return tags["ensure"] == ensureTag 170 }) 171 if err != nil { 172 return nil, fmt.Errorf("cannot get timings of ensure %s: %v", ensureTag, err) 173 } 174 if len(ensures) == 0 { 175 return nil, fmt.Errorf("cannot find ensure: %v", ensureTag) 176 } 177 178 // If allEnsures is true, then report all activities of given ensure, otherwise just the latest 179 first := len(ensures) - 1 180 if allEnsures { 181 first = 0 182 } 183 var responseData []*debugTimings 184 var changeTimings map[string]*changeTimings 185 for _, ensureTm := range ensures[first:] { 186 ensureChangeID := ensureTm.Tags["change-id"] 187 // change is optional for ensure timings 188 if ensureChangeID != "" { 189 // ignore an error when getting a change, it may no longer be present in the state 190 changeTimings, _ = collectChangeTimings(st, ensureChangeID) 191 } 192 debugTm := &debugTimings{ 193 ChangeID: ensureChangeID, 194 ChangeTimings: changeTimings, 195 EnsureTimings: ensureTm.NestedTimings, 196 TotalDuration: ensureTm.Duration, 197 } 198 responseData = append(responseData, debugTm) 199 } 200 201 return responseData, nil 202 } 203 204 func collectStartupTimings(st *state.State, startupTag string, allStarts bool) ([]*debugTimings, error) { 205 starts, err := timings.Get(st, -1, func(tags map[string]string) bool { 206 return tags["startup"] == startupTag 207 }) 208 if err != nil { 209 return nil, fmt.Errorf("cannot get timings of startup %s: %v", startupTag, err) 210 } 211 if len(starts) == 0 { 212 return nil, fmt.Errorf("cannot find startup: %v", startupTag) 213 } 214 215 // If allStarts is true, then report all activities of given startup, otherwise just the latest 216 first := len(starts) - 1 217 if allStarts { 218 first = 0 219 } 220 var responseData []*debugTimings 221 for _, startTm := range starts[first:] { 222 debugTm := &debugTimings{ 223 StartupTimings: startTm.NestedTimings, 224 TotalDuration: startTm.Duration, 225 } 226 responseData = append(responseData, debugTm) 227 } 228 229 return responseData, nil 230 } 231 232 func getChangeTimings(st *state.State, changeID, ensureTag, startupTag string, all bool) Response { 233 // If ensure tag was passed by the client, find its related changes; 234 // we can have many ensure executions and their changes in the responseData array. 235 if ensureTag != "" { 236 responseData, err := collectEnsureTimings(st, ensureTag, all) 237 if err != nil { 238 return BadRequest(err.Error()) 239 } 240 return SyncResponse(responseData, nil) 241 } 242 243 if startupTag != "" { 244 responseData, err := collectStartupTimings(st, startupTag, all) 245 if err != nil { 246 return BadRequest(err.Error()) 247 } 248 return SyncResponse(responseData, nil) 249 } 250 251 // timings for single change ID 252 changeTimings, err := collectChangeTimings(st, changeID) 253 if err != nil { 254 return BadRequest(err.Error()) 255 } 256 257 responseData := []*debugTimings{ 258 { 259 ChangeID: changeID, 260 ChangeTimings: changeTimings, 261 }, 262 } 263 return SyncResponse(responseData, nil) 264 } 265 266 func getDebug(c *Command, r *http.Request, user *auth.UserState) Response { 267 query := r.URL.Query() 268 aspect := query.Get("aspect") 269 st := c.d.overlord.State() 270 st.Lock() 271 defer st.Unlock() 272 switch aspect { 273 case "base-declaration": 274 return getBaseDeclaration(st) 275 case "connectivity": 276 return checkConnectivity(st) 277 case "model": 278 model, err := c.d.overlord.DeviceManager().Model() 279 if err != nil { 280 return InternalError("cannot get model: %v", err) 281 } 282 return SyncResponse(map[string]interface{}{ 283 "model": string(asserts.Encode(model)), 284 }, nil) 285 case "change-timings": 286 chgID := query.Get("change-id") 287 ensureTag := query.Get("ensure") 288 startupTag := query.Get("startup") 289 all := query.Get("all") 290 return getChangeTimings(st, chgID, ensureTag, startupTag, all == "true") 291 case "seeding": 292 return getSeedingInfo(st) 293 default: 294 return BadRequest("unknown debug aspect %q", aspect) 295 } 296 } 297 298 func postDebug(c *Command, r *http.Request, user *auth.UserState) Response { 299 var a debugAction 300 decoder := json.NewDecoder(r.Body) 301 if err := decoder.Decode(&a); err != nil { 302 return BadRequest("cannot decode request body into a debug action: %v", err) 303 } 304 305 st := c.d.overlord.State() 306 st.Lock() 307 defer st.Unlock() 308 309 switch a.Action { 310 case "add-warning": 311 st.Warnf("%v", a.Message) 312 return SyncResponse(true, nil) 313 case "unshow-warnings": 314 st.UnshowAllWarnings() 315 return SyncResponse(true, nil) 316 case "ensure-state-soon": 317 ensureStateSoon(st) 318 return SyncResponse(true, nil) 319 case "get-base-declaration": 320 return getBaseDeclaration(st) 321 case "can-manage-refreshes": 322 return SyncResponse(devicestate.CanManageRefreshes(st), nil) 323 case "connectivity": 324 return checkConnectivity(st) 325 case "prune": 326 opTime, err := c.d.overlord.DeviceManager().StartOfOperationTime() 327 if err != nil { 328 return BadRequest("cannot get start of operation time: %s", err) 329 } 330 st.Prune(opTime, 0, 0, 0) 331 return SyncResponse(true, nil) 332 default: 333 return BadRequest("unknown debug action: %v", a.Action) 334 } 335 }