| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| package apirunner | |
| import ( | |
| "bufio" | |
| "crypto/hmac" | |
| "crypto/sha1" | |
| "encoding/base64" | |
| "encoding/csv" | |
| "encoding/json" | |
| "fmt" | |
| "io/ioutil" | |
| "math" | |
| "net/http" | |
| "net/url" | |
| "os" | |
| "regexp" | |
| "strconv" | |
| "strings" | |
| "time" | |
| log "github.com/sirupsen/logrus" | |
| ) | |
| var processedAPImap = make(map[string]bool) | |
| var APIscount = 0 | |
| var SuccessAPIs = 0 | |
| var FailedAPIs = 0 | |
| var TotalTime = 0.0 | |
| func generateParams(apiKey string, secretKey string, signatureVersion int, expires int, command string, page int, pagesize int, keyword string) url.Values { | |
| log.Info("Starting to generate parameters") | |
| params := url.Values{} | |
| params.Set("apiKey", apiKey) | |
| params.Set("response", "json") | |
| params.Set("signatureVersion", strconv.Itoa(signatureVersion)) | |
| params.Set("listall", "true") | |
| params.Set("expires", time.Now().UTC().Add(time.Duration(expires)*time.Second).Format("2006-01-02T15:04:05Z")) | |
| params.Set("command", command) | |
| if command == "listTemplates" { | |
| params.Set("templatefilter", "all") | |
| } | |
| if page != 0 { | |
| params.Set("page", strconv.Itoa(page)) | |
| params.Set("pagesize", strconv.Itoa(pagesize)) | |
| } | |
| if keyword != "" { | |
| params.Set("keyword", keyword) | |
| } | |
| // Generate and add the signature | |
| signature := generateSignature(params.Encode(), secretKey) | |
| params.Set("signature", signature) | |
| return params | |
| } | |
| func RunAPIs(profileName string, apiURL string, apiKey string, secretKey string, expires int, signatureVersion int, iterations int, page int, pagesize int, dbProfile int) { | |
| log.Infof("Starting to run APIs from listCommands.txt file. Each command in the file will be run for multiple iterations and with page parameters mentioned in the configuration file.") | |
| commandsFile := "listCommands.txt" | |
| // Read commands from file | |
| commands, commandsKeywordMap, err := readCommandsFromFile(commandsFile) | |
| if err != nil { | |
| log.Infof("Error reading commands from file: %s\n", err.Error()) | |
| return | |
| } | |
| reportAppend := false | |
| for _, command := range commands { | |
| keyword := commandsKeywordMap[command] | |
| if processedAPImap[command] { | |
| reportAppend = true | |
| } | |
| if page != 0 { | |
| if iterations != 1 { | |
| log.Infof("Calling API [%s] with page %d and pagesize %d -> ", command, page, pagesize) | |
| } else { | |
| log.Infof("Calling API [%s] -> ", command) | |
| } | |
| params := generateParams(apiKey, secretKey, signatureVersion, expires, command, page, pagesize, "") | |
| executeAPIandCalculate(profileName, apiURL, command, params, iterations, page, pagesize, "", dbProfile, reportAppend) | |
| reportAppend = true | |
| } | |
| if len(keyword) != 0 || keyword != "" { | |
| fmt.Printf("Calling API [%s] with keyword -> ", command) | |
| params := generateParams(apiKey, secretKey, signatureVersion, expires, command, 0, 0, keyword) | |
| executeAPIandCalculate(profileName, apiURL, command, params, iterations, 0, 0, keyword, dbProfile, reportAppend) | |
| } | |
| fmt.Printf("Calling API [%s] -> ", command) | |
| params := generateParams(apiKey, secretKey, signatureVersion, expires, command, 0, 0, "") | |
| executeAPIandCalculate(profileName, apiURL, command, params, iterations, 0, 0, "", dbProfile, reportAppend) | |
| fmt.Printf("------------------------------------------------------------\n") | |
| processedAPImap[command] = true | |
| } | |
| } | |
| func executeAPIandCalculate(profileName string, apiURL string, command string, params url.Values, iterations int, page int, pagesize int, keyword string, dbProfile int, reportAppend bool) { | |
| var minTime = math.MaxFloat64 | |
| var maxTime = 0.0 | |
| var avgTime float64 | |
| var totalTime float64 | |
| var count float64 | |
| getRequestList := map[string]struct{}{"isaccountallowedtocreateofferingswithtags": {}, "readyforshutdown": {}, "cloudianisenabled": {}, "quotabalance": {}, | |
| "quotasummary": {}, "quotatarifflist": {}, "quotaisenabled": {}, "quotastatement": {}, "verifyoauthcodeandgetuser": {}} | |
| _, isInGetRequestList := getRequestList[command] | |
| isGetRequest, _ := regexp.MatchString("^(get|list|query|find)(\\w+)+$", command) | |
| if iterations != 1 { | |
| log.Infof("Calling API %s for %d number of iterations with parameters %s", command, iterations, params) | |
| for i := 1; i <= iterations; i++ { | |
| log.Infof("Started with iteration %d for the command %s", i, command) | |
| elapsedTime, apicount, result := executeAPI(apiURL, params, !(isGetRequest || isInGetRequestList)) | |
| count = apicount | |
| if elapsedTime < minTime { | |
| minTime = elapsedTime | |
| } | |
| if elapsedTime > maxTime { | |
| maxTime = elapsedTime | |
| } | |
| totalTime += elapsedTime | |
| if !result { | |
| break | |
| } | |
| } | |
| avgTime = totalTime / float64(iterations) | |
| log.Infof("count [%.f] : Time in seconds [Min - %.2f] [Max - %.2f] [Avg - %.2f]\n", count, minTime, maxTime, avgTime) | |
| saveData(apiURL, count, minTime, maxTime, avgTime, page, pagesize, keyword, profileName, command, dbProfile, reportAppend) | |
| } else { | |
| elapsedTime, apicount, _ := executeAPI(apiURL, params, !(isGetRequest || isInGetRequestList)) | |
| log.Infof("Elapsed time [%.2f seconds] for the count [%.0f]", elapsedTime, apicount) | |
| saveData(apiURL, count, elapsedTime, elapsedTime, elapsedTime, page, pagesize, keyword, profileName, command, dbProfile, reportAppend) | |
| } | |
| } | |
| func saveData(apiURL string, count float64, minTime float64, maxTime float64, avgTime float64, page int, pageSize int, keyword string, user string, filename string, dbProfile int, reportAppend bool) { | |
| parsedURL, err := url.Parse(apiURL) | |
| if err != nil { | |
| log.Infof("Error parsing URL : %s with error : %s\n", apiURL, err) | |
| return | |
| } | |
| host := parsedURL.Hostname() | |
| err = os.MkdirAll(fmt.Sprintf("report/accumulated/%s", host), 0755) | |
| if err != nil { | |
| log.Infof("Error creating host directory : report/accumulated/%s\n", host, err) | |
| return | |
| } | |
| err = os.MkdirAll(fmt.Sprintf("report/individual/%s", host), 0755) | |
| if err != nil { | |
| log.Infof("Error creating host directory : report/individual/%s\n", host, err) | |
| return | |
| } | |
| fileMode := os.O_WRONLY | os.O_CREATE | |
| if reportAppend { | |
| fileMode |= os.O_APPEND | |
| } else { | |
| fileMode |= os.O_TRUNC | |
| } | |
| individualFile, err := os.OpenFile(fmt.Sprintf("report/individual/%s/%s.csv", host, filename), fileMode, 0644) | |
| if err != nil { | |
| log.Fatalf("Error opening the file CSV : report/individual/%s/%s.csv with error %s\n", host, filename, apiURL, err) | |
| } | |
| defer individualFile.Close() | |
| accumulatedFile, err := os.OpenFile(fmt.Sprintf("report/accumulated/%s/%s.csv", host, filename), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) | |
| if err != nil { | |
| log.Fatalf("Error opening the file CSV : report/accumulated/%s/%s.csv with error %s\n", host, filename, apiURL, err) | |
| } | |
| defer accumulatedFile.Close() | |
| filePointers := []*os.File{individualFile, accumulatedFile} | |
| for _, file := range filePointers { | |
| writer := csv.NewWriter(file) | |
| defer writer.Flush() | |
| filereader, err := os.Open(file.Name()) | |
| if err != nil { | |
| log.Fatalf("Error opening the file CSV : %s with error %s\n", file.Name(), err) | |
| } | |
| defer filereader.Close() | |
| reader := bufio.NewReader(filereader) | |
| firstLine, _ := reader.ReadString('\n') | |
| containsCount := strings.Contains(strings.ToLower(firstLine), "count") | |
| if !containsCount { | |
| header := []string{"Count", "MinTime", "MaxTime", "AvgTime", "Page", "PageSize", "keyword", "User", "DBprofile"} | |
| err = writer.Write(header) | |
| if err != nil { | |
| log.Infof("Error writing CSV header for the API: %s with error %s\n", apiURL, err) | |
| return | |
| } | |
| } | |
| var record []string | |
| if page != 0 { | |
| record = []string{ | |
| fmt.Sprintf("%.f", count), | |
| fmt.Sprintf("%.2f", minTime), | |
| fmt.Sprintf("%.2f", maxTime), | |
| fmt.Sprintf("%.2f", avgTime), | |
| strconv.Itoa(page), | |
| strconv.Itoa(pageSize), | |
| keyword, | |
| user, | |
| strconv.Itoa(dbProfile), | |
| } | |
| } else { | |
| record = []string{ | |
| fmt.Sprintf("%.f", count), | |
| fmt.Sprintf("%.2f", minTime), | |
| fmt.Sprintf("%.2f", maxTime), | |
| fmt.Sprintf("%.2f", avgTime), | |
| "-", | |
| "-", | |
| keyword, | |
| user, | |
| strconv.Itoa(dbProfile), | |
| } | |
| } | |
| err = writer.Write(record) | |
| if err != nil { | |
| log.Infof("Error writing to CSV for the API: %s with error %s\n", apiURL, err) | |
| return | |
| } | |
| } | |
| message := fmt.Sprintf("Data saved to report/%s/%s.csv successfully.\n", host, filename) | |
| log.Info(message) | |
| } | |
| func executeAPI(apiURL string, params url.Values, postRequest bool) (float64, float64, bool) { | |
| // Send the API request and calculate the time | |
| var resp *http.Response | |
| var err error | |
| log.Infof("Running the API %s", apiURL) | |
| start := time.Now() | |
| if postRequest { | |
| dataBody := strings.NewReader(params.Encode()) | |
| resp, err = http.Post( | |
| apiURL, | |
| "application/x-www-form-urlencoded", | |
| dataBody, | |
| ) | |
| } else { | |
| apiURL = fmt.Sprintf("%s?%s", apiURL, params.Encode()) | |
| resp, err = http.Get(apiURL) | |
| } | |
| APIscount++ | |
| if err != nil { | |
| log.Infof("Error sending API request: %s with error %s\n", apiURL, err) | |
| FailedAPIs++ | |
| return 0, 0, false | |
| } | |
| defer resp.Body.Close() | |
| elapsed := time.Since(start) | |
| TotalTime += elapsed.Seconds() | |
| body, err := ioutil.ReadAll(resp.Body) | |
| if err != nil { | |
| log.Infof("Error reading API response: %s with error %s\n", apiURL, err) | |
| FailedAPIs++ | |
| return 0, 0, false | |
| } | |
| var data map[string]interface{} | |
| err = json.Unmarshal([]byte(body), &data) | |
| if err != nil { | |
| log.Infof("Error parsing JSON for the API response: %s with error %s\n", apiURL, err) | |
| FailedAPIs++ | |
| return 0, 0, false | |
| } | |
| var key string | |
| for k := range data { | |
| key = k | |
| break | |
| } | |
| count, ok := data[key].(map[string]interface{})["count"].(float64) | |
| if !ok { | |
| errorCode, ok := data[key].(map[string]interface{})["errorcode"].(float64) | |
| if ok { | |
| errorText := data[key].(map[string]interface{})["errortext"].(string) | |
| log.Infof(" [Error] while calling the API ErrorCode[%.0f] ErrorText[%s]", errorCode, errorText) | |
| FailedAPIs++ | |
| return elapsed.Seconds(), count, false | |
| } | |
| } | |
| SuccessAPIs++ | |
| return elapsed.Seconds(), count, true | |
| } | |
| func generateSignature(unsignedRequest string, secretKey string) string { | |
| unsignedRequest = strings.ToLower(unsignedRequest) | |
| hasher := hmac.New(sha1.New, []byte(secretKey)) | |
| hasher.Write([]byte(unsignedRequest)) | |
| encryptedBytes := hasher.Sum(nil) | |
| computedSignature := base64.StdEncoding.EncodeToString(encryptedBytes) | |
| return computedSignature | |
| } | |
| func readCommandsFromFile(filename string) ([]string, map[string]string, error) { | |
| file, err := os.Open(filename) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| defer file.Close() | |
| var commands []string | |
| var commandsKeywordMap = make(map[string]string) | |
| scanner := bufio.NewScanner(file) | |
| for scanner.Scan() { | |
| line := strings.TrimSpace(scanner.Text()) | |
| keyword := "" | |
| if strings.Contains(line, "keyword=") { | |
| keywordStartIndex := strings.Index(line, "keyword=") | |
| keyword = strings.TrimSpace(line[keywordStartIndex+8:]) | |
| line = strings.TrimSpace(line[:keywordStartIndex]) | |
| } | |
| if line != "" { | |
| commands = append(commands, line) | |
| commandsKeywordMap[line] = keyword | |
| } | |
| } | |
| if err := scanner.Err(); err != nil { | |
| return nil, nil, err | |
| } | |
| return commands, commandsKeywordMap, nil | |
| } |