-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP! Refactoring to make this easier to test... maybe 😉
- Loading branch information
Showing
14 changed files
with
515 additions
and
198 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package fio | ||
|
||
type Command interface { | ||
Run() (Result, error) | ||
} | ||
|
||
type ConcreteCommand struct { | ||
args []string | ||
} | ||
|
||
func NewCommand(args ...string) Command { | ||
return &ConcreteCommand{ | ||
args: args, | ||
} | ||
} | ||
|
||
func (*ConcreteCommand) Run() (Result, error) { | ||
return Result{}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package fio | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/conjurinc/conjur-preflight/pkg/log" | ||
"github.com/conjurinc/conjur-preflight/pkg/maybe" | ||
) | ||
|
||
const fioExecutable = "fio" | ||
|
||
type commandWrapper struct { | ||
command *exec.Cmd | ||
stdout bytes.Buffer | ||
stderr bytes.Buffer | ||
} | ||
|
||
func newCommandWrapper(name string, args ...string) *commandWrapper { | ||
wrapper := commandWrapper{} | ||
|
||
// Instantiate the command | ||
command := exec.Command(name, args...) | ||
|
||
// Bind the stdout and stderr | ||
command.Stdout = &wrapper.stdout | ||
command.Stderr = &wrapper.stderr | ||
|
||
// Wrap the command | ||
wrapper.command = command | ||
|
||
return &wrapper | ||
} | ||
|
||
func (wrapper *commandWrapper) Run() ([]byte, error) { | ||
err := wrapper.command.Run() | ||
|
||
if err != nil { | ||
log.Error("Failed to run command: %s", err) | ||
return nil, newErrorFromStderr(&wrapper.stderr) | ||
} | ||
|
||
return wrapper.stdout.Bytes(), nil | ||
} | ||
|
||
func Exec( | ||
jobName string, | ||
args []string, | ||
) (*Result, error) { | ||
// Create the directory for running the fio test within | ||
cleanup, err := usingTestDirectory(jobName) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to create test directory: %s", err) | ||
} | ||
defer cleanup() | ||
|
||
// Run the fio command | ||
maybeOutput := maybeJobOutput(args) | ||
maybeWriteResultToFile(maybeOutput, jobName) | ||
return maybeResultFromJson(maybeOutput).ValueE() | ||
} | ||
|
||
func maybeJobOutput(args []string) maybe.Maybe[[]byte] { | ||
return maybe.Bind( | ||
maybe.Bind( | ||
maybe.Result(exec.LookPath(fioExecutable)), | ||
func(fioPath string) (*commandWrapper, error) { | ||
return newCommandWrapper(fioPath, args...), nil | ||
}, | ||
), | ||
func(commandWrapper *commandWrapper) ([]byte, error) { | ||
return commandWrapper.Run() | ||
}, | ||
) | ||
} | ||
|
||
func usingTestDirectory(jobName string) (func(), error) { | ||
err := os.MkdirAll(jobName, os.ModePerm) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return func() { | ||
err := os.RemoveAll(jobName) | ||
if err != nil { | ||
log.Warn("Unable to clean up test directory for job: %s", jobName) | ||
} | ||
}, nil | ||
} | ||
|
||
func maybeWriteResultToFile(maybeBuffer maybe.Maybe[[]byte], jobName string) { | ||
// Attempt to write the full fio result to a file | ||
maybe.BindVoid( | ||
maybeBuffer, | ||
func(resultBytes []byte) error { | ||
return writeResultToFile(resultBytes, jobName) | ||
}, | ||
) | ||
} | ||
|
||
func writeResultToFile(buffer []byte, jobName string) error { | ||
|
||
outputFilename := fmt.Sprintf("%s.json", jobName) | ||
|
||
err := os.WriteFile(outputFilename, buffer, 0644) | ||
|
||
if err != nil { | ||
log.Warn("Failed to write result file for %s: %s", jobName, err) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func maybeResultFromJson(maybeBuffer maybe.Maybe[[]byte]) maybe.Maybe[*Result] { | ||
return maybe.Bind( | ||
maybeBuffer, | ||
func(resultBytes []byte) (*Result, error) { | ||
return newResultFromJson(resultBytes) | ||
}, | ||
) | ||
} | ||
|
||
func newResultFromJson(buffer []byte) (*Result, error) { | ||
result := Result{} | ||
|
||
err := json.Unmarshal(buffer, &result) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &result, nil | ||
} | ||
|
||
func newErrorFromStderr(buffer *bytes.Buffer) error { | ||
str := buffer.String() | ||
|
||
// Trim any extra space at the start or end (e.g. trailing newline) | ||
str = strings.TrimSpace(str) | ||
|
||
// Convert multi-line response into comma separated | ||
str = strings.ReplaceAll(str, "\n", ", ") | ||
|
||
return errors.New(str) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package fio | ||
|
||
// Result is a data structure that represents the JSON | ||
// output returned from running `fio`. | ||
type Result struct { | ||
Version string `json:"fio version"` | ||
Jobs []JobResult `json:"jobs"` | ||
} | ||
|
||
// JobResult represents the results from an individual fio job. A FioResult | ||
// may include multiple job results. | ||
type JobResult struct { | ||
Sync JobModeResult `json:"sync"` | ||
Read JobModeResult `json:"read"` | ||
Write JobModeResult `json:"write"` | ||
} | ||
|
||
// JobModeResult represents the measurements for a given test mode | ||
// (e.g. read, write). Not all modes provide all values. The populated | ||
// values depend on the fio job parameters. | ||
type JobModeResult struct { | ||
Iops float64 `json:"iops"` | ||
IopsMin int64 `json:"iops_min"` | ||
IopsMax int64 `json:"iops_max"` | ||
IopsMean float64 `json:"iops_mean"` | ||
IopsStddev float64 `json:"iops_stddev"` | ||
|
||
LatNs ResultStats `json:"lat_ns"` | ||
} | ||
|
||
// ResultStats represents the statistical measurements provided by fio. | ||
type ResultStats struct { | ||
Min int64 `json:"min"` | ||
Max int64 `json:"max"` | ||
Mean float64 `json:"mean"` | ||
StdDev float64 `json:"stddev"` | ||
N int64 `json:"N"` | ||
Percentile Percentile `json:"percentile"` | ||
} | ||
|
||
// Percentile provides a simple interface to return particular statistical | ||
// percentiles from the fio stats results. | ||
type Percentile struct { | ||
NinetyNinth int64 `json:"99.000000"` | ||
} |
Oops, something went wrong.