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…
x
Reference in New Issue
Block a user