Bootstrap Ephemeral - Command methods
This patch set includes the methods supporting the bootstrap container feature, which will be used by the BootConfiguration executor, as part of "phase run" implementation. The methods under command.go file are responsible for creating the bootstrap container, monitor its state, etc. Change-Id: I3f6b1a0bb7c7be7ac29487d09ae2ccd5e9f4bd87
This commit is contained in:
parent
f4e532e91a
commit
fb49020d00
246
pkg/bootstrap/ephemeral/command.go
Normal file
246
pkg/bootstrap/ephemeral/command.go
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
Licensed 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
|
||||
|
||||
https://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 ephemeral
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/container"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// BootCmdCreate is the string for the command "create" Ephemeral cluster
|
||||
BootCmdCreate = "create"
|
||||
// BootCmdDelete is the string for the command "delete" Ephemeral cluster
|
||||
BootCmdDelete = "delete"
|
||||
// BootCmdHelp is the string for the command "help" for the Ephemeral cluster
|
||||
BootCmdHelp = "help"
|
||||
// BootVolumeSeparator is the string Container volume mount
|
||||
BootVolumeSeparator = ":"
|
||||
// BootNullString represents an empty string
|
||||
BootNullString = ""
|
||||
// BootHelpFilename is the help filename
|
||||
BootHelpFilename = "help.txt"
|
||||
// Bootstrap Environment Variables
|
||||
envBootstrapCommand = "BOOTSTRAP_COMMAND"
|
||||
envBootstrapConfig = "BOOTSTRAP_CONFIG"
|
||||
envBootstrapVolume = "BOOTSTRAP_VOLUME"
|
||||
)
|
||||
|
||||
var exitCodeMap = map[int]string{
|
||||
1: ContainerLoadEphemeralConfigError,
|
||||
2: ContainerValidationEphemeralConfigError,
|
||||
3: ContainerSetEnvVarsError,
|
||||
4: ContainerUnknownCommandError,
|
||||
5: ContainerCreationEphemeralFailedError,
|
||||
6: ContainerDeletionEphemeralFailedError,
|
||||
7: ContainerHelpCommandFailedError,
|
||||
8: ContainerUnknownError,
|
||||
}
|
||||
|
||||
// BootstrapContainerOptions structure used by the executor
|
||||
type BootstrapContainerOptions struct {
|
||||
Container container.Container
|
||||
Cfg *v1alpha1.BootConfiguration
|
||||
Sleep func(d time.Duration)
|
||||
|
||||
// optional fields for verbose output
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// VerifyInputs verify if all input data to the container is correct
|
||||
func (options *BootstrapContainerOptions) VerifyInputs() error {
|
||||
if options.Cfg.BootstrapContainer.Volume == "" {
|
||||
return ErrInvalidInput{
|
||||
What: MissingVolumeError,
|
||||
}
|
||||
}
|
||||
|
||||
if options.Cfg.BootstrapContainer.Image == "" {
|
||||
return ErrInvalidInput{
|
||||
What: MissingContainerImageError,
|
||||
}
|
||||
}
|
||||
|
||||
if options.Cfg.BootstrapContainer.ContainerRuntime == "" {
|
||||
return ErrInvalidInput{
|
||||
What: MissingContainerRuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
if options.Cfg.EphemeralCluster.ConfigFilename == "" {
|
||||
return ErrInvalidInput{
|
||||
What: MissingConfigError,
|
||||
}
|
||||
}
|
||||
|
||||
vols := strings.Split(options.Cfg.BootstrapContainer.Volume, ":")
|
||||
switch {
|
||||
case len(vols) == 1:
|
||||
options.Cfg.BootstrapContainer.Volume = fmt.Sprintf("%s:%s", vols[0], vols[0])
|
||||
case len(vols) > 2:
|
||||
return ErrVolumeMalFormed{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContainerStatus returns the Bootstrap Container state
|
||||
func (options *BootstrapContainerOptions) GetContainerStatus() (container.Status, error) {
|
||||
// Check status of the container, e.g., "running"
|
||||
state, err := options.Container.InspectContainer()
|
||||
if err != nil {
|
||||
return BootNullString, err
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
exitCode = state.ExitCode
|
||||
if exitCode > 0 {
|
||||
reader, err := options.Container.GetContainerLogs()
|
||||
if err != nil {
|
||||
log.Printf("Error while trying to retrieve the container logs")
|
||||
return BootNullString, err
|
||||
}
|
||||
|
||||
containerError := ErrBootstrapContainerRun{}
|
||||
containerError.ExitCode = exitCode
|
||||
containerError.ErrMsg = exitCodeMap[exitCode]
|
||||
|
||||
if reader != nil {
|
||||
logs := new(bytes.Buffer)
|
||||
_, err = logs.ReadFrom(reader)
|
||||
if err != nil {
|
||||
return BootNullString, err
|
||||
}
|
||||
reader.Close()
|
||||
containerError.StdErr = logs.String()
|
||||
}
|
||||
return state.Status, containerError
|
||||
}
|
||||
|
||||
return state.Status, nil
|
||||
}
|
||||
|
||||
// WaitUntilContainerExitsOrTimesout waits for the container to exit or time out
|
||||
func (options *BootstrapContainerOptions) WaitUntilContainerExitsOrTimesout(
|
||||
maxRetries int,
|
||||
configFilename string,
|
||||
bootstrapCommand string) error {
|
||||
// Give 2 seconds before checking if container is still running
|
||||
// This period should be enough to detect some initial errors thrown by the container
|
||||
options.Sleep(2 * time.Second)
|
||||
|
||||
// Wait until container finished executing bootstrap of ephemeral cluster
|
||||
status, err := options.GetContainerStatus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == container.ExitedContainerStatus {
|
||||
// bootstrap container command execution completed
|
||||
return nil
|
||||
}
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
log.Printf("Waiting for bootstrap container using %s config file to %s Ephemeral cluster (%d/%d)",
|
||||
configFilename, bootstrapCommand, attempt, maxRetries)
|
||||
// Wait for 15 seconds and check again bootstrap container state
|
||||
options.Sleep(15 * time.Second)
|
||||
status, err = options.GetContainerStatus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status == container.ExitedContainerStatus {
|
||||
// bootstrap container command execution completed
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNumberOfRetriesExceeded{}
|
||||
}
|
||||
|
||||
// CreateBootstrapContainer creates a Bootstrap Container
|
||||
func (options *BootstrapContainerOptions) CreateBootstrapContainer() error {
|
||||
containerVolMount := options.Cfg.BootstrapContainer.Volume
|
||||
vols := []string{containerVolMount}
|
||||
log.Printf("Running default container command. Mounted dir: %s", vols)
|
||||
|
||||
bootstrapCommand := options.Cfg.EphemeralCluster.BootstrapCommand
|
||||
configFilename := options.Cfg.EphemeralCluster.ConfigFilename
|
||||
envVars := []string{
|
||||
fmt.Sprintf("%s=%s", envBootstrapCommand, bootstrapCommand),
|
||||
fmt.Sprintf("%s=%s", envBootstrapConfig, configFilename),
|
||||
fmt.Sprintf("%s=%s", envBootstrapVolume, containerVolMount),
|
||||
}
|
||||
|
||||
err := options.Container.RunCommand([]string{}, nil, vols, envVars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxRetries := 50
|
||||
switch bootstrapCommand {
|
||||
case BootCmdCreate:
|
||||
// Wait until container finished executing bootstrap of ephemeral cluster
|
||||
err = options.WaitUntilContainerExitsOrTimesout(maxRetries, configFilename, bootstrapCommand)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create Ephemeral cluster using %s config file", configFilename)
|
||||
return err
|
||||
}
|
||||
log.Printf("Ephemeral cluster created successfully using %s config file", configFilename)
|
||||
case BootCmdDelete:
|
||||
// Wait until container finished executing bootstrap of ephemeral cluster
|
||||
err = options.WaitUntilContainerExitsOrTimesout(maxRetries, configFilename, bootstrapCommand)
|
||||
if err != nil {
|
||||
log.Printf("Failed to delete Ephemeral cluster using %s config file", configFilename)
|
||||
return err
|
||||
}
|
||||
log.Printf("Ephemeral cluster deleted successfully using %s config file", configFilename)
|
||||
case BootCmdHelp:
|
||||
// Display Ephemeral Config file format for help
|
||||
sepPos := strings.Index(containerVolMount, BootVolumeSeparator)
|
||||
helpPath := filepath.Join(containerVolMount[:sepPos], BootHelpFilename)
|
||||
|
||||
// Display help.txt on stdout
|
||||
data, err := ioutil.ReadFile(helpPath)
|
||||
if err != nil {
|
||||
log.Printf("File reading %s error: %s", helpPath, err)
|
||||
return err
|
||||
}
|
||||
// Printing the help.txt content to stdout
|
||||
fmt.Println(string(data))
|
||||
|
||||
// Delete help.txt file
|
||||
err = os.Remove(helpPath)
|
||||
if err != nil {
|
||||
log.Printf("Could not delete %s", helpPath)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrInvalidBootstrapCommand{}
|
||||
}
|
||||
|
||||
log.Printf("Ephemeral cluster %s command completed successfully.", bootstrapCommand)
|
||||
if !options.Debug {
|
||||
log.Print("Removing bootstrap container.")
|
||||
return options.Container.RmContainer()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
625
pkg/bootstrap/ephemeral/command_test.go
Normal file
625
pkg/bootstrap/ephemeral/command_test.go
Normal file
@ -0,0 +1,625 @@
|
||||
/*
|
||||
Licensed 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
|
||||
|
||||
https://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 ephemeral_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
api "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/bootstrap/ephemeral"
|
||||
"opendev.org/airship/airshipctl/pkg/container"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
type mockContainer struct {
|
||||
imagePull func() error
|
||||
runCommand func() error
|
||||
getContainerLogs func() (io.ReadCloser, error)
|
||||
rmContainer func() error
|
||||
getID func() string
|
||||
waitUntilFinished func() error
|
||||
inspectContainer func() (container.State, error)
|
||||
}
|
||||
|
||||
func (mc *mockContainer) ImagePull() error {
|
||||
return mc.imagePull()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) RunCommand([]string, io.Reader, []string, []string) error {
|
||||
return mc.runCommand()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) GetContainerLogs() (io.ReadCloser, error) {
|
||||
return mc.getContainerLogs()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) RmContainer() error {
|
||||
return mc.rmContainer()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) GetID() string {
|
||||
return mc.getID()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) WaitUntilFinished() error {
|
||||
return mc.waitUntilFinished()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) InspectContainer() (container.State, error) {
|
||||
return mc.inspectContainer()
|
||||
}
|
||||
|
||||
// Unit test for the method getContainerStatus()
|
||||
func TestGetContainerStatus(t *testing.T) {
|
||||
testCfg := &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
container *mockContainer
|
||||
cfg *api.BootConfiguration
|
||||
debug bool
|
||||
expectedStatus container.Status
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
// Container running and no errors
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.RunningContainerStatus
|
||||
state.ExitCode = 0
|
||||
return state, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.RunningContainerStatus,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerLoadEphemeralConfigError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 1
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 1,
|
||||
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerValidationEphemeralConfigError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 2
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 2,
|
||||
ErrMsg: ephemeral.ContainerValidationEphemeralConfigError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerSetEnvVarsError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 3
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 3,
|
||||
ErrMsg: ephemeral.ContainerSetEnvVarsError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerUnknownCommandError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 4
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 4,
|
||||
ErrMsg: ephemeral.ContainerUnknownCommandError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerCreationEphemeralFailedError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 5
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 5,
|
||||
ErrMsg: ephemeral.ContainerCreationEphemeralFailedError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerDeletionEphemeralFailedError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 6
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 6,
|
||||
ErrMsg: ephemeral.ContainerDeletionEphemeralFailedError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerHelpCommandFailedError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 7
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 7,
|
||||
ErrMsg: ephemeral.ContainerHelpCommandFailedError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Container running and with ContainerUnknownError
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 8
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
expectedStatus: container.ExitedContainerStatus,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 8,
|
||||
ErrMsg: ephemeral.ContainerUnknownError,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
outBuf := &bytes.Buffer{}
|
||||
log.Init(tt.debug, outBuf)
|
||||
bootstrapOpts := ephemeral.BootstrapContainerOptions{
|
||||
Container: tt.container,
|
||||
Cfg: tt.cfg,
|
||||
Sleep: func(_ time.Duration) {},
|
||||
Debug: tt.debug,
|
||||
}
|
||||
actualStatus, actualErr := bootstrapOpts.GetContainerStatus()
|
||||
assert.Equal(t, tt.expectedStatus, actualStatus)
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test for the method waitUntilContainerExitsOrTimesout()
|
||||
func TestWaitUntilContainerExitsOrTimesout(t *testing.T) {
|
||||
testCfg := &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
container *mockContainer
|
||||
cfg *api.BootConfiguration
|
||||
debug bool
|
||||
maxRetries int
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
// Test: container exits normally
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 0
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
maxRetries: 10,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test: container times out
|
||||
container: &mockContainer{
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.RunningContainerStatus
|
||||
state.ExitCode = 0
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
debug: false,
|
||||
maxRetries: 1,
|
||||
expectedErr: ephemeral.ErrNumberOfRetriesExceeded{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
outBuf := &bytes.Buffer{}
|
||||
log.Init(tt.debug, outBuf)
|
||||
bootstrapOpts := ephemeral.BootstrapContainerOptions{
|
||||
Container: tt.container,
|
||||
Cfg: tt.cfg,
|
||||
Sleep: func(_ time.Duration) {},
|
||||
Debug: tt.debug,
|
||||
}
|
||||
actualErr := bootstrapOpts.WaitUntilContainerExitsOrTimesout(tt.maxRetries, "dummy-config.yaml", "dummy")
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test for the method createBootstrapContainer()
|
||||
func TestCreateBootstrapContainer(t *testing.T) {
|
||||
testCfg := &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
container *mockContainer
|
||||
cfg *api.BootConfiguration
|
||||
debug bool
|
||||
bootstrapCommand string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
// Test: Create Ephemeral Cluster successful
|
||||
container: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
rmContainer: func() error { return nil },
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 0
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
bootstrapCommand: ephemeral.BootCmdCreate,
|
||||
debug: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test: Delete Ephemeral Cluster successful
|
||||
container: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
rmContainer: func() error { return nil },
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 0
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
bootstrapCommand: ephemeral.BootCmdDelete,
|
||||
debug: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test: Create Ephemeral Cluster exit with error
|
||||
container: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
rmContainer: func() error { return nil },
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 1
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
bootstrapCommand: ephemeral.BootCmdCreate,
|
||||
debug: false,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 1,
|
||||
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test: Delete Ephemeral Cluster exit with error
|
||||
container: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
rmContainer: func() error { return nil },
|
||||
inspectContainer: func() (container.State, error) {
|
||||
state := container.State{}
|
||||
state.Status = container.ExitedContainerStatus
|
||||
state.ExitCode = 1
|
||||
return state, nil
|
||||
},
|
||||
getContainerLogs: func() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
cfg: testCfg,
|
||||
bootstrapCommand: ephemeral.BootCmdDelete,
|
||||
debug: false,
|
||||
expectedErr: ephemeral.ErrBootstrapContainerRun{
|
||||
ExitCode: 1,
|
||||
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt.cfg.EphemeralCluster.BootstrapCommand = tt.bootstrapCommand
|
||||
bootstrapOpts := ephemeral.BootstrapContainerOptions{
|
||||
Container: tt.container,
|
||||
Cfg: tt.cfg,
|
||||
Sleep: func(_ time.Duration) {},
|
||||
Debug: tt.debug,
|
||||
}
|
||||
actualErr := bootstrapOpts.CreateBootstrapContainer()
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInputs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *api.BootConfiguration
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "Verify successful",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Missing Ephemeral cluster config file",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "",
|
||||
},
|
||||
},
|
||||
expectedErr: ephemeral.ErrInvalidInput{
|
||||
What: ephemeral.MissingConfigError,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Missing Volume mount for the Bootstrap container",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: ephemeral.ErrInvalidInput{
|
||||
What: ephemeral.MissingVolumeError,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bootstrap container Volume mount mal formed 1",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Bootstrap container Volume mount mal formed 2",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: ephemeral.ErrVolumeMalFormed{},
|
||||
},
|
||||
{
|
||||
name: "Bootstrap container image missing",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "docker",
|
||||
Image: "",
|
||||
Volume: "/dummy:/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: ephemeral.ErrInvalidInput{
|
||||
What: ephemeral.MissingContainerImageError,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bootstrap container runtime missing",
|
||||
cfg: &api.BootConfiguration{
|
||||
BootstrapContainer: api.BootstrapContainer{
|
||||
ContainerRuntime: "",
|
||||
Image: "quay.io/dummy/dummy:latest",
|
||||
Volume: "/dummy:/dummy:/dummy",
|
||||
Kubeconfig: "dummy.kubeconfig",
|
||||
},
|
||||
EphemeralCluster: api.EphemeralCluster{
|
||||
BootstrapCommand: "dummy",
|
||||
ConfigFilename: "dummy.yaml",
|
||||
},
|
||||
},
|
||||
expectedErr: ephemeral.ErrInvalidInput{
|
||||
What: ephemeral.MissingContainerRuntimeError,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
bootstrapOpts := ephemeral.BootstrapContainerOptions{
|
||||
Cfg: tt.cfg,
|
||||
}
|
||||
t.Run(tt.name, func(subTest *testing.T) {
|
||||
actualErr := bootstrapOpts.VerifyInputs()
|
||||
assert.Equal(subTest, tt.expectedErr, actualErr)
|
||||
})
|
||||
}
|
||||
}
|
95
pkg/bootstrap/ephemeral/errors.go
Normal file
95
pkg/bootstrap/ephemeral/errors.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Licensed 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
|
||||
|
||||
https://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 ephemeral
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// ContainerLoadEphemeralConfigError ...
|
||||
ContainerLoadEphemeralConfigError = "Container error: Failed to load Ephemeral cluster config file"
|
||||
// ContainerValidationEphemeralConfigError ...
|
||||
ContainerValidationEphemeralConfigError = "Container error: Ephemeral cluster configuration file validation failed"
|
||||
// ContainerSetEnvVarsError ...
|
||||
ContainerSetEnvVarsError = "Container error: Failed to set environment variables"
|
||||
// ContainerUnknownCommandError ...
|
||||
ContainerUnknownCommandError = "Container error: Container unknown command"
|
||||
// ContainerCreationEphemeralFailedError ...
|
||||
ContainerCreationEphemeralFailedError = "Container error: Creation of Ephemeral cluster failed"
|
||||
// ContainerDeletionEphemeralFailedError ...
|
||||
ContainerDeletionEphemeralFailedError = "Container error: Deletion of Ephemeral cluster failed"
|
||||
// ContainerHelpCommandFailedError ...
|
||||
ContainerHelpCommandFailedError = "Container error: Help command failed"
|
||||
// ContainerUnknownError ...
|
||||
ContainerUnknownError = "Container error: Unknown error code"
|
||||
// NumberOfRetriesExceededError ...
|
||||
NumberOfRetriesExceededError = "number of retries exceeded"
|
||||
// MissingConfigError ...
|
||||
MissingConfigError = "Missing Ephemeral Cluster ConfigFilename"
|
||||
// MissingVolumeError ...
|
||||
MissingVolumeError = "Must specify volume bind for Bootstrap builder container"
|
||||
// VolumeMalFormedError ...
|
||||
VolumeMalFormedError = "Bootstrap Container volume mount is mal formed"
|
||||
// MissingContainerImageError ...
|
||||
MissingContainerImageError = "Missing Bootstrap Container image"
|
||||
// MissingContainerRuntimeError ...
|
||||
MissingContainerRuntimeError = "Missing Container runtime information"
|
||||
// InvalidBootstrapCommandError ...
|
||||
InvalidBootstrapCommandError = "Invalid Bootstrap Container command"
|
||||
)
|
||||
|
||||
// ErrBootstrapContainerRun is returned when there is an error executing the container
|
||||
type ErrBootstrapContainerRun struct {
|
||||
ExitCode int
|
||||
StdErr string
|
||||
ErrMsg string
|
||||
}
|
||||
|
||||
func (e ErrBootstrapContainerRun) Error() string {
|
||||
return fmt.Sprintf("Error from boostrap container: %s exit Code: %d\nContainer Logs: %s",
|
||||
e.ErrMsg, e.ExitCode, e.StdErr)
|
||||
}
|
||||
|
||||
// ErrNumberOfRetriesExceeded is returned when number of retries have exceeded a limit
|
||||
type ErrNumberOfRetriesExceeded struct {
|
||||
}
|
||||
|
||||
func (e ErrNumberOfRetriesExceeded) Error() string {
|
||||
return NumberOfRetriesExceededError
|
||||
}
|
||||
|
||||
// ErrInvalidInput is returned when invalid values were passed to the Bootstrap Container
|
||||
type ErrInvalidInput struct {
|
||||
What string
|
||||
}
|
||||
|
||||
func (e ErrInvalidInput) Error() string {
|
||||
return fmt.Sprintf("Invalid Bootstrap Container input: %s", e.What)
|
||||
}
|
||||
|
||||
// ErrVolumeMalFormed is returned when error volume mount is mal formed
|
||||
type ErrVolumeMalFormed struct {
|
||||
}
|
||||
|
||||
func (e ErrVolumeMalFormed) Error() string {
|
||||
return VolumeMalFormedError
|
||||
}
|
||||
|
||||
// ErrInvalidBootstrapCommand is returned when invalid command is invoked
|
||||
type ErrInvalidBootstrapCommand struct {
|
||||
}
|
||||
|
||||
func (e ErrInvalidBootstrapCommand) Error() string {
|
||||
return InvalidBootstrapCommandError
|
||||
}
|
Loading…
Reference in New Issue
Block a user