Skip to content

Commit

Permalink
Add IOPs checks to preflight
Browse files Browse the repository at this point in the history
These are based on the `fio` tool, which must
be installed separately as a prereq for these
checks.

Further, the IOPs checks require `libaio` to be
installed separately, and is only available on
linux.
  • Loading branch information
micahlee committed Feb 9, 2023
1 parent 0c2791e commit 25713b3
Show file tree
Hide file tree
Showing 18 changed files with 1,191 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- A new CLI flag `--debug` causes `conjur-preflight` to log more verbose
information about the execution of the application and its checks.
[conjurinc/conjur-preflight#19](https://github.com/conjurinc/conjur-preflight/pull/19)
- `conjur-preflight` now includes disk related checks for read, write, and sync
latency, as well as read and write operations per second (IOPs). These require
`fio` and `libaio` to be present as a prerequesite for these checks.
[conjurinc/conjur-preflight#19](https://github.com/conjurinc/conjur-preflight/pull/19)

### Fixed
- Previously, the application version was not properly embedded in the final
Expand Down
43 changes: 43 additions & 0 deletions ci/integration/Dockerfile.rhel.all-dependencies
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
FROM redhat/ubi8

RUN yum install -y podman

# Install FIO from source, since it's not available from the
# UBI package repositories unless also running on a registered
# RHEL host.
RUN yum install -y \
wget \
gcc \
make && \
#
# Build libaio for iops tests
#
wget \
-O libaio.tar.gz \
https://pagure.io/libaio/archive/libaio-0.3.111/libaio-libaio-0.3.111.tar.gz && \
mkdir libaio && \
tar \
--strip-components=1 \
--directory=libaio \
-xvf libaio.tar.gz && \
cd libaio && \
make install && \
ldconfig && \
cd .. && \
rm libaio.tar.gz && \
#
# Build fio
#
wget \
-O fio.tar.gz \
https://github.com/axboe/fio/archive/refs/tags/fio-3.33.tar.gz && \
mkdir fio && \
tar \
--strip-components=1 \
--directory=fio \
-xvf fio.tar.gz && \
cd fio && \
./configure && \
make && \
make install && \
cd .. && \
rm -rf fio.tar.gz && \
yum remove -y wget make gcc

3 changes: 3 additions & 0 deletions ci/integration/Dockerfile.ubuntu.all-dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ RUN apt-get update && \
docker-ce \
docker-ce-cli \
containerd.io

RUN apt-get update && \
apt-get install -y fio
40 changes: 40 additions & 0 deletions dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,43 @@ RUN yum install -y \
git \
golang \
make

# Install FIO from source, since it's not available from the
# UBI package repositories unless also running on a registered
# RHEL host.
RUN yum install -y \
wget && \
#
# Build libaio for iops tests
#
wget \
-O libaio.tar.gz \
https://pagure.io/libaio/archive/libaio-0.3.111/libaio-libaio-0.3.111.tar.gz && \
mkdir libaio && \
tar \
--strip-components=1 \
--directory=libaio \
-xvf libaio.tar.gz && \
cd libaio && \
make install && \
ldconfig && \
cd .. && \
rm libaio.tar.gz && \
#
# Build fio
#
wget \
-O fio.tar.gz \
https://github.com/axboe/fio/archive/refs/tags/fio-3.33.tar.gz && \
mkdir fio && \
tar \
--strip-components=1 \
--directory=fio \
-xvf fio.tar.gz && \
cd fio && \
./configure && \
make && \
make install && \
cd .. && \
rm -rf fio.tar.gz && \
yum remove -y wget
42 changes: 42 additions & 0 deletions pkg/checks/disk/fio/command_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package fio

import (
"bytes"
"os/exec"
)

type command interface {
Run() ([]byte, error)
}

type commandWrapper struct {
command *exec.Cmd
stdout bytes.Buffer
stderr bytes.Buffer
}

func newCommandWrapper(name string, args ...string) command {
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 {
return nil, err
}

return wrapper.stdout.Bytes(), nil
}
34 changes: 34 additions & 0 deletions pkg/checks/disk/fio/command_wrapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package fio

import (
"bytes"
"testing"
)

func TestNewCommandWrapper(t *testing.T) {
cmd := newCommandWrapper("echo", "hello world")
if cmd == nil {
t.Error("Expected CommandWrapper, got nil")
}
}

func TestCommandWrapper_Run(t *testing.T) {
cmd := newCommandWrapper("echo", "hello world")

output, err := cmd.Run()
if err != nil {
t.Errorf("Expected nil error, got %s", err)
}
if !bytes.Equal(output, []byte("hello world\n")) {
t.Errorf("Expected 'hello world\\n', got %s", string(output))
}
}

func TestCommandWrapper_Run_Error(t *testing.T) {
cmd := newCommandWrapper("invalid_command", "hello world")

_, err := cmd.Run()
if err == nil {
t.Error("Expected error, got nil")
}
}
117 changes: 117 additions & 0 deletions pkg/checks/disk/fio/job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package fio

import (
"encoding/json"
"fmt"
"os"
"os/exec"

"github.com/conjurinc/conjur-preflight/pkg/log"
)

const fioExecutable = "fio"

// Executable represents an operation that can produce an fio result and
// emit raw output data.
type Executable interface {
Exec() (*Result, error)
OnRawOutput(func([]byte))
}

// Job is a concrete Executable for executing fio jobs
type Job struct {
// Required fields:
// ----------------------

Name string
Args []string

// Optional fields:
// ----------------------

// OnRawOutput may be configured to receive the full standard output
// of the fio command. For example, to write the full output to a file.
rawOutputCallback func([]byte)

// Injected dependencies:
// ----------------------

// Lookup function to return the full path for a command name
execLookPath func(string) (string, error)
newCommandWrapper func(string, ...string) command
jsonUnmarshal func([]byte, any) error
}

// NewJob constructs a Job with the default dependencies
func NewJob(name string, args []string) Executable {
return &Job{
// Set required fields
Name: name,
Args: args,

// Construct default dependencies
execLookPath: exec.LookPath,
newCommandWrapper: newCommandWrapper,
jsonUnmarshal: json.Unmarshal,
}
}

// Exec runs the given fio job in a temporary directory
func (job *Job) Exec() (*Result, error) {
// Create the directory for running the fio test. We have this return the
// cleanup method as well to simplify deferring this task when the function
// finishes.
cleanup, err := usingTestDirectory(job.Name)
if err != nil {
return nil, fmt.Errorf("unable to create test directory: %s", err)
}
defer cleanup()

// Lookup full path for 'fio'
fioPath, err := job.execLookPath(fioExecutable)
if err != nil {
return nil, fmt.Errorf("unable to find 'fio' path: %s", err)
}

// Run 'fio' command
commandWrapper := job.newCommandWrapper(fioPath, job.Args...)
output, err := commandWrapper.Run()

if err != nil {
return nil, fmt.Errorf("unable to execute 'fio' job: %s", err)
}

// If there is a configured result listener, notify it of the result output
if job.rawOutputCallback != nil {
job.rawOutputCallback(output)
}

// Parse the result JSON
jsonResult := Result{}
err = job.jsonUnmarshal(output, &jsonResult)
if err != nil {
return nil, fmt.Errorf("unable to parse 'fio' output: %s", err)
}

return &jsonResult, nil
}

// OnRawOutput sets the callback to receive standard output from the fio
// command.
func (job *Job) OnRawOutput(callback func([]byte)) {
job.rawOutputCallback = callback
}

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
}
Loading

0 comments on commit 25713b3

Please sign in to comment.