diff --git a/.dockerignore b/.dockerignore index 1a1adc8..4330b62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,5 @@ !/rally !/go.mod !/go.sum +!/delete-tasks.sh !/rally_exporter.go diff --git a/Dockerfile b/Dockerfile index 1e3d86b..16a9a6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,13 @@ -FROM golang:1.13-stretch AS builder +FROM golang:1.20-bullseye AS builder + WORKDIR /go/src/app COPY . . RUN go build -FROM xrally/xrally-openstack:1.7.0 +# FROM xrally/xrally-openstack:2.2.0 +FROM xrally/xrally-openstack:latest + +COPY delete-tasks.sh /delete-tasks.sh +RUN chmod +x /delete-tasks.sh COPY --from=builder /go/src/app/rally-exporter /rally-exporter ENTRYPOINT ["/rally-exporter"] diff --git a/delete-tasks.sh b/delete-tasks.sh new file mode 100644 index 0000000..6a0aa06 --- /dev/null +++ b/delete-tasks.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +COUNT=$(($1 + 1)) + +TASKID=$(rally task list | tail -$COUNT | head -1 | awk '{print $2}') + +rally task delete --uuid $TASKID diff --git a/rally/deployment.go b/rally/deployment.go deleted file mode 100644 index cf09a47..0000000 --- a/rally/deployment.go +++ /dev/null @@ -1,20 +0,0 @@ -package rally - -type OpenStackUser struct { - Username string `json:"username"` - Password string `json:"password"` - UserDomainName string `json:"user_domain_name"` - ProjectName string `json:"project_name"` - ProjectDomainName string `json:"project_domain_name"` -} - -type OpenStackDeployment struct { - AuthURL string `json:"auth_url"` - RegionName string `json:"region_name"` - EndpointType string `json:"endpoint_type"` - Users []OpenStackUser `json:"users"` -} - -type Deployment struct { - OpenStackDeployment `json:"openstack"` -} diff --git a/rally/runner.go b/rally/runner.go index e43e722..e92af13 100644 --- a/rally/runner.go +++ b/rally/runner.go @@ -1,14 +1,11 @@ package rally import ( - "encoding/json" "fmt" - "io/ioutil" - "os" "os/exec" + "strconv" "time" - "github.com/gophercloud/utils/openstack/clientconfig" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/prometheus/client_golang/prometheus" @@ -25,18 +22,21 @@ type PeriodicRunner struct { prometheus.Collector CloudName string - TaskFile string + ExecTime int + TaskCount int TaskDuration *prometheus.Desc TaskSLADesc *prometheus.Desc TaskTime *prometheus.Desc } -// NewPeriodicRunner creates a rally runner which executes every 5 minutes -func NewPeriodicRunner(cloudName string, taskFile string) *PeriodicRunner { +// NewPeriodicRunner creates a rally runner which executes every +// `execTime` minutes after last run. +func NewPeriodicRunner(cloudName string, execTime int, taskCount int) *PeriodicRunner { return &PeriodicRunner{ CloudName: cloudName, - TaskFile: taskFile, + ExecTime: execTime, + TaskCount: taskCount, TaskDuration: prometheus.NewDesc( "rally_task_duration", "Rally task duration", @@ -55,13 +55,29 @@ func NewPeriodicRunner(cloudName string, taskFile string) *PeriodicRunner { } } -// Run starts the PeriodicRunner which runs Rally every 5 minutes. +// Run starts the PeriodicRunner which runs Rally every +// `execTime` minutes after last run. func (runner *PeriodicRunner) Run() { runner.createDeployment() for { - log.Info("Starting Rally run...") - cmd := exec.Command("rally", "task", "start", runner.TaskFile) + currentTime := time.Now() + + count := strconv.Itoa(runner.TaskCount) + + log.Info("Deleting last Rally task") + // This is horrible. Should be rewritten in native golang. + precmd := exec.Command("/delete-tasks.sh", count) + preoutput, err := precmd.CombinedOutput() + if err != nil { + log.Error("Failed to delete last Rally task:") + } else { + log.Info("Deleted last Rally task:") + } + fmt.Println(string(preoutput)) + + log.Info("Starting Rally run at ", currentTime.String()) + cmd := exec.Command("rally", "task", "start", "/conf/tasks.yml", "--task-args-file", "/conf/arguments.yml") output, err := cmd.CombinedOutput() if err != nil { log.Error("Failed test, output below:") @@ -70,50 +86,23 @@ func (runner *PeriodicRunner) Run() { } fmt.Println(string(output)) - time.Sleep(5 * time.Minute) + minutes := runner.ExecTime + time.Sleep(time.Duration(minutes) * time.Minute) } } func (runner *PeriodicRunner) createDeployment() { - opts := &clientconfig.ClientOpts{Cloud: runner.CloudName} - cloud, err := clientconfig.GetCloudFromYAML(opts) - if err != nil { - log.Fatal(err) - } - deployment := &Deployment{ - OpenStackDeployment: OpenStackDeployment{ - AuthURL: cloud.AuthInfo.AuthURL, - RegionName: cloud.RegionName, - EndpointType: cloud.EndpointType, - Users: []OpenStackUser{ - OpenStackUser{ - Username: cloud.AuthInfo.Username, - Password: cloud.AuthInfo.Password, - UserDomainName: cloud.AuthInfo.UserDomainName, - ProjectName: cloud.AuthInfo.ProjectName, - ProjectDomainName: cloud.AuthInfo.ProjectDomainName, - }, - }, - }, - } - - b, err := json.Marshal(deployment) - if err != nil { - log.Fatal(err) - } - - file, err := ioutil.TempFile("/tmp", "deployment") + precmd := exec.Command("rally", "deployment", "destroy", runner.CloudName) + preoutput, err := precmd.CombinedOutput() if err != nil { - log.Fatal(err) - } - defer os.Remove(file.Name()) - _, err = file.Write(b) - if err != nil { - log.Fatal(err) + log.Warn("There is no Rally deployment named '", runner.CloudName, "' to destroy.") + } else { + log.Info("Successfully destroyed Rally deployment named '", runner.CloudName, "'.") } + fmt.Println(string(preoutput)) - cmd := exec.Command("rally", "deployment", "create", "--filename", file.Name(), "--name", runner.CloudName) + cmd := exec.Command("rally", "deployment", "create", "--filename", "/conf/deployment.yml", "--name", runner.CloudName) output, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(output)) @@ -137,7 +126,7 @@ func getLatestTask(db *gorm.DB) (*models.Task, error) { // Collect grabs all the data from the Rally database func (runner *PeriodicRunner) Collect(ch chan<- prometheus.Metric) { - db, err := gorm.Open("sqlite3", "/home/rally/data/rally.db") + db, err := gorm.Open("sqlite3", "/home/rally/.rally/rally.db") if err != nil { log.Fatal(err) } diff --git a/rally_exporter.go b/rally_exporter.go index 3304ac2..bd28ff0 100644 --- a/rally_exporter.go +++ b/rally_exporter.go @@ -22,31 +22,42 @@ func main() { "web.telemetry-path", "Path under which to expose metrics.", ).Default("/metrics").String() - cloud = kingpin.Arg( - "cloud", - "Name of the cloud from clouds.yaml", - ).Required().String() - task = kingpin.Arg( - "file", - "Name of the task file", + deployment = kingpin.Flag( + "deployment-name", + "Name of the Rally deployment", ).Required().String() + exectime = kingpin.Flag( + "execution-time", + "Wait X minutes before next run. Default: 5", + ).Default("5").Int() + taskcount = kingpin.Flag( + "task-history", + "Number of tasks to keep in history. Default: 10", + ).Default("10").Int() ) kingpin.Version(version.Print("rally-exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() - runner := rally.NewPeriodicRunner(*cloud, *task) - prometheus.MustRegister(runner) + // Get rid of any additional metrics + // we have to expose our metrics with a custom registry + registry := prometheus.NewRegistry() + + runner := rally.NewPeriodicRunner(*deployment, *exectime, *taskcount) + + registry.MustRegister(runner) go runner.Run() - http.Handle("/metrics", promhttp.Handler()) + handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) + http.Handle("/metrics", handler) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(` - Rally Exporter + OpenStack Rally Exporter -

Rally Exporter

+

OpenStack Rally Exporter

Metrics

`))