This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-csbench.git

commit a2a3564d852daef67b2bb9b6bac30fee1bdcf6dd
Author: kishankavala <[email protected]>
AuthorDate: Tue Nov 21 19:44:33 2023 +0530

    Enhance csbench tool to generate load
    
    * Generate Load. Deploy domains, network, Vm and volumes
    
    * Read config from file. Remove hard-coded values
    
    * Add flags to generate load and Tear down
    
    * Setup workers for execution and generate report
    
    * Improve logging and results formatting
    
    * Add option to output create results to a file
    
    * Update README.md
    
    * Add Apache license
    
    * Resolve comments
    
    * Fix limits and non-running VMs for volumes
    
    * Rename csmetrictool to csbench
    
    * Update Readme and add logs to update progress of operations
    
    ---------
    
    Co-authored-by: Vishesh <[email protected]>
---
 README.md                  |  70 +++++-
 apirunner/apirunner.go     | 504 +++++++++++++++++++++--------------------
 apirunner/listCommands.txt |  13 --
 config/config              |  19 +-
 config/configreader.go     | 285 +++++++++++++----------
 csbench.go                 | 554 ++++++++++++++++++++++++++++++++++++++++-----
 domain/domain.go           | 108 +++++++++
 go.mod                     |  27 ++-
 go.sum                     | 226 ++++--------------
 logger/logger.go           |  17 ++
 network/network.go         |  72 ++++++
 utils/utils.go             |  37 +++
 vm/vm.go                   |  83 +++++++
 volume/volume.go           |  79 +++++++
 14 files changed, 1462 insertions(+), 632 deletions(-)

diff --git a/README.md b/README.md
index e939384..de0aaac 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,81 @@
 # csbench
 
 This is Apache CloudStack Benchmark Tool, also known as "csbench"! Csbench is 
a tool designed to evaluate the performance and efficiency of Apache 
CloudStack. 
+
+The tool is designed to be run from a single host, and can be used to 
benchmark a single CloudStack Zone.
+
+As of now, there are two modes of operation:
+1. Setting up environment with multiple domains, accounts, users, networks, 
VMs, etc.
+2. Benchmarking a list of APIs against an existing CloudStack environment
+
+# Building
+1. Install go 1.20 or above. Follow instructions 
[here](https://go.dev/doc/install) to install golang.
+2. Clone the repository
+3. Build the binary using the below command. This will generate a binary named 
`csbench` in the current directory.
+```bash
+go build
+```
+
+# Usage
+
+Setup a config file. Check the sample config file [here](./config/config).
+
+```bash
+/csbench$ ./csbench -h
+Usage: go run csmetrictool.go -dbprofile <DB profile number>
+Options:
+  -benchmark
+        Benchmark list APIs
+  -config string
+        Path to config file (default "config/config")
+  -create
+        Create resources
+  -dbprofile int
+        DB profile number
+  -domain
+        Create domain
+  -format string
+        Format of the report (csv, tsv, table). Valid only for create (default 
"table")
+  -limits
+        Update limits to -1
+  -network
+        Create shared network
+  -output string
+        Path to output file. Valid only for create
+  -teardown
+        Tear down all subdomains
+  -vm
+        Deploy VMs
+  -volume
+        Attach Volumes to VMs
+  -workers int
+        number of workers to use while creating resources (default 10)
+```
+
+## Setting up an environment for benchmarking
+This mode of operation is designed to set up a CloudStack environment with 
multiple domains, accounts, users, networks and VMs as per the configuration 
file.
+
+To execute this mode, run the following command followed by the type of 
resources to be created:
+```bash
+csbench -create -domain -limits -network -vm -volume
+```
+
+This will create the resources under the domain specified in the config file. 
If there are existing domains, network and VMs present under the domain, they 
will be used as well for creating the resources.
+
+By default, the number of workers for executing the setup operation is 10. 
This can be changed by passing the -workers flag followed by the number of 
workers to be used.
+
+By default the results of setting up the environment are printed out to 
stdout, if you want to save the results to a file, you can pass the `-output` 
flag followed by the path to the file. And use `-format` flag to specify the 
format of the report (`csv`, `tsv`, `table`).
+
+## Benchmarking list APIs
 By internally executing a series of APIs, this tool meticulously measures the 
response times for various users, page sizes, and keyword combinations. 
 With its comprehensive benchmarking capabilities, csbench provides invaluable 
insights into the system's overall performance, allowing cloud administrators 
 and developers to fine-tune their configurations for optimal efficiency and 
seamless user experiences.
 
 Currently, it looks like
 
-/csbench$ go run csbench.go 
+```bash
+/csbench$ ./csbench -benchmark
+```
 
![image](https://github.com/shapeblue/csbench/assets/3348673/db37e176-474e-4b7d-8323-6a9a919414be)
 
 The following are configurations options 
diff --git a/apirunner/apirunner.go b/apirunner/apirunner.go
index d8ed5cd..8a3987c 100644
--- a/apirunner/apirunner.go
+++ b/apirunner/apirunner.go
@@ -1,23 +1,40 @@
+// 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 (
-    "encoding/json"
-    "io/ioutil"
-    "net/http"
-    "net/url"
-    "time"
-    "strconv"
-    "fmt"
-    "bufio"
-    "os"
-    "crypto/hmac"
-    "crypto/sha1"
-    "encoding/base64"
-    "strings"
-    "math"
-    "encoding/csv"
-
-    logger "csmetrictool/logger"
+       "bufio"
+       "crypto/hmac"
+       "crypto/sha1"
+       "encoding/base64"
+       "encoding/csv"
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "math"
+       "net/http"
+       "net/url"
+       "os"
+       "strconv"
+       "strings"
+       "time"
+
+       log "github.com/sirupsen/logrus"
 )
 
 var processedAPImap = make(map[string]bool)
@@ -27,141 +44,131 @@ 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 
{
-    logger.Log("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
+       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) {
 
-    logger.Log(fmt.Sprintf("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."))
+       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 {
-               message := fmt.Sprintf("Error reading commands from file: 
%s\n", err.Error())
-               fmt.Printf(message)
-               logger.Log(message)
+               log.Infof("Error reading commands from file: %s\n", err.Error())
                return
        }
-    reportAppend := false
-    for _, command := range commands {
-        keyword := commandsKeywordMap[command]
+       reportAppend := false
+       for _, command := range commands {
+               keyword := commandsKeywordMap[command]
                if processedAPImap[command] {
                        reportAppend = true
                }
-        if page != 0 {
-            if iterations != 1 {
-                message := fmt.Sprintf("Calling API [%s] with page %d and 
pagesize %d -> ", command, page, pagesize)
-                logger.Log(message)
-                fmt.Printf(message)
-            } else {
-                message := fmt.Sprintf("Calling API [%s] -> ", command)
-                logger.Log(message)
-                fmt.Printf(message)
-            }
-
-            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
-    }
+               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
-    if iterations != 1 {
-        logger.Log(fmt.Sprintf("Calling API %s for %d number of iterations 
with parameters %s", command, iterations, params))
-        for i := 1; i <= iterations; i++ {
-            // fmt.Printf("%d,", i)
-            logger.Log(fmt.Sprintf("Started with iteration %d for the command 
%s", i, command))
-            elapsedTime, apicount, result := executeAPI(apiURL, params)
-            count = apicount
-            if elapsedTime < minTime {
-                minTime = elapsedTime
-            }
-            if elapsedTime > maxTime {
-                maxTime = elapsedTime
-            }
-            totalTime += elapsedTime
-            if !result {
-                break
-            }
-        }
-        avgTime = totalTime / float64(iterations)
-        message := fmt.Sprintf("count [%.f] : Time in seconds [Min - %.2f] 
[Max - %.2f] [Avg - %.2f]\n", count, minTime, maxTime, avgTime)
-        fmt.Printf("%s", message)
-        logger.Log(fmt.Sprintf("Time taken for the API %s\n %s", command, 
message))
-        saveData(apiURL, count, minTime, maxTime, avgTime, page, pagesize, 
keyword, profileName, command, dbProfile, reportAppend)
-    } else {
-        elapsedTime, apicount, _ := executeAPI(apiURL, params)
-        fmt.Printf("\n  Elapsed time [%.2f seconds] for the count [%.0f]\n", 
elapsedTime, apicount)
-        logger.Log(fmt.Sprintf("\n  Elapsed time [%.2f seconds] for the count 
[%.0f]\n", elapsedTime, apicount))
-        saveData(apiURL, count, elapsedTime, elapsedTime, elapsedTime, page, 
pagesize, keyword, profileName, command, dbProfile, reportAppend)
-    }
+       var minTime = math.MaxFloat64
+       var maxTime = 0.0
+       var avgTime float64
+       var totalTime float64
+       var count float64
+       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)
+                       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)
+               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 {
-        logger.Log(fmt.Sprintf("Error parsing URL : %s with error : %s\n", 
apiURL, err))
+               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 {
-        logger.Log(fmt.Sprintf("Error creating host directory : 
report/accumulated/%s\n", host, err))
+               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 {
-        logger.Log(fmt.Sprintf("Error creating host directory : 
report/individual/%s\n", host, err))
+               log.Infof("Error creating host directory : 
report/individual/%s\n", host, err)
                return
        }
 
@@ -174,126 +181,125 @@ func saveData(apiURL string, count float64, minTime 
float64, maxTime float64, av
 
        individualFile, err := 
os.OpenFile(fmt.Sprintf("report/individual/%s/%s.csv", host, filename), 
fileMode, 0644)
        if err != nil {
-        logger.Log(fmt.Sprintf("Error opening the file CSV : 
report/individual/%s/%s.csv with error %s\n", host, filename, apiURL, err))
-               return
+               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)
+       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 {
-        logger.Log(fmt.Sprintf("Error opening the file CSV : 
report/accumulated/%s/%s.csv with error %s\n", host, filename, apiURL, err))
-               return
+               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())
-        defer filereader.Close()
-        reader := bufio.NewReader(filereader)
-        firstLine, err := 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 {
-                logger.Log(fmt.Sprintf("Error writing CSV header for the API: 
%s with error %s\n", apiURL, err))
-                return
-            }
-        }
-
-       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),
-                fmt.Sprintf("-"),
-                fmt.Sprintf("-"),
-                keyword,
-                       user,
-                       strconv.Itoa(dbProfile),
-               }
-       }
-       err = writer.Write(record)
-       if err != nil {
-            logger.Log(fmt.Sprintf("Error writing to CSV for the API: %s with 
error %s\n", apiURL, err))
-               return
-       }
-    }
+       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)
-       logger.Log(message)
+       log.Info(message)
 }
 
 func executeAPI(apiURL string, params url.Values) (float64, float64, bool) {
-    // Send the API request and calculate the time
-    apiURL = fmt.Sprintf("%s?%s", apiURL, params.Encode())
-    logger.Log(fmt.Sprintf("Running the API %s", apiURL))
-    start := time.Now()
-    resp, err := http.Get(apiURL)
-    APIscount++
-    if err != nil {
-        logger.Log(fmt.Sprintf("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 {
-        logger.Log(fmt.Sprintf("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 {
-        logger.Log(fmt.Sprintf("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)
-            message := fmt.Sprintf(" [Error] while calling the API 
ErrorCode[%.0f] ErrorText[%s]", errorCode, errorText)
-            fmt.Printf(message)
-            logger.Log(message)
-            FailedAPIs++
-            return elapsed.Seconds(), count, false
-        }
-    }
-
-    SuccessAPIs++
-    return elapsed.Seconds(), count, true
+       // Send the API request and calculate the time
+       apiURL = fmt.Sprintf("%s?%s", apiURL, params.Encode())
+       log.Infof("Running the API %s", apiURL)
+       start := time.Now()
+       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 {
@@ -304,36 +310,36 @@ func generateSignature(unsignedRequest string, secretKey 
string) string {
 
        computedSignature := base64.StdEncoding.EncodeToString(encryptedBytes)
 
-       return computedSignature
+       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())
+       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
-}
\ No newline at end of file
+               if line != "" {
+                       commands = append(commands, line)
+                       commandsKeywordMap[line] = keyword
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, nil, err
+       }
+
+       return commands, commandsKeywordMap, nil
+}
diff --git a/apirunner/listCommands.txt b/apirunner/listCommands.txt
deleted file mode 100644
index 5164fb3..0000000
--- a/apirunner/listCommands.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-listDomains keyword=test
-listAccounts
-listVirtualMachinesMetrics
-listVolumes
-listTemplates
-listNetworks
-listPublicIpAddresses
-listVPCs
-listSnapshots
-listVMSnapshot
-listServiceOfferings
-listDiskOfferings
-listHosts
diff --git a/config/config b/config/config
index 1aa3fe1..a44b543 100644
--- a/config/config
+++ b/config/config
@@ -1,11 +1,20 @@
 url = http://localhost:8080/client/api/
-iterations = 4
-page = 1
-pagesize = 20
+iterations = 1
+page = 0
+pagesize = 1000
+zoneid = d6beefe6-655e-4980-a7fe-b8e954d37029
+templateid = 5b958213-73d9-11ee-8150-7404f10c2178
+serviceofferingid = 39f91d73-0491-43e6-9d2a-1731de959044
+diskofferingid = d645f7ff-0a4d-4c34-a127-74bc1b61777a
+networkofferingid = b3161697-b891-4708-ab10-696c44472764
+parentdomainid = e9fe9167-73d8-11ee-8150-7404f10c2178
+numdomains = 2
+numvms = 2
+numvolumes = 2
 
 [admin]
-apikey = 
wLvaRCjiMCEKzo8OnF9uH6ieCGZKG_6gfkVK1Qkx5yi5pde5jlUPXku66A2d2ehIowlIanTfGVwQSxMRryN3nA
-secretkey = 
-9e4AGjrCtDThzSFVJKC3E2hPAnD9YV_bintfOvxnzPzMAuDkRB7CslOEYYkrWVqFeCTslLmxp--Bcg39br8ow
+apikey = 
j3oGqk6f0cCwLCM8H5aCTZc6pZhu6wqSP0cg5k7hTP50adMmN00s5meQp83WE1oLBVAw1k0jb6z0-4kFofzAIg
+secretkey = 
RkDYbrzy70McncZXtPEtto3mLacjOVDeOYAQ5gTYefSdlznYm_WLZEgKc8O-GIKTzK_WpwtdtQjQhjkoybrbFA
 expires = 600
 signatureversion = 3
 timeout = 3600
diff --git a/config/configreader.go b/config/configreader.go
index 1daee3e..866168d 100644
--- a/config/configreader.go
+++ b/config/configreader.go
@@ -1,20 +1,39 @@
+// 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 config
 
 import (
-    "bufio"
-    "fmt"
-    "os"
-    "strings"
-    "net/url"
+       "bufio"
+       "fmt"
+       "net/url"
+       "os"
+       "strings"
+
+       log "github.com/sirupsen/logrus"
 )
 
 type Profile struct {
-    Name             string
-    ApiKey           string
-    SecretKey        string
-    Expires          int        `default:"600"`
-    SignatureVersion int        `default:"3"`
-    Timeout          int        `default:"3600"`
+       Name             string
+       ApiKey           string
+       SecretKey        string
+       Expires          int `default:"600"`
+       SignatureVersion int `default:"3"`
+       Timeout          int `default:"3600"`
 }
 
 var URL = "http://localhost:8080/client/api/";
@@ -22,123 +41,161 @@ var Iterations = 1
 var Page = 0
 var PageSize = 0
 var Host = ""
+var ZoneId = ""
+var NetworkOfferingId = ""
+var ServiceOfferingId = ""
+var DiskOfferingId = ""
+var TemplateId = ""
+var ParentDomainId = ""
+var NumDomains = 0
+var NumVms = 0
+var NumVolumes = 0
 
 func ReadProfiles(filePath string) (map[int]*Profile, error) {
-    file, err := os.Open(filePath)
-    if err != nil {
-        return nil, fmt.Errorf("error opening file: %w", err)
-    }
-    defer file.Close()
-
-    scanner := bufio.NewScanner(file)
-    profiles := make(map[int]*Profile)
-    var currentProfile string
-
-    i := 0
-    for scanner.Scan() {
-        line := scanner.Text()
-        line = strings.TrimSpace(line)
-
-        if line == "" || strings.HasPrefix(line, ";") {
-            continue
-        }
-
-        if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
-            currentProfile = line[1 : len(line)-1]
-            i++
-            profiles[i] = &Profile{}
-            profiles[i].Name = currentProfile
-        } else {
-            // Parse key-value pairs within the profile
-            parts := strings.SplitN(line, "=", 2)
-            if len(parts) == 2 {
-                key := strings.TrimSpace(parts[0])
-                value := strings.TrimSpace(parts[1])
-
-                switch strings.ToLower(key) {
-                case "apikey":
-                    profiles[i].ApiKey = value
-                case "secretkey":
-                    profiles[i].SecretKey = value
-                case "url":
-                    URL = value
-                case "iterations":
-                    var iterations int
-                    _, err := fmt.Sscanf(value, "%d", &iterations)
-                    if err == nil {
-                        Iterations = iterations
-                    }
-                case "page":
-                    var page int
-                    _, err := fmt.Sscanf(value, "%d", &page)
-                    if err == nil {
-                        Page = page
-                    }
-                case "pagesize":
-                    var pagesize int
-                    _, err := fmt.Sscanf(value, "%d", &pagesize)
-                    if err == nil {
-                        PageSize = pagesize
-                    }
-                case "expires":
-                    var expires int
-                    _, err := fmt.Sscanf(value, "%d", &expires)
-                    if err == nil {
-                        profiles[i].Expires = expires
-                    }
-                case "signatureversion":
-                    var signatureVersion int
-                    _, err := fmt.Sscanf(value, "%d", &signatureVersion)
-                    if err == nil {
-                        profiles[i].SignatureVersion = signatureVersion
-                    }
-                case "timeout":
-                    var timeout int
-                    _, err := fmt.Sscanf(value, "%d", &timeout)
-                    if err == nil {
-                        profiles[i].Timeout = timeout
-                    }
-                }
-            }
-        }
-    }
-
-    if err := scanner.Err(); err != nil {
-        return nil, fmt.Errorf("error scanning file: %w", err)
-    }
-
-    if len(profiles) == 0 {
-        fmt.Println("No roles are defined in the configuration file")
-        return nil, fmt.Errorf("No roles are defined in the configuration 
file: %w", err)
-    }
-
-    if URL == "" {
-        fmt.Println("URL not found in the configuration, please verify")
-        os.Exit(1)
-    }
+       file, err := os.Open(filePath)
+       if err != nil {
+               return nil, fmt.Errorf("error opening file: %w", err)
+       }
+       defer file.Close()
+
+       scanner := bufio.NewScanner(file)
+       profiles := make(map[int]*Profile)
+       var currentProfile string
+
+       i := 0
+       for scanner.Scan() {
+               line := scanner.Text()
+               line = strings.TrimSpace(line)
+
+               if line == "" || strings.HasPrefix(line, ";") {
+                       continue
+               }
+
+               if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") 
{
+                       currentProfile = line[1 : len(line)-1]
+                       i++
+                       profiles[i] = &Profile{}
+                       profiles[i].Name = currentProfile
+               } else {
+                       // Parse key-value pairs within the profile
+                       parts := strings.SplitN(line, "=", 2)
+                       if len(parts) == 2 {
+                               key := strings.TrimSpace(parts[0])
+                               value := strings.TrimSpace(parts[1])
+
+                               switch strings.ToLower(key) {
+                               case "apikey":
+                                       profiles[i].ApiKey = value
+                               case "secretkey":
+                                       profiles[i].SecretKey = value
+                               case "url":
+                                       URL = value
+                               case "iterations":
+                                       var iterations int
+                                       _, err := fmt.Sscanf(value, "%d", 
&iterations)
+                                       if err == nil {
+                                               Iterations = iterations
+                                       }
+                               case "page":
+                                       var page int
+                                       _, err := fmt.Sscanf(value, "%d", &page)
+                                       if err == nil {
+                                               Page = page
+                                       }
+                               case "pagesize":
+                                       var pagesize int
+                                       _, err := fmt.Sscanf(value, "%d", 
&pagesize)
+                                       if err == nil {
+                                               PageSize = pagesize
+                                       }
+                               case "expires":
+                                       var expires int
+                                       _, err := fmt.Sscanf(value, "%d", 
&expires)
+                                       if err == nil {
+                                               profiles[i].Expires = expires
+                                       }
+                               case "signatureversion":
+                                       var signatureVersion int
+                                       _, err := fmt.Sscanf(value, "%d", 
&signatureVersion)
+                                       if err == nil {
+                                               profiles[i].SignatureVersion = 
signatureVersion
+                                       }
+                               case "timeout":
+                                       var timeout int
+                                       _, err := fmt.Sscanf(value, "%d", 
&timeout)
+                                       if err == nil {
+                                               profiles[i].Timeout = timeout
+                                       }
+                               case "zoneid":
+                                       ZoneId = value
+                               case "networkofferingid":
+                                       NetworkOfferingId = value
+                               case "serviceofferingid":
+                                       ServiceOfferingId = value
+                               case "diskofferingid":
+                                       DiskOfferingId = value
+                               case "templateid":
+                                       TemplateId = value
+                               case "parentdomainid":
+                                       ParentDomainId = value
+                               case "numdomains":
+                                       var numDomains int
+                                       _, err := fmt.Sscanf(value, "%d", 
&numDomains)
+                                       if err == nil {
+                                               NumDomains = numDomains
+                                       }
+                               case "numvms":
+                                       var numVms int
+                                       _, err := fmt.Sscanf(value, "%d", 
&numVms)
+                                       if err == nil {
+                                               NumVms = numVms
+                                       }
+                               case "numvolumes":
+                                       var numVolumes int
+                                       _, err := fmt.Sscanf(value, "%d", 
&numVolumes)
+                                       if err == nil {
+                                               NumVolumes = numVolumes
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, fmt.Errorf("error scanning file: %w", err)
+       }
+
+       if len(profiles) == 0 {
+               fmt.Println("No roles are defined in the configuration file")
+               return nil, fmt.Errorf("no roles are defined in the 
configuration file: %w", err)
+       }
+
+       if URL == "" {
+               log.Fatalln("URL not found in the configuration, please verify")
+       }
 
        parsedURL, err := url.Parse(URL)
        if err != nil {
-        fmt.Println("Error parsing URL : %s with error : %s\n", URL, err)
-               return nil, fmt.Errorf("Error parsing URL : %s with error : 
%s\n", URL, err)
+               log.Errorf("Error parsing URL : %s with error : %s", URL, err)
+               return nil, fmt.Errorf("error parsing url : %s with error : 
%s", URL, err)
        }
        Host = parsedURL.Hostname()
 
-    validateConfig(profiles)
+       validateConfig(profiles)
 
-    return profiles, nil
+       return profiles, nil
 }
 
-func validateConfig(profiles map[int]*Profile) (bool) {
+func validateConfig(profiles map[int]*Profile) bool {
 
-    result := true
+       result := true
        for i, profile := range profiles {
                if profile.ApiKey == "" || profile.SecretKey == "" {
-                   message := "Please check ApiKey, SecretKey of the profile. 
They should not be empty"
-                   fmt.Printf("Skipping profile [%s] : %s\n", profile.Name, 
message)
-                   delete(profiles, i)
-            result = false
-        }
+                       message := "Please check ApiKey, SecretKey of the 
profile. They should not be empty"
+                       fmt.Printf("Skipping profile [%s] : %s\n", 
profile.Name, message)
+                       delete(profiles, i)
+                       result = false
+               }
        }
 
        return result
diff --git a/csbench.go b/csbench.go
index 74d5e10..5ab84af 100644
--- a/csbench.go
+++ b/csbench.go
@@ -1,35 +1,80 @@
+// 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 main
 
 import (
-    "os"
-    "fmt"
-    "flag"
-    "strings"
-
-    config "csmetrictool/config"
-    apirunner "csmetrictool/apirunner"
-    logger "csmetrictool/logger"
+       "csbench/domain"
+       "csbench/network"
+       "csbench/vm"
+       "csbench/volume"
+       "flag"
+       "fmt"
+       "io"
+       "math"
+       "os"
+       "strings"
+       "time"
+
+       "csbench/apirunner"
+       "csbench/config"
+
+       log "github.com/sirupsen/logrus"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/jedib0t/go-pretty/v6/table"
+       "github.com/montanaflynn/stats"
+       "github.com/sourcegraph/conc/pool"
 )
 
 var (
-    profiles = make(map[int]*config.Profile)
+       profiles = make(map[int]*config.Profile)
 )
 
-func readConfigurations() map[int]*config.Profile {
-    profiles, err := config.ReadProfiles("config/config")
-    if err != nil {
-        fmt.Println("Error reading profiles:", err)
-        os.Exit(1)
-    }
+type Result struct {
+       Success  bool
+       Duration float64
+}
+
+func init() {
+       logFile, err := os.OpenFile("csmetrics.log", 
os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+       if err != nil {
+               log.Fatalf("Failed to create log file: %v", err)
+       }
+
+       mw := io.MultiWriter(os.Stdout, logFile)
+
+       log.SetOutput(mw)
+}
+
+func readConfigurations(configFile string) map[int]*config.Profile {
+       profiles, err := config.ReadProfiles(configFile)
+       if err != nil {
+               log.Fatal("Error reading profiles:", err)
+       }
 
-    return profiles
+       return profiles
 }
 
 func logConfigurationDetails(profiles map[int]*config.Profile) {
-    apiURL := config.URL
-    iterations := config.Iterations
-    page := config.Page
-    pagesize := config.PageSize
+       apiURL := config.URL
+       iterations := config.Iterations
+       page := config.Page
+       pagesize := config.PageSize
        host := config.Host
 
        userProfileNames := make([]string, 0, len(profiles))
@@ -37,64 +82,457 @@ func logConfigurationDetails(profiles 
map[int]*config.Profile) {
                userProfileNames = append(userProfileNames, profile.Name)
        }
 
-    fmt.Printf("\n\n\033[1;34mBenchmarking the CloudStack environment [%s] 
with the following configuration\033[0m\n\n", apiURL)
-    fmt.Printf("Management server : %s\n", host)
-    fmt.Printf("Roles : %s\n", strings.Join(userProfileNames, ","))
-    fmt.Printf("Iterations : %d\n", iterations)
-    fmt.Printf("Page : %d\n", page)
-    fmt.Printf("PageSize : %d\n\n", pagesize)
-
-    logger.Log(fmt.Sprintf("Found %d profiles in the configuration: ", 
len(profiles)))
+       fmt.Printf("\n\n\033[1;34mBenchmarking the CloudStack environment [%s] 
with the following configuration\033[0m\n\n", apiURL)
+       fmt.Printf("Management server : %s\n", host)
+       fmt.Printf("Roles : %s\n", strings.Join(userProfileNames, ","))
+       fmt.Printf("Iterations : %d\n", iterations)
+       fmt.Printf("Page : %d\n", page)
+       fmt.Printf("PageSize : %d\n\n", pagesize)
 
+       log.Infof("Found %d profiles in the configuration: ", len(profiles))
+       log.Infof("Management server : %s", host)
 }
 
 func logReport() {
-    fmt.Printf("\n\n\nLog file : csmetrics.log\n")
-    fmt.Printf("Reports directory per API : report/%s/\n", config.Host)
-    fmt.Printf("Number of APIs : %d\n", apirunner.APIscount)
-    fmt.Printf("Successful APIs : %d\n", apirunner.SuccessAPIs)
-    fmt.Printf("Failed APIs : %d\n", apirunner.FailedAPIs)
-    fmt.Printf("Time in seconds per API: %.2f (avg)\n", 
apirunner.TotalTime/float64(apirunner.APIscount))
-    
fmt.Printf("\n\n\033[1;34m--------------------------------------------------------------------------------\033[0m\n"
 +
-        "                            Done with benchmarking\n" +
-        
"\033[1;34m--------------------------------------------------------------------------------\033[0m\n\n")
+       fmt.Printf("\n\n\nLog file : csmetrics.log\n")
+       fmt.Printf("Reports directory per API : report/%s/\n", config.Host)
+       fmt.Printf("Number of APIs : %d\n", apirunner.APIscount)
+       fmt.Printf("Successful APIs : %d\n", apirunner.SuccessAPIs)
+       fmt.Printf("Failed APIs : %d\n", apirunner.FailedAPIs)
+       fmt.Printf("Time in seconds per API: %.2f (avg)\n", 
apirunner.TotalTime/float64(apirunner.APIscount))
+       
fmt.Printf("\n\n\033[1;34m--------------------------------------------------------------------------------\033[0m\n"
 +
+               "                            Done with benchmarking\n" +
+               
"\033[1;34m--------------------------------------------------------------------------------\033[0m\n\n")
+}
+
+func getSamples(results []*Result) (stats.Float64Data, stats.Float64Data, 
stats.Float64Data) {
+       var allExecutionsSample stats.Float64Data
+       var successfulExecutionSample stats.Float64Data
+       var failedExecutionSample stats.Float64Data
+
+       for _, result := range results {
+               duration := math.Round(result.Duration*1000) / 1000
+               allExecutionsSample = append(allExecutionsSample, duration)
+               if result.Success {
+                       successfulExecutionSample = 
append(successfulExecutionSample, duration)
+               } else {
+                       failedExecutionSample = append(failedExecutionSample, 
duration)
+               }
+       }
+
+       return allExecutionsSample, successfulExecutionSample, 
failedExecutionSample
+}
+
+func getRowFromSample(key string, sample stats.Float64Data) table.Row {
+       min, _ := sample.Min()
+       min = math.Round(min*1000) / 1000
+       max, _ := sample.Max()
+       max = math.Round(max*1000) / 1000
+       mean, _ := sample.Mean()
+       mean = math.Round(mean*1000) / 1000
+       median, _ := sample.Median()
+       median = math.Round(median*1000) / 1000
+       percentile90, _ := sample.Percentile(90)
+       percentile90 = math.Round(percentile90*1000) / 1000
+       percentile95, _ := sample.Percentile(95)
+       percentile95 = math.Round(percentile95*1000) / 1000
+       percentile99, _ := sample.Percentile(99)
+       percentile99 = math.Round(percentile99*1000) / 1000
+
+       return table.Row{key, len(sample), min, max, mean, median, 
percentile90, percentile95, percentile99}
+}
+
+/*
+This function will generate a report with the following details:
+ 1. Total Number of executions
+ 2. Number of successful executions
+ 3. Number of failed exections
+ 4. Different statistics like min, max, avg, median, 90th percentile, 95th 
percentile, 99th percentile for above 3
+
+Output format:
+ 1. CSV
+ 2. TSV
+ 3. Table
+*/
+func generateReport(results map[string][]*Result, format string, outputFile 
string) {
+       fmt.Println("Generating report")
+
+       t := table.NewWriter()
+       t.SetOutputMirror(os.Stdout)
+       t.AppendHeader(table.Row{"Type", "Count", "Min", "Max", "Avg", 
"Median", "90th percentile", "95th percentile", "99th percentile"})
+
+       for key, result := range results {
+               allExecutionsSample, successfulExecutionSample, 
failedExecutionSample := getSamples(result)
+               t.AppendRow(getRowFromSample(fmt.Sprintf("%s - All", key), 
allExecutionsSample))
+
+               if failedExecutionSample.Len() != 0 {
+                       t.AppendRow(getRowFromSample(fmt.Sprintf("%s - 
Successful", key), successfulExecutionSample))
+                       t.AppendRow(getRowFromSample(fmt.Sprintf("%s - Failed", 
key), failedExecutionSample))
+               }
+       }
+
+       if outputFile != "" {
+               f, err := os.Create(outputFile)
+               if err != nil {
+                       log.Error("Error creating file: ", err)
+               }
+               defer f.Close()
+               t.SetOutputMirror(f)
+       }
+       switch format {
+       case "csv":
+               t.RenderCSV()
+       case "tsv":
+               t.RenderTSV()
+       case "table":
+               t.Render()
+       }
 }
 
 func main() {
        dbprofile := flag.Int("dbprofile", 0, "DB profile number")
+       create := flag.Bool("create", false, "Create resources")
+       benchmark := flag.Bool("benchmark", false, "Benchmark list APIs")
+       domainFlag := flag.Bool("domain", false, "Create domain")
+       limitsFlag := flag.Bool("limits", false, "Update limits to -1")
+       networkFlag := flag.Bool("network", false, "Create shared network")
+       vmFlag := flag.Bool("vm", false, "Deploy VMs")
+       volumeFlag := flag.Bool("volume", false, "Attach Volumes to VMs")
+       tearDown := flag.Bool("teardown", false, "Tear down all subdomains")
+       workers := flag.Int("workers", 10, "number of workers to use while 
creating resources")
+       format := flag.String("format", "table", "Format of the report (csv, 
tsv, table). Valid only for create")
+       outputFile := flag.String("output", "", "Path to output file. Valid 
only for create")
+       configFile := flag.String("config", "config/config", "Path to config 
file")
        flag.Usage = func() {
-               fmt.Fprintf(os.Stderr, "Usage: go run csmetrictool.go 
--dbprofile <DB profile number>\n")
+               fmt.Fprintf(os.Stderr, "Usage: go run csmetrictool.go 
-dbprofile <DB profile number>\n")
                fmt.Fprintf(os.Stderr, "Options:\n")
                flag.PrintDefaults()
        }
        flag.Parse()
+
+       if !(*create || *benchmark || *tearDown) {
+               log.Fatal("Please provide one of the following options: 
-create, -benchmark, -teardown")
+       }
+
+       if *create && !(*domainFlag || *limitsFlag || *networkFlag || *vmFlag 
|| *volumeFlag) {
+               log.Fatal("Please provide one of the following options with 
create: -domain, -limits, -network, -vm, -volume")
+       }
+
+       switch *format {
+       case "csv", "tsv", "table":
+               // valid format, continue
+       default:
+               log.Fatal("Invalid format. Please provide one of the following: 
csv, tsv, table")
+       }
+
        if *dbprofile < 0 {
-               fmt.Println("Invalid DB profile number. Please provide a 
positive integer.")
-               return
+               log.Fatal("Invalid DB profile number. Please provide a positive 
integer.")
+       }
+
+       profiles = readConfigurations(*configFile)
+       apiURL := config.URL
+       iterations := config.Iterations
+       page := config.Page
+       pagesize := config.PageSize
+
+       if *create {
+               results := createResources(domainFlag, limitsFlag, networkFlag, 
vmFlag, volumeFlag, workers)
+               generateReport(results, *format, *outputFile)
+       }
+
+       if *benchmark {
+               log.Infof("\nStarted benchmarking the CloudStack environment 
[%s]", apiURL)
+
+               logConfigurationDetails(profiles)
+
+               for i, profile := range profiles {
+                       userProfileName := profile.Name
+                       log.Infof("Using profile %d.%s for benchmarking", i, 
userProfileName)
+                       
fmt.Printf("\n\033[1;34m============================================================\033[0m\n")
+                       fmt.Printf("                    Profile: [%s]\n", 
userProfileName)
+                       
fmt.Printf("\033[1;34m============================================================\033[0m\n")
+                       apirunner.RunAPIs(userProfileName, apiURL, 
profile.ApiKey, profile.SecretKey, profile.Expires, profile.SignatureVersion, 
iterations, page, pagesize, *dbprofile)
+               }
+               logReport()
+
+               log.Infof("Done with benchmarking the CloudStack environment 
[%s]", apiURL)
        }
 
-    profiles = readConfigurations()
-    apiURL := config.URL
-    iterations := config.Iterations
-    page := config.Page
-    pagesize := config.PageSize
+       if *tearDown {
+               tearDownEnv()
+       }
+}
+
+func createResources(domainFlag, limitsFlag, networkFlag, vmFlag, volumeFlag 
*bool, workers *int) map[string][]*Result {
+       apiURL := config.URL
+
+       for _, profile := range profiles {
+               if profile.Name == "admin" {
+
+                       numVmsPerNetwork := config.NumVms
+                       numVolumesPerVM := config.NumVolumes
+
+                       cs := cloudstack.NewAsyncClient(apiURL, profile.ApiKey, 
profile.SecretKey, false)
+
+                       var results = make(map[string][]*Result)
+
+                       if *domainFlag {
+                               workerPool := 
pool.NewWithResults[*Result]().WithMaxGoroutines(*workers)
+                               results["domain"] = createDomains(workerPool, 
cs, config.ParentDomainId, config.NumDomains)
+                       }
+
+                       if *limitsFlag {
+                               workerPool := 
pool.NewWithResults[*Result]().WithMaxGoroutines(*workers)
+                               results["limits"] = updateLimits(workerPool, 
cs, config.ParentDomainId)
+                       }
+
+                       if *networkFlag {
+                               workerPool := 
pool.NewWithResults[*Result]().WithMaxGoroutines(*workers)
+                               results["network"] = createNetwork(workerPool, 
cs, config.ParentDomainId)
+                       }
+
+                       if *vmFlag {
+                               workerPool := 
pool.NewWithResults[*Result]().WithMaxGoroutines(*workers)
+                               results["vm"] = createVms(workerPool, cs, 
config.ParentDomainId, numVmsPerNetwork)
+                       }
+
+                       if *volumeFlag {
+                               workerPool := 
pool.NewWithResults[*Result]().WithMaxGoroutines(*workers)
+                               results["volume"] = createVolumes(workerPool, 
cs, config.ParentDomainId, numVolumesPerVM)
+                       }
+
+                       return results
+               }
+       }
+       return nil
+}
+
+func createDomains(workerPool *pool.ResultPool[*Result], cs 
*cloudstack.CloudStackClient, parentDomainId string, count int) []*Result {
+       progressMarker := int(math.Ceil(float64(count) / 10.0))
+       start := time.Now()
+       log.Infof("Creating %d domains", count)
+       for i := 0; i < count; i++ {
+               if (i+1)%progressMarker == 0 {
+                       log.Infof("Created %d domains", i+1)
+               }
+               workerPool.Go(func() *Result {
+                       taskStart := time.Now()
+                       dmn, err := domain.CreateDomain(cs, parentDomainId)
+                       if err != nil {
+                               return &Result{
+                                       Success:  false,
+                                       Duration: 
time.Since(taskStart).Seconds(),
+                               }
+                       }
+                       _, err = domain.CreateAccount(cs, dmn.Id)
+                       if err != nil {
+                               return &Result{
+                                       Success:  false,
+                                       Duration: 
time.Since(taskStart).Seconds(),
+                               }
+                       }
+
+                       return &Result{
+                               Success:  true,
+                               Duration: time.Since(taskStart).Seconds(),
+                       }
+               })
+       }
+       res := workerPool.Wait()
+       log.Infof("Created %d domains in %.2f seconds", count, 
time.Since(start).Seconds())
+       return res
+}
+
+func updateLimits(workerPool *pool.ResultPool[*Result], cs 
*cloudstack.CloudStackClient, parentDomainId string) []*Result {
+       log.Infof("Fetching subdomains for domain %s", parentDomainId)
+       domains := domain.ListSubDomains(cs, parentDomainId)
+       accounts := make([]*cloudstack.Account, 0)
+       for _, dmn := range domains {
+               accounts = append(accounts, domain.ListAccounts(cs, dmn.Id)...)
+       }
+
+       progressMarker := int(math.Ceil(float64(len(accounts)) / 10.0))
+       start := time.Now()
+       log.Infof("Updating limits for %d accounts", len(accounts))
+       for i, account := range accounts {
+               if (i+1)%progressMarker == 0 {
+                       log.Infof("Updated limits for %d accounts", i+1)
+               }
+               account := account
+               workerPool.Go(func() *Result {
+                       taskStart := time.Now()
+                       resp := domain.UpdateLimits(cs, account)
+                       return &Result{
+                               Success:  resp,
+                               Duration: time.Since(taskStart).Seconds(),
+                       }
+               })
+       }
+       res := workerPool.Wait()
+       log.Infof("Updated limits for %d accounts in %.2f seconds", 
len(accounts), time.Since(start).Seconds())
+       return res
+}
+
+func createNetwork(workerPool *pool.ResultPool[*Result], cs 
*cloudstack.CloudStackClient, parentDomainId string) []*Result {
+       log.Infof("Fetching subdomains for domain %s", parentDomainId)
+       domains := domain.ListSubDomains(cs, parentDomainId)
+
+       progressMarker := int(math.Ceil(float64(len(domains)) / 10.0))
+       start := time.Now()
+       log.Infof("Creating %d networks", len(domains))
+       for i, dmn := range domains {
+               if (i+1)%progressMarker == 0 {
+                       log.Infof("Created %d networks", i+1)
+               }
+               i := i
+               dmn := dmn
+               workerPool.Go(func() *Result {
+                       taskStart := time.Now()
+                       _, err := network.CreateNetwork(cs, dmn.Id, i)
+                       if err != nil {
+                               return &Result{
+                                       Success:  false,
+                                       Duration: 
time.Since(taskStart).Seconds(),
+                               }
+                       }
+                       return &Result{
+                               Success:  true,
+                               Duration: time.Since(taskStart).Seconds(),
+                       }
+               })
+       }
+       res := workerPool.Wait()
+       log.Infof("Created %d networks in %.2f seconds", len(domains), 
time.Since(start).Seconds())
+       return res
+}
 
-       logger.Log(fmt.Sprintf("\nStarted benchmarking the CloudStack 
environment [%s]", apiURL))
+func createVms(workerPool *pool.ResultPool[*Result], cs 
*cloudstack.CloudStackClient, parentDomainId string, numVmPerNetwork int) 
[]*Result {
+       log.Infof("Fetching subdomains & accounts for domain %s", 
parentDomainId)
+       domains := domain.ListSubDomains(cs, parentDomainId)
+       var accounts []*cloudstack.Account
+       for i := 0; i < len(domains); i++ {
+               account := domain.ListAccounts(cs, domains[i].Id)
+               accounts = append(accounts, account...)
+       }
 
-    logConfigurationDetails(profiles)
+       domainIdAccountMapping := make(map[string]*cloudstack.Account)
+       for _, account := range accounts {
+               domainIdAccountMapping[account.Domainid] = account
+       }
 
-    for i := 1; i <= len(profiles); i++ {
-        profile := profiles[i]
-        userProfileName := profile.Name
-        logger.Log(fmt.Sprintf("Using profile %d.%s for benchmarking", i, 
userProfileName))
-        
fmt.Printf("\n\033[1;34m============================================================\033[0m\n")
-        fmt.Printf("                    Profile: [%s]\n", userProfileName)
-        
fmt.Printf("\033[1;34m============================================================\033[0m\n")
-        apirunner.RunAPIs(userProfileName, apiURL, profile.ApiKey, 
profile.SecretKey, profile.Expires, profile.SignatureVersion, iterations, page, 
pagesize, *dbprofile)
-    }
+       log.Infof("Fetching networks for subdomains in domain %s", 
parentDomainId)
+       var allNetworks []*cloudstack.Network
+       for _, domain := range domains {
+               network, _ := network.ListNetworks(cs, domain.Id)
+               allNetworks = append(allNetworks, network...)
+       }
 
-    logReport()
+       progressMarker := 
int(math.Ceil(float64(len(allNetworks)*numVmPerNetwork) / 10.0))
+       start := time.Now()
+       log.Infof("Creating %d VMs", len(allNetworks)*numVmPerNetwork)
+       for i, network := range allNetworks {
+               network := network
+               for j := 1; j <= numVmPerNetwork; j++ {
 
-       logger.Log(fmt.Sprintf("Done with benchmarking the CloudStack 
environment [%s]", apiURL))
+                       if (i*j+j)%progressMarker == 0 {
+                               log.Infof("Created %d VMs", i*j+j)
+                       }
+                       workerPool.Go(func() *Result {
+                               taskStart := time.Now()
+                               _, err := vm.DeployVm(cs, network.Domainid, 
network.Id, domainIdAccountMapping[network.Domainid].Name)
+                               if err != nil {
+                                       return &Result{
+                                               Success:  false,
+                                               Duration: 
time.Since(taskStart).Seconds(),
+                                       }
+                               }
+                               return &Result{
+                                       Success:  true,
+                                       Duration: 
time.Since(taskStart).Seconds(),
+                               }
+                       })
+               }
+       }
+       res := workerPool.Wait()
+       log.Infof("Created %d VMs in %.2f seconds", 
len(allNetworks)*numVmPerNetwork, time.Since(start).Seconds())
+       return res
 }
 
+func createVolumes(workerPool *pool.ResultPool[*Result], cs 
*cloudstack.CloudStackClient, parentDomainId string, numVolumesPerVM int) 
[]*Result {
+       log.Infof("Fetching all VMs in subdomains for domain %s", 
parentDomainId)
+       domains := domain.ListSubDomains(cs, parentDomainId)
+       var allVMs []*cloudstack.VirtualMachine
+       for _, dmn := range domains {
+               vms, err := vm.ListVMs(cs, dmn.Id)
+               if err != nil {
+                       log.Warn("Error listing VMs: ", err)
+                       continue
+               }
+               allVMs = append(allVMs, vms...)
+       }
+
+       progressMarker := int(math.Ceil(float64(len(allVMs)*numVolumesPerVM) / 
10.0))
+       start := time.Now()
+
+       log.Infof("Creating %d volumes", len(allVMs)*numVolumesPerVM)
+       unsuitableVmCount := 0
+
+       for i, vm := range allVMs {
+               vm := vm
+               if vm.State != "Running" && vm.State != "Stopped" {
+                       unsuitableVmCount++
+                       continue
+               }
+               for j := 1; j <= numVolumesPerVM; j++ {
+                       if (i*j+j)%progressMarker == 0 {
+                               log.Infof("Created %d volumes", i*j+j)
+                       }
+
+                       workerPool.Go(func() *Result {
+                               taskStart := time.Now()
+                               vol, err := volume.CreateVolume(cs, 
vm.Domainid, vm.Account)
+                               if err != nil {
+                                       return &Result{
+                                               Success:  false,
+                                               Duration: 
time.Since(taskStart).Seconds(),
+                                       }
+                               }
+                               _, err = volume.AttachVolume(cs, vol.Id, vm.Id)
+                               if err != nil {
+                                       return &Result{
+                                               Success:  false,
+                                               Duration: 
time.Since(taskStart).Seconds(),
+                                       }
+                               }
+                               return &Result{
+                                       Success:  true,
+                                       Duration: 
time.Since(taskStart).Seconds(),
+                               }
+                       })
+               }
+       }
+       if unsuitableVmCount > 0 {
+               log.Warnf("Found %d VMs in unsuitable state", unsuitableVmCount)
+       }
+       res := workerPool.Wait()
+       log.Infof("Created %d volumes in %.2f seconds", 
(len(allVMs)-unsuitableVmCount)*numVolumesPerVM, time.Since(start).Seconds())
+       return res
+}
+
+func tearDownEnv() {
+       parentDomain := config.ParentDomainId
+       apiURL := config.URL
+
+       for _, profile := range profiles {
+               userProfileName := profile.Name
+               if userProfileName == "admin" {
+                       cs := cloudstack.NewAsyncClient(apiURL, profile.ApiKey, 
profile.SecretKey, false)
+                       domains := domain.ListSubDomains(cs, parentDomain)
+                       log.Infof("Deleting %d domains", len(domains))
+                       for _, subdomain := range domains {
+                               domain.DeleteDomain(cs, subdomain.Id)
+                       }
+                       break
+               }
+       }
+}
diff --git a/domain/domain.go b/domain/domain.go
new file mode 100644
index 0000000..cfc0b14
--- /dev/null
+++ b/domain/domain.go
@@ -0,0 +1,108 @@
+// 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 domain
+
+import (
+       "csbench/config"
+       "csbench/utils"
+       "log"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+)
+
+func CreateDomain(cs *cloudstack.CloudStackClient, parentDomainId string) 
(*cloudstack.CreateDomainResponse, error) {
+       domainName := "Domain-" + utils.RandomString(10)
+       p := cs.Domain.NewCreateDomainParams(domainName)
+       p.SetParentdomainid(parentDomainId)
+       resp, err := cs.Domain.CreateDomain(p)
+
+       if err != nil {
+               log.Printf("Failed to create domain due to: %v", err)
+               return nil, err
+       }
+       return resp, err
+}
+
+func DeleteDomain(cs *cloudstack.CloudStackClient, domainId string) (bool, 
error) {
+       deleteParams := cs.Domain.NewDeleteDomainParams(domainId)
+       deleteParams.SetCleanup(true)
+       delResp, err := cs.Domain.DeleteDomain(deleteParams)
+       if err != nil {
+               log.Printf("Failed to delete domain with id  %s due to %v", 
domainId, err)
+               return delResp.Success, err
+       }
+       return delResp.Success, nil
+}
+
+func CreateAccount(cs *cloudstack.CloudStackClient, domainId string) 
(*cloudstack.CreateAccountResponse, error) {
+       accountName := "Account-" + utils.RandomString(10)
+       p := cs.Account.NewCreateAccountParams("test@test", accountName, 
"Account", "password", accountName)
+       p.SetDomainid(domainId)
+       p.SetAccounttype(2)
+
+       resp, err := cs.Account.CreateAccount(p)
+
+       if err != nil {
+               log.Printf("Failed to create account due to: %v", err)
+               return nil, err
+       }
+       return resp, err
+}
+
+func ListSubDomains(cs *cloudstack.CloudStackClient, domainId string) 
[]*cloudstack.DomainChildren {
+       p := cs.Domain.NewListDomainChildrenParams()
+       p.SetId(domainId)
+       p.SetPage(1)
+       p.SetPagesize(config.PageSize)
+       // TODO: Handle pagination to get all domains
+       resp, err := cs.Domain.ListDomainChildren(p)
+       if err != nil {
+               log.Printf("Failed to list domains due to: %v", err)
+               return nil
+       }
+       return resp.DomainChildren
+}
+
+func ListAccounts(cs *cloudstack.CloudStackClient, domainId string) 
[]*cloudstack.Account {
+       p := cs.Account.NewListAccountsParams()
+       p.SetDomainid(domainId)
+       p.SetPage(1)
+       p.SetPagesize(config.PageSize)
+       // TODO: Handle pagination to get all domains
+       resp, err := cs.Account.ListAccounts(p)
+       if err != nil {
+               log.Printf("Failed to list domains due to: %v", err)
+               return nil
+       }
+       return resp.Accounts
+}
+
+func UpdateLimits(cs *cloudstack.CloudStackClient, account 
*cloudstack.Account) bool {
+       for i := 0; i <= 11; i++ {
+               p := cs.Limit.NewUpdateResourceLimitParams(i)
+               p.SetAccount(account.Name)
+               p.SetDomainid(account.Domainid)
+               p.SetMax(-1)
+               _, err := cs.Limit.UpdateResourceLimit(p)
+               if err != nil {
+                       log.Printf("Failed to update resource limit due to: 
%v", err)
+                       return false
+               }
+       }
+       return true
+}
diff --git a/go.mod b/go.mod
index 75d6496..6366ce4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,20 @@
-module csmetrictool
+module csbench
 
-go 1.17
+go 1.20
 
 require (
-       git.sr.ht/~sbinet/gg v0.4.1 // indirect
-       github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
-       github.com/go-fonts/liberation v0.3.1 // indirect
-       github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect
-       github.com/go-pdf/fpdf v0.8.0 // indirect
-       github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // 
indirect
-       golang.org/x/image v0.7.0 // indirect
-       golang.org/x/text v0.9.0 // indirect
-       gonum.org/v1/plot v0.13.0 // indirect
+       github.com/apache/cloudstack-go/v2 v2.15.0
+       github.com/jedib0t/go-pretty/v6 v6.4.8
+       github.com/montanaflynn/stats v0.7.1
+       github.com/sirupsen/logrus v1.9.3
+       github.com/sourcegraph/conc v0.3.0
+)
+
+require (
+       github.com/golang/mock v1.6.0 // indirect
+       github.com/mattn/go-runewidth v0.0.13 // indirect
+       github.com/rivo/uniseg v0.2.0 // indirect
+       go.uber.org/atomic v1.7.0 // indirect
+       go.uber.org/multierr v1.9.0 // indirect
+       golang.org/x/sys v0.13.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 1640139..f097dcd 100644
--- a/go.sum
+++ b/go.sum
@@ -1,200 +1,64 @@
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod 
h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
-gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod 
h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
-gioui.org v0.0.0-20230418224039-a7c9ca99f3be/go.mod 
h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw=
-gioui.org v0.0.0-20230506155350-febadd314531/go.mod 
h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw=
-gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
-gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
-gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
-gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
-gioui.org/x v0.0.0-20230426160849-752f112c7a59/go.mod 
h1:nMctdnZS2HKxfSXb+bCPnhw1n2LLsXoxtTarZjtIBuI=
-git.sr.ht/~jackmordaunt/go-toast v1.0.0/go.mod 
h1:aIuRX/HdBOz7yRS8rOVYQCwJQlFS7DbYBTpUV0SHeeg=
-git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
-git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod 
h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
-git.sr.ht/~sbinet/gg v0.3.1/go.mod 
h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
-git.sr.ht/~sbinet/gg v0.4.1 h1:YccqPPS57/TpqX2fFnSRlisrqQ43gEdqVm3JtabPrp0=
-git.sr.ht/~sbinet/gg v0.4.1/go.mod 
h1:xKrQ22W53kn8Hlq+gzYeyyohGMwR8yGgSMlVpY/mHGc=
-git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod 
h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
-github.com/BurntSushi/toml v0.3.1/go.mod 
h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod 
h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod 
h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
-github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod 
h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
-github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod 
h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
-github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b 
h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
-github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod 
h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
-github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod 
h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg=
-github.com/boombuler/barcode v1.0.0/go.mod 
h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/boombuler/barcode v1.0.1/go.mod 
h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/apache/cloudstack-go/v2 v2.15.0 
h1:oojn1qx0+wBwrFSSmA2rL8XjWd4BXqwYo0RVCrAXoHk=
+github.com/apache/cloudstack-go/v2 v2.15.0/go.mod 
h1:Mc+tXpujtslBuZFk5atoGT2LanVxOrXS2GGgidAoz1A=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/esiqveland/notify v0.11.0/go.mod 
h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvCZyknsuonBc=
-github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod 
h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
-github.com/fogleman/gg v1.3.0/go.mod 
h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
-github.com/go-fonts/dejavu v0.1.0 
h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
-github.com/go-fonts/dejavu v0.1.0/go.mod 
h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
-github.com/go-fonts/latin-modern v0.2.0/go.mod 
h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
-github.com/go-fonts/latin-modern v0.3.0/go.mod 
h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
-github.com/go-fonts/latin-modern v0.3.1 
h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM=
-github.com/go-fonts/latin-modern v0.3.1/go.mod 
h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
-github.com/go-fonts/liberation v0.1.1/go.mod 
h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
-github.com/go-fonts/liberation v0.2.0/go.mod 
h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
-github.com/go-fonts/liberation v0.3.0/go.mod 
h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
-github.com/go-fonts/liberation v0.3.1 
h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM=
-github.com/go-fonts/liberation v0.3.1/go.mod 
h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
-github.com/go-fonts/stix v0.1.0/go.mod 
h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod 
h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod 
h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod 
h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
-github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod 
h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
-github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 
h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
-github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod 
h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
-github.com/go-ole/go-ole v1.2.6/go.mod 
h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-pdf/fpdf v0.5.0/go.mod 
h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
-github.com/go-pdf/fpdf v0.6.0/go.mod 
h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
-github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ=
-github.com/go-pdf/fpdf v0.8.0/go.mod 
h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc=
-github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod 
h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
-github.com/go-text/typesetting v0.0.0-20230502123426-87572f5551cf/go.mod 
h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
-github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc/go.mod 
h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE=
-github.com/godbus/dbus/v5 v5.0.3/go.mod 
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/godbus/dbus/v5 v5.0.6/go.mod 
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 
h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod 
h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/google/go-cmp v0.5.8/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/jezek/xgb v1.0.0/go.mod 
h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-github.com/jung-kurt/gofpdf v1.0.0/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/kisielk/gotool v1.0.0/go.mod 
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/phpdave11/gofpdf v1.4.2/go.mod 
h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
-github.com/phpdave11/gofpdi v1.0.12/go.mod 
h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
-github.com/phpdave11/gofpdi v1.0.13/go.mod 
h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
-github.com/pkg/errors v0.8.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod 
h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/jedib0t/go-pretty/v6 v6.4.8 
h1:HiNzyMSEpsBaduKhmK+CwcpulEeBrTmxutz4oX/oWkg=
+github.com/jedib0t/go-pretty/v6 v6.4.8/go.mod 
h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
+github.com/mattn/go-runewidth v0.0.13 
h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
+github.com/mattn/go-runewidth v0.0.13/go.mod 
h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/montanaflynn/stats v0.7.1 
h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
+github.com/montanaflynn/stats v0.7.1/go.mod 
h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
+github.com/pkg/profile v1.6.0/go.mod 
h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod 
h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
-github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod 
h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
-github.com/stretchr/testify v1.2.2/go.mod 
h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/yuin/goldmark v1.2.1/go.mod 
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.13/go.mod 
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod 
h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/sirupsen/logrus v1.9.3 
h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod 
h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sourcegraph/conc v0.3.0 
h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod 
h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod 
h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.3.0/go.mod 
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.4/go.mod 
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 
h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/yuin/goldmark v1.3.5/go.mod 
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod 
h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod 
h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.1.0/go.mod 
h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod 
h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
-golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod 
h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
-golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod 
h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod 
h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea 
h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
-golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod 
h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
-golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod 
h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
-golang.org/x/exp/shiny v0.0.0-20230425010034-47ecfdc1ba53/go.mod 
h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
-golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod 
h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod 
h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
-golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
-golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
-golang.org/x/image v0.3.0/go.mod 
h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
-golang.org/x/image v0.5.0/go.mod 
h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
-golang.org/x/image v0.6.0/go.mod 
h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
-golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
-golang.org/x/image v0.7.0/go.mod 
h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod 
h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod 
h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod 
h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod 
h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod 
h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod 
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod 
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod 
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod 
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod 
h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.1.0/go.mod 
h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.2.0/go.mod 
h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
-golang.org/x/tools v0.6.0/go.mod 
h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.7.0/go.mod 
h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/tools v0.1.1/go.mod 
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod 
h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2/go.mod 
h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/gonum v0.9.3/go.mod 
h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
-gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=
-gonum.org/v1/gonum v0.13.0/go.mod 
h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod 
h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
-gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod 
h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
-gonum.org/v1/plot v0.10.1/go.mod 
h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
-gonum.org/v1/plot v0.13.0 h1:yb2Z/b8bY5h/xC4uix+ujJ+ixvPUvBmUOtM73CJzpsw=
-gonum.org/v1/plot v0.13.0/go.mod 
h1:mV4Bpu4PWTgN2CETURNF8hCMg7EtlZqJYCcmYo/t4Co=
-honnef.co/go/tools v0.1.3/go.mod 
h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
-rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/logger/logger.go b/logger/logger.go
index 6ad1b4b..76eb971 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -1,3 +1,20 @@
+// 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 logger
 
 import (
diff --git a/network/network.go b/network/network.go
new file mode 100644
index 0000000..1c238e7
--- /dev/null
+++ b/network/network.go
@@ -0,0 +1,72 @@
+// 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 network
+
+import (
+       "csbench/config"
+       "csbench/utils"
+       "log"
+       "strconv"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+)
+
+func ListNetworks(cs *cloudstack.CloudStackClient, domainId string) 
([]*cloudstack.Network, error) {
+       p := cs.Network.NewListNetworksParams()
+       p.SetDomainid(domainId)
+       p.SetListall(true)
+       p.SetZoneid(config.ZoneId)
+       p.SetPage(1)
+       p.SetPagesize(config.PageSize)
+       resp, err := cs.Network.ListNetworks(p)
+       if err != nil {
+               log.Printf("Failed to list networks due to %v", err)
+               return nil, err
+       }
+       return resp.Networks, nil
+}
+
+func CreateNetwork(cs *cloudstack.CloudStackClient, domainId string, count 
int) (*cloudstack.CreateNetworkResponse, error) {
+       netName := "Network-" + utils.RandomString(10)
+       p := cs.Network.NewCreateNetworkParams(netName, 
config.NetworkOfferingId, config.ZoneId)
+       p.SetDomainid(domainId)
+       p.SetAcltype("Domain")
+       p.SetGateway("10.10.0.1")
+       p.SetNetmask("255.255.252.0")
+       p.SetDisplaytext(netName)
+       p.SetStartip("10.10.0.2")
+       p.SetEndip("10.10.3.255")
+       p.SetVlan(strconv.Itoa(80 + count))
+
+       resp, err := cs.Network.CreateNetwork(p)
+       if err != nil {
+               log.Printf("Failed to create network due to: %v", err)
+               return nil, err
+       }
+       return resp, nil
+}
+
+func DeleteNetwork(cs *cloudstack.CloudStackClient, networkId string) (bool, 
error) {
+       deleteParams := cs.Network.NewDeleteNetworkParams(networkId)
+       delResp, err := cs.Network.DeleteNetwork(deleteParams)
+       if err != nil {
+               log.Printf("Failed to delete network with id %s due to %v", 
networkId, err)
+               return false, err
+       }
+       return delResp.Success, nil
+}
diff --git a/utils/utils.go b/utils/utils.go
new file mode 100644
index 0000000..8b6092c
--- /dev/null
+++ b/utils/utils.go
@@ -0,0 +1,37 @@
+// 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 utils
+
+import (
+       "math/rand"
+       "time"
+)
+
+func init() {
+       rand.Seed(time.Now().UnixNano())
+}
+
+var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+func RandomString(n int) string {
+       b := make([]rune, n)
+       for i := range b {
+               b[i] = letters[rand.Intn(len(letters))]
+       }
+       return string(b)
+}
diff --git a/vm/vm.go b/vm/vm.go
new file mode 100644
index 0000000..82433d3
--- /dev/null
+++ b/vm/vm.go
@@ -0,0 +1,83 @@
+// 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 vm
+
+import (
+       "csbench/config"
+       "csbench/utils"
+       "encoding/json"
+       "fmt"
+       "log"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+)
+
+func ListVMs(cs *cloudstack.CloudStackClient, domainId string) 
([]*cloudstack.VirtualMachine, error) {
+       p := cs.VirtualMachine.NewListVirtualMachinesParams()
+       p.SetDomainid(domainId)
+       p.SetPage(1)
+       p.SetPagesize(config.PageSize)
+       resp, err := cs.VirtualMachine.ListVirtualMachines(p)
+       if err != nil {
+               log.Printf("Failed to list vm due to %v", err)
+               return nil, err
+       }
+       return resp.VirtualMachines, nil
+}
+
+func DeployVm(cs *cloudstack.CloudStackClient, domainId string, networkId 
string, account string) (*cloudstack.DeployVirtualMachineResponse, error) {
+       vmName := "Vm-" + utils.RandomString(10)
+       p := 
cs.VirtualMachine.NewDeployVirtualMachineParams(config.ServiceOfferingId, 
config.TemplateId, vmName)
+       p.SetDomainid(domainId)
+       p.SetZoneid(config.ZoneId)
+       p.SetNetworkids([]string{networkId})
+       p.SetName(vmName)
+       p.SetAccount(account)
+
+       resp, err := cs.VirtualMachine.DeployVirtualMachine(p)
+       if err != nil {
+               log.Printf("Failed to deploy vm due to: %v", err)
+               return nil, err
+       }
+       return resp, nil
+}
+
+func DestroyVm(cs *cloudstack.CloudStackClient, vmId string) {
+
+       deleteParams := cs.VirtualMachine.NewDestroyVirtualMachineParams(vmId)
+       deleteParams.SetExpunge(true)
+       delResp, err := cs.VirtualMachine.DestroyVirtualMachine(deleteParams)
+       if err != nil {
+               log.Printf("Failed to destroy Vm with Id %s due to %v", vmId, 
err)
+       }
+       r, err := parseResponse(delResp)
+       if err != nil {
+               log.Printf("Failed to parse destroy vm response due to %v", err)
+               return
+       }
+       fmt.Printf("Destroy Vm response: %v\n\n", string(r))
+}
+
+func parseResponse(resp interface{}) ([]byte, error) {
+       b, err := json.MarshalIndent(resp, "", "    ")
+       if err != nil {
+               log.Printf("%v", err)
+               return nil, err
+       }
+       return b, nil
+}
diff --git a/volume/volume.go b/volume/volume.go
new file mode 100644
index 0000000..b3ab0c6
--- /dev/null
+++ b/volume/volume.go
@@ -0,0 +1,79 @@
+// 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 volume
+
+import (
+       "csbench/config"
+       "log"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+
+       "csbench/utils"
+)
+
+func CreateVolume(cs *cloudstack.CloudStackClient, domainId string, account 
string) (*cloudstack.CreateVolumeResponse, error) {
+       volName := "Volume-" + utils.RandomString(10)
+       p := cs.Volume.NewCreateVolumeParams()
+       p.SetDomainid(domainId)
+       p.SetName(volName)
+       p.SetZoneid(config.ZoneId)
+       p.SetDiskofferingid(config.DiskOfferingId)
+       p.SetAccount(account)
+       resp, err := cs.Volume.CreateVolume(p)
+
+       if err != nil {
+               log.Printf("Failed to create volume due to: %v", err)
+               return nil, err
+       }
+       return resp, nil
+}
+
+func DestroyVolume(cs *cloudstack.CloudStackClient, volumeId string) 
(*cloudstack.DestroyVolumeResponse, error) {
+
+       p := cs.Volume.NewDestroyVolumeParams(volumeId)
+       p.SetExpunge(true)
+       resp, err := cs.Volume.DestroyVolume(p)
+       if err != nil {
+               log.Printf("Failed to destroy volume with id  %s due to %v", 
volumeId, err)
+               return nil, err
+       }
+       return resp, nil
+}
+
+func AttachVolume(cs *cloudstack.CloudStackClient, volumeId string, vmId 
string) (*cloudstack.AttachVolumeResponse, error) {
+       p := cs.Volume.NewAttachVolumeParams(volumeId, vmId)
+       resp, err := cs.Volume.AttachVolume(p)
+
+       if err != nil {
+               log.Printf("Failed to attach volume due to: %v", err)
+               return nil, err
+       }
+       return resp, nil
+}
+
+func DetachVolume(cs *cloudstack.CloudStackClient, volumeId string) 
(*cloudstack.DetachVolumeResponse, error) {
+       p := cs.Volume.NewDetachVolumeParams()
+       p.SetId(volumeId)
+       resp, err := cs.Volume.DetachVolume(p)
+
+       if err != nil {
+               log.Printf("Failed to detach volume due to: %v", err)
+               return nil, err
+       }
+       return resp, nil
+}


Reply via email to