Add idempotency when stopping a VM and some tests

When deleting a VM, we try to force stop it. If the VM is already stopped,
LXD will return an error. Unfortunately, we can't import the drivers package
from LXD without also pulling in a bunch of linux specific CGO dependencies
which we want to avoid.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2022-06-28 15:13:02 +00:00
parent b4e9af13d5
commit f52accc47f
13 changed files with 340 additions and 27 deletions

View file

@ -36,12 +36,12 @@ func (e *External) ExecutablePath() (string, error) {
func (e *External) Validate() error {
if e.ConfigFile != "" {
if _, err := os.Stat(e.ConfigFile); err != nil {
return fmt.Errorf("failed to access config file %s", e.ConfigFile)
}
if !filepath.IsAbs(e.ConfigFile) {
return fmt.Errorf("path to config file must be an absolute path")
}
if _, err := os.Stat(e.ConfigFile); err != nil {
return fmt.Errorf("failed to access config file %s", e.ConfigFile)
}
}
if e.ProviderDir == "" {

88
config/external_test.go Normal file
View file

@ -0,0 +1,88 @@
package config
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func getDefaultExternalConfig(t *testing.T) External {
dir, err := ioutil.TempDir("", "garm-test")
if err != nil {
t.Fatalf("failed to create temporary directory: %s", err)
}
t.Cleanup(func() { os.RemoveAll(dir) })
err = ioutil.WriteFile(filepath.Join(dir, "garm-external-provider"), []byte{}, 0755)
if err != nil {
t.Fatalf("failed to write file: %s", err)
}
return External{
ConfigFile: "",
ProviderDir: dir,
}
}
func TestExternal(t *testing.T) {
cfg := getDefaultExternalConfig(t)
tests := []struct {
name string
cfg External
errString string
}{
{
name: "Config is valid",
cfg: cfg,
errString: "",
},
{
name: "Config path cannot be relative path",
cfg: External{
ConfigFile: "../test",
ProviderDir: cfg.ProviderDir,
},
errString: "path to config file must be an absolute path",
},
{
name: "Config must exist if specified",
cfg: External{
ConfigFile: "/there/is/no/config/here",
ProviderDir: cfg.ProviderDir,
},
errString: "failed to access config file /there/is/no/config/here",
},
{
name: "Missing provider dir",
cfg: External{
ConfigFile: "",
ProviderDir: "",
},
errString: "missing provider dir",
},
{
name: "Provider dir must not be relative",
cfg: External{
ConfigFile: "",
ProviderDir: "../test",
},
errString: "path to provider dir must be absolute",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := tc.cfg.Validate()
if tc.errString == "" {
assert.Nil(t, err)
} else {
assert.NotNil(t, err)
assert.EqualError(t, err, tc.errString)
}
})
}
}

View file

@ -35,14 +35,16 @@ const (
LXDImageContainer LXDImageType = "container"
)
type LXDRemote struct {
// LXDImageRemote holds information about a remote server from which LXD can fetch
// OS images. Typically this will be a simplestreams server.
type LXDImageRemote struct {
Address string `toml:"addr" json:"addr"`
Public bool `toml:"public" json:"public"`
Protocol LXDRemoteProtocol `toml:"protocol" json:"protocol"`
InsecureSkipVerify bool `toml:"skip_verify" json:"skip-verify"`
}
func (l *LXDRemote) Validate() error {
func (l *LXDImageRemote) Validate() error {
if l.Protocol != SimpleStreams {
// Only supports simplestreams for now.
return fmt.Errorf("invalid remote protocol %s. Supported protocols: %s", l.Protocol, SimpleStreams)
@ -94,7 +96,7 @@ type LXD struct {
// ImageRemotes is a map to a set of remote image repositories we can use to
// download images.
ImageRemotes map[string]LXDRemote `toml:"image_remotes" json:"image-remotes"`
ImageRemotes map[string]LXDImageRemote `toml:"image_remotes" json:"image-remotes"`
// SecureBoot enables secure boot for VMs spun up using this provider.
SecureBoot bool `yaml:"secure_boot" json:"secure-boot"`
@ -113,12 +115,17 @@ func (l *LXD) Validate() error {
return fmt.Errorf("unix_socket or address must be specified")
}
if _, err := url.Parse(l.URL); err != nil {
url, err := url.ParseRequestURI(l.URL)
if err != nil {
return fmt.Errorf("invalid LXD URL")
}
if url.Scheme != "https" {
return fmt.Errorf("address must be https")
}
if l.ClientCertificate == "" || l.ClientKey == "" {
return fmt.Errorf("client_certificate and client_key are mandatory when connecting via HTTPs")
return fmt.Errorf("client_certificate and client_key are mandatory")
}
if _, err := os.Stat(l.ClientCertificate); err != nil {

161
config/lxd_test.go Normal file
View file

@ -0,0 +1,161 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func getDefaultLXDImageRemoteConfig() LXDImageRemote {
return LXDImageRemote{
Address: "https://cloud-images.ubuntu.com/releases",
Public: true,
Protocol: SimpleStreams,
InsecureSkipVerify: false,
}
}
func getDefaultLXDConfig() LXD {
remote := getDefaultLXDImageRemoteConfig()
return LXD{
URL: "https://example.com:8443",
ProjectName: "default",
IncludeDefaultProfile: false,
ClientCertificate: "../testdata/lxd/certs/client.crt",
ClientKey: "../testdata/lxd/certs/client.key",
TLSServerCert: "../testdata/lxd/certs/servercert.crt",
ImageRemotes: map[string]LXDImageRemote{
"default": remote,
},
SecureBoot: false,
}
}
func TestLXDRemote(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
err := cfg.Validate()
assert.Nil(t, err)
}
func TestLXDRemoteEmptyAddress(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = ""
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "missing address")
}
func TestLXDRemoteInvalidAddress(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = "bogus address"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "validating address: parse \"bogus address\": invalid URI for request")
}
func TestLXDRemoteIvalidAddressScheme(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = "ftp://whatever"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "address must be http or https")
}
func TestLXDConfig(t *testing.T) {
cfg := getDefaultLXDConfig()
err := cfg.Validate()
assert.Nil(t, err)
}
func TestLXDWithInvalidUnixSocket(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.UnixSocket = "bogus unix socket"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "could not access unix socket bogus unix socket: \"stat bogus unix socket: no such file or directory\"")
}
func TestMissingUnixSocketAndMissingURL(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = ""
cfg.UnixSocket = ""
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "unix_socket or address must be specified")
}
func TestInvalidLXDURL(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = "bogus"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "invalid LXD URL")
}
func TestLXDURLIsHTTPS(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = "http://example.com"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "address must be https")
}
func TestMissingClientCertOrKey(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ClientKey = ""
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "client_certificate and client_key are mandatory")
cfg = getDefaultLXDConfig()
cfg.ClientCertificate = ""
err = cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "client_certificate and client_key are mandatory")
}
func TestLXDIvalidCertOrKeyPaths(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ClientCertificate = "/i/am/not/here"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "failed to access client certificate /i/am/not/here: \"stat /i/am/not/here: no such file or directory\"")
cfg.ClientCertificate = "../testdata/lxd/certs/client.crt"
cfg.ClientKey = "/me/neither"
err = cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "failed to access client key /me/neither: \"stat /me/neither: no such file or directory\"")
}
func TestLXDInvalidServerCertPath(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.TLSServerCert = "/not/a/valid/server/cert/path"
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "failed to access tls_server_certificate /not/a/valid/server/cert/path: \"stat /not/a/valid/server/cert/path: no such file or directory\"")
}
func TestInvalidLXDImageRemotes(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ImageRemotes["default"] = LXDImageRemote{
Protocol: LXDRemoteProtocol("bogus"),
}
err := cfg.Validate()
assert.NotNil(t, err)
assert.EqualError(t, err, "remote default is invalid: invalid remote protocol bogus. Supported protocols: simplestreams")
}