Merge "Bootstrap Ephemeral - Command methods"
This commit is contained in:
commit
5dbacb0a20
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