blob: 5767b7cc890cd7030de1b5430c1eb989ad058573 [file] [log] [blame]
// 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
}