k8s.io/kubernetes@v1.29.3/test/images/agnhost/resource-consumer-controller/controller.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resconsumerctrl 18 19 import ( 20 "fmt" 21 "log" 22 "net/http" 23 "net/url" 24 "regexp" 25 "strconv" 26 "sync" 27 28 "github.com/spf13/cobra" 29 30 "k8s.io/kubernetes/test/images/agnhost/dns" 31 "k8s.io/kubernetes/test/images/resource-consumer/common" 32 ) 33 34 // CmdResourceConsumerController is used by agnhost Cobra. 35 var CmdResourceConsumerController = &cobra.Command{ 36 Use: "resource-consumer-controller", 37 Short: "Starts a HTTP server that spreads requests around resource consumers", 38 Long: "Starts a HTTP server that spreads requests around resource consumers. The HTTP server has the same endpoints and usage as the one spawned by the \"resource-consumer\" subcommand.", 39 Args: cobra.MaximumNArgs(0), 40 Run: main, 41 } 42 43 var ( 44 port int 45 consumerPort int 46 consumerServiceName string 47 consumerServiceNamespace string 48 dnsDomain string 49 ) 50 51 // getDNSDomain walks through DNS configuration and looks for "svc.foo" record 52 // where "foo" is currently configured DNS suffix. Then picks that 'foo' part up 53 // and returns to a caller. 54 func getDNSDomain() string { 55 if dnsDomain != "" { 56 return dnsDomain 57 } 58 dnsSuffixList := dns.GetDNSSuffixList() 59 r, _ := regexp.Compile("^svc.") 60 for _, currentDNSSuffix := range dnsSuffixList { 61 if r.MatchString(currentDNSSuffix) { 62 // Save DNS suffix without the 'svc.' part 63 dnsDomain = currentDNSSuffix[4:] 64 break 65 } 66 } 67 if dnsDomain == "" { 68 panic("Could not find DNS suffix starting with 'svc.' substring") 69 } 70 return dnsDomain 71 } 72 73 func init() { 74 CmdResourceConsumerController.Flags().IntVar(&port, "port", 8080, "Port number.") 75 CmdResourceConsumerController.Flags().IntVar(&consumerPort, "consumer-port", 8080, "Port number of consumers.") 76 CmdResourceConsumerController.Flags().StringVar(&consumerServiceName, "consumer-service-name", "resource-consumer", "Name of service containing resource consumers.") 77 CmdResourceConsumerController.Flags().StringVar(&consumerServiceNamespace, "consumer-service-namespace", "default", "Namespace of service containing resource consumers.") 78 } 79 80 func main(cmd *cobra.Command, args []string) { 81 mgr := newController() 82 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), mgr)) 83 } 84 85 type controller struct { 86 responseWriterLock sync.Mutex 87 waitGroup sync.WaitGroup 88 } 89 90 func newController() *controller { 91 c := &controller{} 92 return c 93 } 94 95 func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request) { 96 if req.Method != "POST" { 97 http.Error(w, common.BadRequest, http.StatusBadRequest) 98 return 99 } 100 // parsing POST request data and URL data 101 if err := req.ParseForm(); err != nil { 102 http.Error(w, err.Error(), http.StatusBadRequest) 103 return 104 } 105 // handle consumeCPU 106 if req.URL.Path == common.ConsumeCPUAddress { 107 c.handleConsumeCPU(w, req.Form) 108 return 109 } 110 // handle consumeMem 111 if req.URL.Path == common.ConsumeMemAddress { 112 c.handleConsumeMem(w, req.Form) 113 return 114 } 115 // handle bumpMetric 116 if req.URL.Path == common.BumpMetricAddress { 117 c.handleBumpMetric(w, req.Form) 118 return 119 } 120 http.Error(w, common.UnknownFunction, http.StatusNotFound) 121 } 122 123 func (c *controller) handleConsumeCPU(w http.ResponseWriter, query url.Values) { 124 // getting string data for consumeCPU 125 durationSecString := query.Get(common.DurationSecQuery) 126 millicoresString := query.Get(common.MillicoresQuery) 127 requestSizeInMillicoresString := query.Get(common.RequestSizeInMillicoresQuery) 128 if durationSecString == "" || millicoresString == "" || requestSizeInMillicoresString == "" { 129 http.Error(w, common.NotGivenFunctionArgument, http.StatusBadRequest) 130 return 131 } 132 133 // convert data (strings to ints) for consumeCPU 134 durationSec, durationSecError := strconv.Atoi(durationSecString) 135 millicores, millicoresError := strconv.Atoi(millicoresString) 136 requestSizeInMillicores, requestSizeInMillicoresError := strconv.Atoi(requestSizeInMillicoresString) 137 if durationSecError != nil || millicoresError != nil || requestSizeInMillicoresError != nil || requestSizeInMillicores <= 0 { 138 http.Error(w, common.IncorrectFunctionArgument, http.StatusBadRequest) 139 return 140 } 141 142 count := millicores / requestSizeInMillicores 143 rest := millicores - count*requestSizeInMillicores 144 fmt.Fprintf(w, "RC manager: sending %v requests to consume %v millicores each and 1 request to consume %v millicores\n", count, requestSizeInMillicores, rest) 145 if count > 0 { 146 c.waitGroup.Add(count) 147 c.sendConsumeCPURequests(w, count, requestSizeInMillicores, durationSec) 148 } 149 if rest > 0 { 150 c.waitGroup.Add(1) 151 go c.sendOneConsumeCPURequest(w, rest, durationSec) 152 } 153 c.waitGroup.Wait() 154 } 155 156 func (c *controller) handleConsumeMem(w http.ResponseWriter, query url.Values) { 157 // getting string data for consumeMem 158 durationSecString := query.Get(common.DurationSecQuery) 159 megabytesString := query.Get(common.MegabytesQuery) 160 requestSizeInMegabytesString := query.Get(common.RequestSizeInMegabytesQuery) 161 if durationSecString == "" || megabytesString == "" || requestSizeInMegabytesString == "" { 162 http.Error(w, common.NotGivenFunctionArgument, http.StatusBadRequest) 163 return 164 } 165 166 // convert data (strings to ints) for consumeMem 167 durationSec, durationSecError := strconv.Atoi(durationSecString) 168 megabytes, megabytesError := strconv.Atoi(megabytesString) 169 requestSizeInMegabytes, requestSizeInMegabytesError := strconv.Atoi(requestSizeInMegabytesString) 170 if durationSecError != nil || megabytesError != nil || requestSizeInMegabytesError != nil || requestSizeInMegabytes <= 0 { 171 http.Error(w, common.IncorrectFunctionArgument, http.StatusBadRequest) 172 return 173 } 174 175 count := megabytes / requestSizeInMegabytes 176 rest := megabytes - count*requestSizeInMegabytes 177 fmt.Fprintf(w, "RC manager: sending %v requests to consume %v MB each and 1 request to consume %v MB\n", count, requestSizeInMegabytes, rest) 178 if count > 0 { 179 c.waitGroup.Add(count) 180 c.sendConsumeMemRequests(w, count, requestSizeInMegabytes, durationSec) 181 } 182 if rest > 0 { 183 c.waitGroup.Add(1) 184 go c.sendOneConsumeMemRequest(w, rest, durationSec) 185 } 186 c.waitGroup.Wait() 187 } 188 189 func (c *controller) handleBumpMetric(w http.ResponseWriter, query url.Values) { 190 // getting string data for handleBumpMetric 191 metric := query.Get(common.MetricNameQuery) 192 deltaString := query.Get(common.DeltaQuery) 193 durationSecString := query.Get(common.DurationSecQuery) 194 requestSizeCustomMetricString := query.Get(common.RequestSizeCustomMetricQuery) 195 if durationSecString == "" || metric == "" || deltaString == "" || requestSizeCustomMetricString == "" { 196 http.Error(w, common.NotGivenFunctionArgument, http.StatusBadRequest) 197 return 198 } 199 200 // convert data (strings to ints/floats) for handleBumpMetric 201 durationSec, durationSecError := strconv.Atoi(durationSecString) 202 delta, deltaError := strconv.Atoi(deltaString) 203 requestSizeCustomMetric, requestSizeCustomMetricError := strconv.Atoi(requestSizeCustomMetricString) 204 if durationSecError != nil || deltaError != nil || requestSizeCustomMetricError != nil || requestSizeCustomMetric <= 0 { 205 http.Error(w, common.IncorrectFunctionArgument, http.StatusBadRequest) 206 return 207 } 208 209 count := delta / requestSizeCustomMetric 210 rest := delta - count*requestSizeCustomMetric 211 fmt.Fprintf(w, "RC manager: sending %v requests to bump custom metric by %v each and 1 request to bump by %v\n", count, requestSizeCustomMetric, rest) 212 if count > 0 { 213 c.waitGroup.Add(count) 214 c.sendConsumeCustomMetric(w, metric, count, requestSizeCustomMetric, durationSec) 215 } 216 if rest > 0 { 217 c.waitGroup.Add(1) 218 go c.sendOneConsumeCustomMetric(w, metric, rest, durationSec) 219 } 220 c.waitGroup.Wait() 221 } 222 223 func (c *controller) sendConsumeCPURequests(w http.ResponseWriter, requests, millicores, durationSec int) { 224 for i := 0; i < requests; i++ { 225 go c.sendOneConsumeCPURequest(w, millicores, durationSec) 226 } 227 } 228 229 func (c *controller) sendConsumeMemRequests(w http.ResponseWriter, requests, megabytes, durationSec int) { 230 for i := 0; i < requests; i++ { 231 go c.sendOneConsumeMemRequest(w, megabytes, durationSec) 232 } 233 } 234 235 func (c *controller) sendConsumeCustomMetric(w http.ResponseWriter, metric string, requests, delta, durationSec int) { 236 for i := 0; i < requests; i++ { 237 go c.sendOneConsumeCustomMetric(w, metric, delta, durationSec) 238 } 239 } 240 241 func createConsumerURL(suffix string) string { 242 // NOTE: full DNS name is used due to the Windows platform restriction where PQDNs are not supported. 243 return fmt.Sprintf("http://%s.%s.svc.%s:%d%s", consumerServiceName, consumerServiceNamespace, getDNSDomain(), consumerPort, suffix) 244 } 245 246 // sendOneConsumeCPURequest sends POST request for cpu consumption 247 func (c *controller) sendOneConsumeCPURequest(w http.ResponseWriter, millicores int, durationSec int) { 248 defer c.waitGroup.Done() 249 query := createConsumerURL(common.ConsumeCPUAddress) 250 _, err := http.PostForm(query, url.Values{common.MillicoresQuery: {strconv.Itoa(millicores)}, common.DurationSecQuery: {strconv.Itoa(durationSec)}}) 251 c.responseWriterLock.Lock() 252 defer c.responseWriterLock.Unlock() 253 if err != nil { 254 fmt.Fprintf(w, "Failed to connect to consumer: %v\n", err) 255 return 256 } 257 fmt.Fprintf(w, "Consumed %d millicores\n", millicores) 258 } 259 260 // sendOneConsumeMemRequest sends POST request for memory consumption 261 func (c *controller) sendOneConsumeMemRequest(w http.ResponseWriter, megabytes int, durationSec int) { 262 defer c.waitGroup.Done() 263 query := createConsumerURL(common.ConsumeMemAddress) 264 _, err := http.PostForm(query, url.Values{common.MegabytesQuery: {strconv.Itoa(megabytes)}, common.DurationSecQuery: {strconv.Itoa(durationSec)}}) 265 c.responseWriterLock.Lock() 266 defer c.responseWriterLock.Unlock() 267 if err != nil { 268 fmt.Fprintf(w, "Failed to connect to consumer: %v\n", err) 269 return 270 } 271 fmt.Fprintf(w, "Consumed %d megabytes\n", megabytes) 272 } 273 274 // sendOneConsumeCustomMetric sends POST request for custom metric consumption 275 func (c *controller) sendOneConsumeCustomMetric(w http.ResponseWriter, customMetricName string, delta int, durationSec int) { 276 defer c.waitGroup.Done() 277 query := createConsumerURL(common.BumpMetricAddress) 278 _, err := http.PostForm(query, 279 url.Values{common.MetricNameQuery: {customMetricName}, common.DurationSecQuery: {strconv.Itoa(durationSec)}, common.DeltaQuery: {strconv.Itoa(delta)}}) 280 c.responseWriterLock.Lock() 281 defer c.responseWriterLock.Unlock() 282 if err != nil { 283 fmt.Fprintf(w, "Failed to connect to consumer: %v\n", err) 284 return 285 } 286 fmt.Fprintf(w, "Bumped metric %s by %d\n", customMetricName, delta) 287 }