diff --git a/api/README_API.md b/api/README_API.md new file mode 100644 index 0000000..a4057cd --- /dev/null +++ b/api/README_API.md @@ -0,0 +1,3 @@ +## API for the Brownout Controller + +[Api endpoints](https://docs.google.com/document/d/1ntqPBhSBGyOzEjVm63QvFVi7aG04ZEON5NJnUvAAJ7A/edit?usp=sharing) \ No newline at end of file diff --git a/api/battery.go b/api/battery.go new file mode 100644 index 0000000..f8a918e --- /dev/null +++ b/api/battery.go @@ -0,0 +1,67 @@ +package api + +import ( + "brownout-controller/brownout" + "encoding/json" + "log" + "net/http" + "time" +) + +type responseBodyBattery struct { + Battery int +} + +// User can use this API endpoint to periodically send the battery percentage to the brownout controller +func handleSetBattery(w http.ResponseWriter, r *http.Request) { + + var body responseBodyBattery + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + message := "Battery Percentage should be an integer. Example request body: {\"Battery\":45}" + http.Error(w, message, http.StatusBadRequest) + return + } + + batteryPercentage := body.Battery + + if batteryPercentage < 100 && batteryPercentage > 0 { + brownout.SetBatteryPercentage(batteryPercentage) + w.WriteHeader(http.StatusOK) + } else { + http.Error(w, "Battery Percentage should be an integer between 0 and 100", http.StatusBadRequest) + } +} + +type BatteryData struct { + Timestamp int64 `json:"timestamp"` + Battery int `json:"battery"` +} + +func handleListenBattery(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen battery") + for { + batteryData := BatteryData{ + Timestamp: time.Now().Unix(), + Battery: brownout.GetBatteryPercentage(), + } + + // Send the data to the client + err := conn.WriteJSON(batteryData) + if err != nil { + log.Println(err) + return + } + log.Println("Battery percentage value written") + log.Printf("Timestamp: %v, Battery: %vW", batteryData.Timestamp, batteryData.Battery) + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/batteryHandler.go b/api/batteryHandler.go deleted file mode 100644 index c6bbe5e..0000000 --- a/api/batteryHandler.go +++ /dev/null @@ -1,32 +0,0 @@ -package api - -import ( - "brownout-controller/brownout" - "encoding/json" - "net/http" -) - -type responseBody struct { - Battery int -} - -// User can use this API endpoint to periodically send the battery percentage to the brownout controller -func handleSetBattery(w http.ResponseWriter, r *http.Request) { - - var body responseBody - err := json.NewDecoder(r.Body).Decode(&body) - if err != nil { - message := "Battery Percentage should be an integer. Example request body: {\"Battery\":45}" - http.Error(w, message, http.StatusBadRequest) - return - } - - batteryPercentage := body.Battery - - if batteryPercentage < 100 && batteryPercentage > 0 { - brownout.SetBatteryPercentage(batteryPercentage) - w.WriteHeader(http.StatusOK) - } else { - http.Error(w, "Battery Percentage should be an integer between 0 and 100", http.StatusBadRequest) - } -} diff --git a/api/brownout.go b/api/brownout.go new file mode 100644 index 0000000..a57d049 --- /dev/null +++ b/api/brownout.go @@ -0,0 +1,155 @@ +package api + +import ( + "brownout-controller/brownout" + "brownout-controller/variables" + "encoding/json" + "github.com/gorilla/mux" + "log" + "net/http" + "strconv" + "time" +) + +func handleBrownoutActivation(w http.ResponseWriter, r *http.Request) { + brownout.SetBrownoutActive(true) + go brownout.ActivateBrownout() // The function will be executed in a new thread (goroutine) + w.WriteHeader(http.StatusOK) +} + +func handleBrownoutDeactivation(w http.ResponseWriter, r *http.Request) { + brownout.SetBrownoutActive(false) + go brownout.DeactivateBrownout() // The function will be executed in a new thread (goroutine) + w.WriteHeader(http.StatusOK) +} + +type BrownoutStatus struct { + Timestamp int64 `json:"timestamp"` + BrownoutActive bool `json:"brownout_active"` +} + +func handleListenBrownoutStatus(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen brownout status") + for { + brownoutStatus := BrownoutStatus{ + Timestamp: time.Now().Unix(), + BrownoutActive: brownout.GetBrownoutActive(), + } + + // Send the data to the client + err := conn.WriteJSON(brownoutStatus) + if err != nil { + log.Println(err) + return + } + log.Println("BrownoutActive value written") + log.Printf("Timestamp: %v, BrownoutActive: %v", brownoutStatus.Timestamp, brownoutStatus.BrownoutActive) + + // Wait for some time before sending the next data + time.Sleep(1 * time.Second) + } +} + +// Get a variable value +func handleGetVariable(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + params := mux.Vars(r) // Gets params + + name := params["name"] + var value any + + switch name { + case "policy": + value = variables.POLICY + case "batteryUpper": + value = variables.BATTERY_UPPER_THRESHOLD + case "batteryLower": + value = variables.BATTERY_LOWER_THRESHOLD + case "slaViolationLatency": + value = variables.SLA_VIOLATION_LATENCY + case "slaInterval": + value = variables.SLA_INTERVAL + case "asr": + value = variables.ACCEPTED_SUCCESS_RATE + case "amsr": + value = variables.ACCEPTED_MIN_SUCCESS_RATE + default: + message := "Invalid variable name." + http.Error(w, message, http.StatusBadRequest) + return + } + + json.NewEncoder(w).Encode(value) +} + +type responseBodyVariable struct { + Value string +} + +// Set a variable value +func handleSetVariable(w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) // Gets params + name := params["name"] + + var body responseBodyVariable + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + message := "Error in request body. Value must be of type string. Example request body: {\"Value\":\"45\"}" + http.Error(w, message, http.StatusBadRequest) + return + } + + strValue := body.Value + + switch name { + case "policy": + variables.POLICY = strValue + case "batteryUpper": + value, err := strconv.Atoi(strValue) + if err != nil { + message := "Error in request body. Cannot convert value to integer. Example request body: {\"Value\":\"45\"}" + http.Error(w, message, http.StatusBadRequest) + return + } + variables.BATTERY_UPPER_THRESHOLD = value + case "batteryLower": + value, err := strconv.Atoi(strValue) + if err != nil { + message := "Error in request body. Cannot convert value to integer. Example request body: {\"Value\":\"45\"}" + http.Error(w, message, http.StatusBadRequest) + return + } + variables.BATTERY_LOWER_THRESHOLD = value + case "slaViolationLatency": + variables.SLA_VIOLATION_LATENCY = strValue + case "slaInterval": + variables.SLA_INTERVAL = strValue + case "asr": + value, err := strconv.ParseFloat(strValue, 64) + if err != nil { + message := "Error in request body. Cannot convert value to float. Example request body: {\"Value\":\"0.65\"}" + http.Error(w, message, http.StatusBadRequest) + return + } + variables.ACCEPTED_SUCCESS_RATE = value + case "amsr": + value, err := strconv.ParseFloat(strValue, 64) + if err != nil { + message := "Error in request body. Cannot convert value to float. Example request body: {\"Value\":\"0.65\"}" + http.Error(w, message, http.StatusBadRequest) + return + } + variables.ACCEPTED_MIN_SUCCESS_RATE = value + default: + message := "Error in request URL. Name of variable incorrect." + http.Error(w, message, http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/api/brownoutHandlers.go b/api/brownoutHandlers.go deleted file mode 100644 index a7a4dd3..0000000 --- a/api/brownoutHandlers.go +++ /dev/null @@ -1,18 +0,0 @@ -package api - -import ( - "brownout-controller/brownout" - "net/http" -) - -func handleBrownoutActivation(w http.ResponseWriter, r *http.Request) { - brownout.SetBrownoutActive(true) - go brownout.ActivateBrownout() // The function will be executed in a new thread (goroutine) - w.WriteHeader(http.StatusOK) -} - -func handleBrownoutDeactivation(w http.ResponseWriter, r *http.Request) { - brownout.SetBrownoutActive(false) - go brownout.DeactivateBrownout() // The function will be executed in a new thread (goroutine) - w.WriteHeader(http.StatusOK) -} diff --git a/api/deployments.go b/api/deployments.go new file mode 100644 index 0000000..7471877 --- /dev/null +++ b/api/deployments.go @@ -0,0 +1,42 @@ +package api + +import ( + "brownout-controller/constants" + "brownout-controller/kubernetesCluster" + "log" + "net/http" + "time" +) + +type DeploymentData struct { + Timestamp int64 `json:"timestamp"` + DeploymentList []kubernetesCluster.Deployment `json:"deployment_list"` +} + +func handleListenDeploymentData(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen deployment data") + for { + + deploymentData := DeploymentData{ + Timestamp: time.Now().Unix(), + DeploymentList: kubernetesCluster.GetDeploymentsAll(constants.NAMESPACE), + } + + // Send the data to the client + err := conn.WriteJSON(deploymentData) + if err != nil { + log.Println(err) + return + } + log.Println("Deployment data values written") + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/nodes.go b/api/nodes.go new file mode 100644 index 0000000..c90698c --- /dev/null +++ b/api/nodes.go @@ -0,0 +1,42 @@ +package api + +import ( + "brownout-controller/kubernetesCluster" + "log" + "net/http" + "time" +) + +type NodeData struct { + Timestamp int64 `json:"timestamp"` + AllNodes []string `json:"nodes_all"` + ActiveNodes []string `json:"nodes_active"` +} + +func handleListenNodeData(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen node data") + for { + nodeData := NodeData{ + Timestamp: time.Now().Unix(), + AllNodes: kubernetesCluster.GetAllNodeNames(), + ActiveNodes: kubernetesCluster.GetActiveNodeNames(), + } + + // Send the data to the client + err := conn.WriteJSON(nodeData) + if err != nil { + log.Println(err) + return + } + log.Println("Node data values written") + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/pods.go b/api/pods.go new file mode 100644 index 0000000..c7d6419 --- /dev/null +++ b/api/pods.go @@ -0,0 +1,48 @@ +package api + +import ( + "brownout-controller/constants" + "brownout-controller/kubernetesCluster" + "log" + "net/http" + "time" +) + +type PodData struct { + Timestamp int64 `json:"timestamp"` + PodList []string `json:"pod_list"` + Count int `json:"count"` + CpuTotal float64 `json:"cpu_total"` +} + +func handleListenPodData(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen pod data") + for { + pods := kubernetesCluster.GetPodNamesAll(constants.NAMESPACE) + podCPU := kubernetesCluster.GetPodsCPUUsageSum(pods, constants.NAMESPACE) + + podData := PodData{ + Timestamp: time.Now().Unix(), + PodList: pods, + Count: len(pods), + CpuTotal: podCPU, + } + + // Send the data to the client + err := conn.WriteJSON(podData) + if err != nil { + log.Println(err) + return + } + log.Println("Pod data values written") + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/power.go b/api/power.go new file mode 100644 index 0000000..a3bb056 --- /dev/null +++ b/api/power.go @@ -0,0 +1,41 @@ +package api + +import ( + "brownout-controller/powerModel" + "log" + "net/http" + "time" +) + +type PowerData struct { + Timestamp int64 `json:"timestamp"` + Power float64 `json:"power"` +} + +func handleListenPower(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen power") + for { + powerData := PowerData{ + Timestamp: time.Now().Unix(), + Power: powerModel.GetPowerModel().GetPowerConsumptionCluster(), + } + + // Send the data to the client + err := conn.WriteJSON(powerData) + if err != nil { + log.Println(err) + return + } + log.Println("Power consumption value written") + log.Printf("Timestamp: %v, Power: %vW", powerData.Timestamp, powerData.Power) + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/router.go b/api/router.go index 27ea8c0..6a0bf8d 100644 --- a/api/router.go +++ b/api/router.go @@ -2,6 +2,7 @@ package api import ( "brownout-controller/constants" + "github.com/gorilla/handlers" "github.com/gorilla/mux" "log" "net/http" @@ -11,7 +12,14 @@ import ( func InitAPI() { addr := ":" + constants.PORT log.Println("Initializing the API Server") - log.Fatal(http.ListenAndServe(addr, initRouter())) + + corsHandler := handlers.CORS( + handlers.AllowedOrigins([]string{"*"}), // Allow requests from any origin + handlers.AllowedMethods([]string{"GET", "POST"}), // Allow GET and POST methods + handlers.AllowedHeaders([]string{"Content-Type"}), // Allow "Content-Type" header + ) + + log.Fatal(http.ListenAndServe(addr, corsHandler(initRouter()))) } func initRouter() *mux.Router { @@ -28,10 +36,12 @@ func initRouter() *mux.Router { func initMetricsSubRouter(r *mux.Router) { s := r.PathPrefix("/metrics").Subrouter() - // Route handles & endpoints - - // Websocket Sample - s.HandleFunc("/ws", handleWebSocket) + s.HandleFunc("/power", handleListenPower) + s.HandleFunc("/battery", handleListenBattery) + s.HandleFunc("/sla", handleListenSLA) + s.HandleFunc("/nodes/list", handleListenNodeData) + s.HandleFunc("/pods", handleListenPodData) + s.HandleFunc("/deployments", handleListenDeploymentData) } func initBrownoutSubRouter(r *mux.Router) { @@ -41,5 +51,7 @@ func initBrownoutSubRouter(r *mux.Router) { s.HandleFunc("/activate", handleBrownoutActivation).Methods("POST") s.HandleFunc("/deactivate", handleBrownoutDeactivation).Methods("POST") s.HandleFunc("/battery/set", handleSetBattery).Methods("POST") - + s.HandleFunc("/variables/{name}", handleGetVariable).Methods("GET") + s.HandleFunc("/variables/{name}", handleSetVariable).Methods("POST") + s.HandleFunc("/status", handleListenBrownoutStatus) } diff --git a/api/sampleWebSocket.go b/api/sampleWebSocket.go deleted file mode 100644 index f8abf54..0000000 --- a/api/sampleWebSocket.go +++ /dev/null @@ -1,40 +0,0 @@ -package api - -import ( - "fmt" - "github.com/gorilla/websocket" - "log" - "math/rand" - "net/http" - "time" -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, -} - -func handleWebSocket(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println(err) - return - } - - fmt.Println("Client Connected") - for { - // Generate some random data - data := []float64{rand.Float64(), rand.Float64(), rand.Float64()} - - // Send the data to the client - err := conn.WriteJSON(data) - if err != nil { - log.Println(err) - return - } - fmt.Println("Data Written") - // Wait for some time before sending the next data - time.Sleep(1 * time.Second) - } -} diff --git a/api/sla.go b/api/sla.go new file mode 100644 index 0000000..8d3eeee --- /dev/null +++ b/api/sla.go @@ -0,0 +1,58 @@ +package api + +import ( + "brownout-controller/constants" + "brownout-controller/prometheus" + "brownout-controller/variables" + "log" + "math" + "net/http" + "time" +) + +type SLAData struct { + Timestamp int64 `json:"timestamp"` + TotReq int `json:"tot_req"` + ErrReq int `json:"err_req"` + SlowReq int `json:"slow_req"` + TotSuccessReq int `json:"tot_success_req"` + SLASuccess float64 `json:"sla_success"` +} + +// User can use this API endpoint to get SLA related information +func handleListenSLA(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + log.Println("Client Connected to listen sla") + for { + + slaSuccess := prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) + if math.IsNaN(slaSuccess) { + slaSuccess = 0 + } + + slaData := SLAData{ + Timestamp: time.Now().Unix(), + TotReq: prometheus.GetTotalRequestCount(constants.HOSTNAME, variables.SLA_INTERVAL), + ErrReq: prometheus.GetErrorRequestCount(constants.HOSTNAME, variables.SLA_INTERVAL), + SlowReq: prometheus.GetSlowRequestCount(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY), + TotSuccessReq: prometheus.GetTotalSuccessRequestCount(constants.HOSTNAME, variables.SLA_INTERVAL), + SLASuccess: slaSuccess, + } + + // Send the data to the client + err := conn.WriteJSON(slaData) + if err != nil { + log.Println(err) + return + } + log.Println("SLA values written") + + // Wait for some time before sending the next data + time.Sleep(30 * time.Second) + } +} diff --git a/api/util.go b/api/util.go new file mode 100644 index 0000000..02407f3 --- /dev/null +++ b/api/util.go @@ -0,0 +1,12 @@ +package api + +import ( + "github.com/gorilla/websocket" + "net/http" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { return true }, +} diff --git a/brownout/brownout.go b/brownout/brownout.go index 71b020c..adb2c91 100644 --- a/brownout/brownout.go +++ b/brownout/brownout.go @@ -7,6 +7,7 @@ import ( "brownout-controller/powerModel" "brownout-controller/prometheus" "brownout-controller/util" + "brownout-controller/variables" "log" "time" ) @@ -20,11 +21,11 @@ func ActivateBrownout() { for { if brownoutActive { log.Printf("Checking battery percentage. Battery is at %v%%", batteryPercentage) - if batteryPercentage < constants.BATTERY_LOWER_THRESHOLD { - log.Printf("Battery percentage less than %v%%. Executing Brownout in the cluster", constants.BATTERY_LOWER_THRESHOLD) + if batteryPercentage < variables.BATTERY_LOWER_THRESHOLD { + log.Printf("Battery percentage less than %v%%. Executing Brownout in the cluster", variables.BATTERY_LOWER_THRESHOLD) runBrownout() - } else if batteryPercentage > constants.BATTERY_UPPER_THRESHOLD { - log.Printf("Battery percentage greater than %v%%. Stopping Brownout in the cluster", constants.BATTERY_UPPER_THRESHOLD) + } else if batteryPercentage > variables.BATTERY_UPPER_THRESHOLD { + log.Printf("Battery percentage greater than %v%%. Stopping Brownout in the cluster", variables.BATTERY_UPPER_THRESHOLD) stopBrownout() } time.Sleep(5 * time.Minute) @@ -40,28 +41,28 @@ func DeactivateBrownout() { } func runBrownout() { - currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY) + currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) log.Println("Initial SR: ", currentSuccessRate) - log.Println("ASR: ", constants.ACCEPTED_SUCCESS_RATE) + log.Println("ASR: ", variables.ACCEPTED_SUCCESS_RATE) // ACCEPTED_SUCCESS_RATE = approx. 0.65 - if currentSuccessRate > constants.ACCEPTED_SUCCESS_RATE { + if currentSuccessRate > variables.ACCEPTED_SUCCESS_RATE { currentPowerConsumption := powerModel.GetPowerModel().GetPowerConsumptionPods(kubernetesCluster.GetPodNamesAll(constants.NAMESPACE)) log.Println("Initial Power: ", currentPowerConsumption) // current_success_rate / ACCEPTED_SUCCESS_RATE = k * (current_power_consumption / upper_threshold_power ) - upperThresholdPower := constants.K_VALUE * (currentPowerConsumption * constants.ACCEPTED_SUCCESS_RATE / currentSuccessRate) + upperThresholdPower := constants.K_VALUE * (currentPowerConsumption * variables.ACCEPTED_SUCCESS_RATE / currentSuccessRate) log.Println("Calculated upper threshold Power: ", upperThresholdPower) // Deactivate containers based on the container selection policy specified in constants - policy := policies.GetSelectedPolicy(constants.POLICY) + policy := policies.GetSelectedPolicy(variables.POLICY) //DEACTIVATE_CONTAINERS(upperThresholdPower) currentDeactivatedDeployments := policy.ExecuteForCluster(upperThresholdPower) deactivatedDeployments = util.AddDeployments(currentDeactivatedDeployments, deactivatedDeployments) // ACCEPTED_LOW_SUCCESS_RATE = approx. 0.50 - } else if currentSuccessRate < constants.ACCEPTED_MIN_SUCCESS_RATE { + } else if currentSuccessRate < variables.ACCEPTED_MIN_SUCCESS_RATE { stopBrownout() } } diff --git a/brownout/status.go b/brownout/status.go index 7769395..b883057 100644 --- a/brownout/status.go +++ b/brownout/status.go @@ -5,3 +5,7 @@ var brownoutActive bool func SetBrownoutActive(y bool) { brownoutActive = y } + +func GetBrownoutActive() bool { + return brownoutActive +} diff --git a/constants/constants.go b/constants/constants.go index dacdb5b..5f4fce9 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -1,20 +1,13 @@ package constants const ( - POLICY = "NISP" // Options: NISP, LUCF, HUCF, RCSP - NISP_PER_NODE_POLICY = "LUCF" - OPTIONAL = "optional" - NAMESPACE = "default" - BATTERY_UPPER_THRESHOLD = 80 - BATTERY_LOWER_THRESHOLD = 50 - SLA_VIOLATION_LATENCY = "0.25" //seconds - SLA_INTERVAL = "1m" - ACCEPTED_SUCCESS_RATE = 0.65 - ACCEPTED_MIN_SUCCESS_RATE = 0.50 - K_VALUE = 0.0000 // Select a policy and put the respective K value - K_LUCF = 0.9217 - K_HUCF = 0.9567 - K_RCSP = 0.9582 - K_NISP = 0.8199 - HOSTNAME = "agrimaster.com" + OPTIONAL = "optional" + NAMESPACE = "default" + NISP_PER_NODE_POLICY = "LUCF" + K_VALUE = 0.8199 // Select a policy and put the respective K value + K_LUCF = 0.9217 + K_HUCF = 0.9567 + K_RCSP = 0.9582 + K_NISP = 0.8199 + HOSTNAME = "agrimaster.com" ) diff --git a/experimentation/brownoutNodePolicies.go b/experimentation/brownoutNodePolicies.go index edf834f..8889305 100644 --- a/experimentation/brownoutNodePolicies.go +++ b/experimentation/brownoutNodePolicies.go @@ -5,6 +5,7 @@ import ( "brownout-controller/kubernetesCluster" "brownout-controller/powerModel" "brownout-controller/prometheus" + "brownout-controller/variables" "log" ) @@ -12,18 +13,18 @@ import ( // (LUCF, LRU, Random) func DoBrownoutExperimentNodePolicy(policy string, policyK float64) { - currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY) + currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) log.Println("Initial SR: ", currentSuccessRate) // ACCEPTED_SUCCESS_RATE = approx. 0.65 - if currentSuccessRate > constants.ACCEPTED_SUCCESS_RATE { + if currentSuccessRate > variables.ACCEPTED_SUCCESS_RATE { currentPowerConsumption := powerModel.GetPowerModel().GetPowerConsumptionPods(kubernetesCluster.GetPodNamesAll(constants.NAMESPACE)) log.Println("Initial Power: ", currentPowerConsumption) // current_success_rate / ACCEPTED_SUCCESS_RATE = k * (current_power_consumption / upper_threshold_power ) - upperThresholdPower := policyK * (currentPowerConsumption * constants.ACCEPTED_SUCCESS_RATE / currentSuccessRate) + upperThresholdPower := policyK * (currentPowerConsumption * variables.ACCEPTED_SUCCESS_RATE / currentSuccessRate) log.Println("Policy K: ", policyK) - log.Println("ASR: ", constants.ACCEPTED_SUCCESS_RATE) + log.Println("ASR: ", variables.ACCEPTED_SUCCESS_RATE) log.Println("Calculated upper threshold Power: ", upperThresholdPower) DoExperimentNodePolicies(policy, upperThresholdPower) diff --git a/experimentation/brownoutPodPolicies.go b/experimentation/brownoutPodPolicies.go index d3e6f15..a174c48 100644 --- a/experimentation/brownoutPodPolicies.go +++ b/experimentation/brownoutPodPolicies.go @@ -5,6 +5,7 @@ import ( "brownout-controller/kubernetesCluster" "brownout-controller/powerModel" "brownout-controller/prometheus" + "brownout-controller/variables" "log" ) @@ -12,18 +13,18 @@ import ( // (LUCF, LRU, Random) func DoBrownoutExperimentPodPolicy(policy string, policyK float64) { - currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY) + currentSuccessRate := prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) log.Println("Initial SR: ", currentSuccessRate) // ACCEPTED_SUCCESS_RATE = approx. 0.65 - if currentSuccessRate > constants.ACCEPTED_SUCCESS_RATE { + if currentSuccessRate > variables.ACCEPTED_SUCCESS_RATE { currentPowerConsumption := powerModel.GetPowerModel().GetPowerConsumptionPods(kubernetesCluster.GetPodNamesAll(constants.NAMESPACE)) log.Println("Initial Power: ", currentPowerConsumption) // current_success_rate / ACCEPTED_SUCCESS_RATE = k * (current_power_consumption / upper_threshold_power ) - upperThresholdPower := policyK * (currentPowerConsumption * constants.ACCEPTED_SUCCESS_RATE / currentSuccessRate) + upperThresholdPower := policyK * (currentPowerConsumption * variables.ACCEPTED_SUCCESS_RATE / currentSuccessRate) log.Println("Policy K: ", policyK) - log.Println("ASR: ", constants.ACCEPTED_SUCCESS_RATE) + log.Println("ASR: ", variables.ACCEPTED_SUCCESS_RATE) log.Println("Calculated upper threshold Power: ", upperThresholdPower) DoExperimentPodPolicies(policy, upperThresholdPower) diff --git a/experimentation/nodePolicies.go b/experimentation/nodePolicies.go index 7f2822d..be3d36e 100644 --- a/experimentation/nodePolicies.go +++ b/experimentation/nodePolicies.go @@ -6,6 +6,7 @@ import ( "brownout-controller/policies" "brownout-controller/powerModel" "brownout-controller/prometheus" + "brownout-controller/variables" "log" "time" ) @@ -15,7 +16,7 @@ func DoExperimentNodePolicies(policyName string, upperThresholdPower float64) { policy := policies.GetSelectedPolicy(policyName) - prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY) + prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) deactivatedPods := policy.ExecuteForCluster(upperThresholdPower) log.Println("Deactivated Pods: ", deactivatedPods) @@ -34,7 +35,7 @@ func DoExperimentNodePolicies(policyName string, upperThresholdPower float64) { for i := 1; i <= 30; i++ { log.Println("==================================================================") - srList = append(srList, prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY)) + srList = append(srList, prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY)) // get power consumption of the pods // NOTE: Don't use GetPowerConsumptionNodesWithMigration() here as migration has already happened predictedPowerList = append(predictedPowerList, powerModel.GetPowerModel().GetPowerConsumptionNodes(activeNodes)) diff --git a/experimentation/podPolices.go b/experimentation/podPolices.go index d18f30b..23c7219 100644 --- a/experimentation/podPolices.go +++ b/experimentation/podPolices.go @@ -6,6 +6,7 @@ import ( "brownout-controller/policies" "brownout-controller/powerModel" "brownout-controller/prometheus" + "brownout-controller/variables" "fmt" "log" "time" @@ -16,7 +17,7 @@ func DoExperimentPodPolicies(policyName string, upperThresholdPower float64) { policy := policies.GetSelectedPolicy(policyName) - prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY) + prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY) deactivatedPods := policy.ExecuteForCluster(upperThresholdPower) log.Println("Deactivated Pods: ", deactivatedPods) @@ -34,7 +35,7 @@ func DoExperimentPodPolicies(policyName string, upperThresholdPower float64) { for i := 1; i <= 30; i++ { log.Println("==================================================================") - srList = append(srList, prometheus.GetSLASuccessRatio(constants.HOSTNAME, constants.SLA_INTERVAL, constants.SLA_VIOLATION_LATENCY)) + srList = append(srList, prometheus.GetSLASuccessRatio(constants.HOSTNAME, variables.SLA_INTERVAL, variables.SLA_VIOLATION_LATENCY)) // get power consumption of the pods predictedPowerList = append(predictedPowerList, powerModel.GetPowerModel().GetPowerConsumptionPods(allClusterPods)) log.Println("Predicted Power List: ", predictedPowerList) diff --git a/go.mod b/go.mod index 1cdbcca..5bd94b4 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module brownout-controller go 1.20 require ( + github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/prometheus/client_golang v1.15.0 @@ -16,6 +17,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect diff --git a/go.sum b/go.sum index c44147f..2cd6c98 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhF github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -53,6 +55,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= diff --git a/kubernetesCluster/getDeployments.go b/kubernetesCluster/getDeployments.go new file mode 100644 index 0000000..3d8fb5f --- /dev/null +++ b/kubernetesCluster/getDeployments.go @@ -0,0 +1,33 @@ +package kubernetesCluster + +import ( + "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "log" +) + +type Deployment struct { + Name string + Replicas int32 +} + +func GetDeploymentsAll(namespace string) []Deployment { + clientset := getKubernetesClientSet() + deploymentList, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), metav1.ListOptions{}) + + if err != nil { + log.Println(err.Error()) + } + + var deploymentObjList []Deployment + + for _, deployment := range deploymentList.Items { + deploymentObj := Deployment{ + Name: deployment.Name, + Replicas: *deployment.Spec.Replicas, + } + deploymentObjList = append(deploymentObjList, deploymentObj) + } + + return deploymentObjList +} diff --git a/powerModel/powerModel.go b/powerModel/powerModel.go index d19b5aa..0fad021 100644 --- a/powerModel/powerModel.go +++ b/powerModel/powerModel.go @@ -24,6 +24,13 @@ func GetPowerModel() *PowerModel { return model } +// GetPowerConsumptionCluster : function to compute power consumption of the cluster +func (model *PowerModel) GetPowerConsumptionCluster() float64 { + + activeNodes := kubernetesCluster.GetActiveNodeNames() + return model.GetPowerConsumptionNodes(activeNodes) +} + // GetPowerConsumptionNodes : function to compute power consumption when a set of nodes given func (model *PowerModel) GetPowerConsumptionNodes(nodeNames []string) float64 { diff --git a/prometheus/sla.go b/prometheus/sla.go index 00c9e99..d4579a9 100644 --- a/prometheus/sla.go +++ b/prometheus/sla.go @@ -5,9 +5,9 @@ import ( ) func GetSLAViolationRatio(hostname string, interval string, latency string) float64 { - reqTotal := getTotalRequestCount(hostname, interval) - reqError := getErrorRequestCount(hostname, interval) - reqSlow := getSlowRequestCount(hostname, interval, latency) + reqTotal := GetTotalRequestCount(hostname, interval) + reqError := GetErrorRequestCount(hostname, interval) + reqSlow := GetSlowRequestCount(hostname, interval, latency) slaViolationRatio := float64(reqSlow+reqError) / float64(reqTotal) log.Printf("SLA violation ratio for host %v", slaViolationRatio) diff --git a/prometheus/sli.go b/prometheus/sli.go index a627b56..f85dc9f 100644 --- a/prometheus/sli.go +++ b/prometheus/sli.go @@ -6,8 +6,8 @@ import ( "log" ) -// interval parameter can have 1m, 30m, 1d, etc -func getTotalRequestCount(hostname string, interval string) int { +// GetTotalRequestCount interval parameter can have 1m, 30m, 1d, etc +func GetTotalRequestCount(hostname string, interval string) int { query := fmt.Sprintf("sum by (host) (increase(nginx_ingress_controller_requests{host=~'%s'}[%s]))", hostname, interval) result := doQuery(query) // Result is of type Vector if result.(model.Vector).Len() == 0 { @@ -19,8 +19,8 @@ func getTotalRequestCount(hostname string, interval string) int { return reqCount } -// interval parameter can have 1m, 30m, 1d, etc -func getErrorRequestCount(hostname string, interval string) int { +// GetErrorRequestCount interval parameter can have 1m, 30m, 1d, etc +func GetErrorRequestCount(hostname string, interval string) int { query := fmt.Sprintf("sum by (host) (increase(nginx_ingress_controller_requests{status=~'[4-5].*', host=~'%s'}[%s]))", hostname, interval) result := doQuery(query) // Result is of type Vector if result.(model.Vector).Len() == 0 { @@ -32,19 +32,19 @@ func getErrorRequestCount(hostname string, interval string) int { return reqCount } -// getSlowRequestCount returns the count of requests which are slower than the given latency +// GetSlowRequestCount returns the count of requests which are slower than the given latency // parameter latency (in seconds): 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 // parameter interval: 1m, 30m, 1d, etc -func getSlowRequestCount(hostname string, interval string, latency string) int { - totalSuccessReq := getTotalSuccessRequestCount(hostname, interval) +func GetSlowRequestCount(hostname string, interval string, latency string) int { + totalSuccessReq := GetTotalSuccessRequestCount(hostname, interval) fastReq := getFastRequestCount(hostname, interval, latency) slowReq := totalSuccessReq - fastReq log.Printf("Slow Request Count for host %s in the last %s for latency %s second: %v", hostname, interval, latency, slowReq) return slowReq } -// interval parameter can have 1m, 30m, 1d, etc -func getTotalSuccessRequestCount(hostname string, interval string) int { +// GetTotalSuccessRequestCount interval parameter can have 1m, 30m, 1d, etc +func GetTotalSuccessRequestCount(hostname string, interval string) int { query := fmt.Sprintf("sum by (host) (increase(nginx_ingress_controller_requests{status!~'[4-5].*', host=~'%s'}[%s]))", hostname, interval) result := doQuery(query) // Result is of type Vector if result.(model.Vector).Len() == 0 { diff --git a/variables/variables.go b/variables/variables.go new file mode 100644 index 0000000..88e37ca --- /dev/null +++ b/variables/variables.go @@ -0,0 +1,11 @@ +package variables + +var ( + POLICY = "NISP" // Options: NISP, LUCF, HUCF, RCSP + BATTERY_UPPER_THRESHOLD = 80 + BATTERY_LOWER_THRESHOLD = 50 + SLA_VIOLATION_LATENCY = "0.25" //seconds + SLA_INTERVAL = "1m" + ACCEPTED_SUCCESS_RATE = 0.65 + ACCEPTED_MIN_SUCCESS_RATE = 0.50 +)