github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/daemon/api_quotas.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "net/http" 24 "sort" 25 26 "github.com/snapcore/snapd/client" 27 "github.com/snapcore/snapd/gadget/quantity" 28 "github.com/snapcore/snapd/jsonutil" 29 "github.com/snapcore/snapd/overlord/auth" 30 "github.com/snapcore/snapd/overlord/servicestate" 31 "github.com/snapcore/snapd/overlord/state" 32 "github.com/snapcore/snapd/snap/naming" 33 "github.com/snapcore/snapd/snap/quota" 34 ) 35 36 var ( 37 quotaGroupsCmd = &Command{ 38 Path: "/v2/quotas", 39 GET: getQuotaGroups, 40 POST: postQuotaGroup, 41 WriteAccess: rootAccess{}, 42 ReadAccess: openAccess{}, 43 } 44 quotaGroupInfoCmd = &Command{ 45 Path: "/v2/quotas/{group}", 46 GET: getQuotaGroupInfo, 47 ReadAccess: openAccess{}, 48 } 49 ) 50 51 type postQuotaGroupData struct { 52 // Action can be "ensure" or "remove" 53 Action string `json:"action"` 54 GroupName string `json:"group-name"` 55 Parent string `json:"parent,omitempty"` 56 Snaps []string `json:"snaps,omitempty"` 57 Constraints client.QuotaValues `json:"constraints,omitempty"` 58 } 59 60 var ( 61 servicestateCreateQuota = servicestate.CreateQuota 62 servicestateUpdateQuota = servicestate.UpdateQuota 63 servicestateRemoveQuota = servicestate.RemoveQuota 64 ) 65 66 var getQuotaMemUsage = func(grp *quota.Group) (quantity.Size, error) { 67 return grp.CurrentMemoryUsage() 68 } 69 70 // getQuotaGroups returns all quota groups sorted by name. 71 func getQuotaGroups(c *Command, r *http.Request, _ *auth.UserState) Response { 72 st := c.d.overlord.State() 73 st.Lock() 74 defer st.Unlock() 75 76 quotas, err := servicestate.AllQuotas(st) 77 if err != nil { 78 return InternalError(err.Error()) 79 } 80 81 i := 0 82 names := make([]string, len(quotas)) 83 for name := range quotas { 84 names[i] = name 85 i++ 86 } 87 sort.Strings(names) 88 89 results := make([]client.QuotaGroupResult, len(quotas)) 90 for i, name := range names { 91 group := quotas[name] 92 93 memoryUsage, err := getQuotaMemUsage(group) 94 if err != nil { 95 return InternalError(err.Error()) 96 } 97 98 results[i] = client.QuotaGroupResult{ 99 GroupName: group.Name, 100 Parent: group.ParentGroup, 101 Subgroups: group.SubGroups, 102 Snaps: group.Snaps, 103 Constraints: &client.QuotaValues{ 104 Memory: group.MemoryLimit, 105 }, 106 Current: &client.QuotaValues{ 107 Memory: memoryUsage, 108 }, 109 } 110 } 111 return SyncResponse(results) 112 } 113 114 // getQuotaGroupInfo returns details of a single quota Group. 115 func getQuotaGroupInfo(c *Command, r *http.Request, _ *auth.UserState) Response { 116 vars := muxVars(r) 117 groupName := vars["group"] 118 if err := naming.ValidateQuotaGroup(groupName); err != nil { 119 return BadRequest(err.Error()) 120 } 121 122 st := c.d.overlord.State() 123 st.Lock() 124 defer st.Unlock() 125 126 group, err := servicestate.GetQuota(st, groupName) 127 if err == servicestate.ErrQuotaNotFound { 128 return NotFound("cannot find quota group %q", groupName) 129 } 130 if err != nil { 131 return InternalError(err.Error()) 132 } 133 134 memoryUsage, err := getQuotaMemUsage(group) 135 if err != nil { 136 return InternalError(err.Error()) 137 } 138 139 res := client.QuotaGroupResult{ 140 GroupName: group.Name, 141 Parent: group.ParentGroup, 142 Snaps: group.Snaps, 143 Subgroups: group.SubGroups, 144 Constraints: &client.QuotaValues{ 145 Memory: group.MemoryLimit, 146 }, 147 Current: &client.QuotaValues{ 148 Memory: memoryUsage, 149 }, 150 } 151 return SyncResponse(res) 152 } 153 154 // postQuotaGroup creates quota resource group or updates an existing group. 155 func postQuotaGroup(c *Command, r *http.Request, _ *auth.UserState) Response { 156 var data postQuotaGroupData 157 158 if err := jsonutil.DecodeWithNumber(r.Body, &data); err != nil { 159 return BadRequest("cannot decode quota action from request body: %v", err) 160 } 161 162 if err := naming.ValidateQuotaGroup(data.GroupName); err != nil { 163 return BadRequest(err.Error()) 164 } 165 166 st := c.d.overlord.State() 167 st.Lock() 168 defer st.Unlock() 169 170 chgSummary := "" 171 172 var ts *state.TaskSet 173 174 switch data.Action { 175 case "ensure": 176 // check if the quota group exists first, if it does then we need to 177 // update it instead of create it 178 _, err := servicestate.GetQuota(st, data.GroupName) 179 if err != nil && err != servicestate.ErrQuotaNotFound { 180 return InternalError(err.Error()) 181 } 182 if err == servicestate.ErrQuotaNotFound { 183 // then we need to create the quota 184 ts, err = servicestateCreateQuota(st, data.GroupName, data.Parent, data.Snaps, data.Constraints.Memory) 185 if err != nil { 186 return errToResponse(err, nil, BadRequest, "cannot create quota group: %v") 187 } 188 chgSummary = "Create quota group" 189 } else if err == nil { 190 // the quota group already exists, update it 191 updateOpts := servicestate.QuotaGroupUpdate{ 192 AddSnaps: data.Snaps, 193 NewMemoryLimit: data.Constraints.Memory, 194 } 195 ts, err = servicestateUpdateQuota(st, data.GroupName, updateOpts) 196 if err != nil { 197 return errToResponse(err, nil, BadRequest, "cannot update quota group: %v") 198 } 199 chgSummary = "Update quota group" 200 } 201 202 case "remove": 203 var err error 204 ts, err = servicestateRemoveQuota(st, data.GroupName) 205 if err != nil { 206 return errToResponse(err, nil, BadRequest, "cannot remove quota group: %v") 207 } 208 chgSummary = "Remove quota group" 209 default: 210 return BadRequest("unknown quota action %q", data.Action) 211 } 212 213 chg := newChange(st, "quota-control", chgSummary, []*state.TaskSet{ts}, data.Snaps) 214 ensureStateSoon(st) 215 return AsyncResponse(nil, chg.ID()) 216 }