Skip to content

Commit

Permalink
Parallels: support Linux as a target platform (#287)
Browse files Browse the repository at this point in the history
* RetrieveBinary: use the OS and the architecture from the arguments

* Parallels: support Linux platform in addition to Darwin

* Test Parallels executor with Linux

* Combine shouldRenewDHCP and delayedIsolation into clonedFromSuspended

* Only synchronize time for suspended VMs

* Parallels: retry SSH connections too and use context for all retries
  • Loading branch information
edigaryev authored Feb 12, 2021
1 parent c95096d commit ad2878c
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 35 deletions.
9 changes: 6 additions & 3 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ task:
docker: installed
parallels: installed
env:
CIRRUS_INTERNAL_PARALLELS_VM: big-sur-base
CIRRUS_INTERNAL_PARALLELS_SSH_USER: admin
CIRRUS_INTERNAL_PARALLELS_SSH_PASSWORD: admin
CIRRUS_INTERNAL_PARALLELS_DARWIN_VM: big-sur-base
CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_USER: admin
CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_PASSWORD: admin
CIRRUS_INTERNAL_PARALLELS_LINUX_VM: debian
CIRRUS_INTERNAL_PARALLELS_LINUX_SSH_USER: parallels
CIRRUS_INTERNAL_PARALLELS_LINUX_SSH_PASSWORD: parallels
test_script:
- go version
- go test -p 1 ./...
Expand Down
3 changes: 1 addition & 2 deletions internal/executor/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
)

var ErrAgentDownloadFailed = errors.New("failed to download agent")
Expand Down Expand Up @@ -54,7 +53,7 @@ func RetrieveBinary(

// Download the agent
agentURL := fmt.Sprintf("https://github.com/cirruslabs/cirrus-ci-agent/releases/download/v%s/agent-%s-%s%s",
agentVersion, runtime.GOOS, runtime.GOARCH, agentSuffix)
agentVersion, agentOS, agentArchitecture, agentSuffix)

req, err := http.NewRequestWithContext(ctx, "GET", agentURL, http.NoBody)
if err != nil {
Expand Down
25 changes: 20 additions & 5 deletions internal/executor/executor_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,30 @@ import (
"io"
"io/ioutil"
"os"
"strings"
"testing"
)

func TestExecutorParallels(t *testing.T) {
testutil.TempChdir(t)

image, imageOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_VM")
user, userOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_USER")
password, passwordOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_PASSWORD")
for _, platform := range []string{"darwin", "linux"} {
t.Run(platform, func(t *testing.T) {
commonPrefix := fmt.Sprintf("CIRRUS_INTERNAL_PARALLELS_%s_", strings.ToUpper(platform))

imageEnvVar := commonPrefix + "VM"
userEnvVar := commonPrefix + "SSH_USER"
passwordEnvVar := commonPrefix + "SSH_PASSWORD"

doSingleParallelsExecution(t, platform, imageEnvVar, userEnvVar, passwordEnvVar)
})
}
}

func doSingleParallelsExecution(t *testing.T, platform, imageEnvVar, userEnvVar, passwordEnvVar string) {
image, imageOk := os.LookupEnv(imageEnvVar)
user, userOk := os.LookupEnv(userEnvVar)
password, passwordOk := os.LookupEnv(passwordEnvVar)
if !imageOk || !userOk || !passwordOk {
t.SkipNow()
}
Expand All @@ -30,11 +45,11 @@ func TestExecutorParallels(t *testing.T) {
image: %s
user: %s
password: %s
platform: darwin
platform: %s
task:
parallels_check_script: true
`, image, user, password)
`, image, user, password, platform)

if err := ioutil.WriteFile(".cirrus.yml", []byte(config), 0600); err != nil {
t.Fatal(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
func TestPrlctl(t *testing.T) {
ctx := context.Background()

_, imageOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_VM")
_, userOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_USER")
_, passwordOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_PASSWORD")
_, imageOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_VM")
_, userOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_USER")
_, passwordOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_PASSWORD")
if !imageOk || !userOk || !passwordOk {
t.SkipNow()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ func cloneFromSuspended(ctx context.Context, vmPathFrom string) (*VM, error) {
vm := &VM{
uuid: uuid.New().String(),

shouldRenewDHCP: true,
delayedIsolation: true,
clonedFromSuspended: true,
}

serverInfo, err := GetServerInfo(ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,23 @@ func (parallels *Parallels) Run(ctx context.Context, config *runconfig.RunConfig
if err := retry.Do(func() error {
ip, err = vm.RetrieveIP(ctx)
return err
}, retry.RetryIf(func(err error) bool {
}, retry.Context(ctx), retry.RetryIf(func(err error) bool {
return errors.Is(err, ErrDHCPSnoopFailed)
})); err != nil {
return fmt.Errorf("%w: failed to retrieve VM %q IP-address: %v", ErrFailed, vm.name, err)
}

// Connect to the VM and upload the agent
var netConn net.Conn
addr := ip + ":22"

dialer := net.Dialer{}
if err := retry.Do(func() error {
dialer := net.Dialer{}

netConn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
netConn, err = dialer.DialContext(ctx, "tcp", addr)

return err
}, retry.Context(ctx)); err != nil {
return fmt.Errorf("%w: failed to connect to the VM %q on SSH port: %v", ErrFailed, vm.Ident(), err)
}

Expand Down Expand Up @@ -111,10 +115,12 @@ func (parallels *Parallels) Run(ctx context.Context, config *runconfig.RunConfig
return fmt.Errorf("%w: failed to start a shell on VM %q: %v", ErrFailed, vm.Ident(), err)
}

// Synchronize time
_, err = stdinBuf.Write([]byte(TimeSyncCommand(time.Now().UTC())))
if err != nil {
return fmt.Errorf("%w: failed to sync time on VM %q: %v", ErrFailed, vm.Ident(), err)
// Synchronize time for suspended VMs
if vm.ClonedFromSuspended() {
_, err = stdinBuf.Write([]byte(TimeSyncCommand(time.Now().UTC())))
if err != nil {
return fmt.Errorf("%w: failed to sync time on VM %q: %v", ErrFailed, vm.Ident(), err)
}
}

command := []string{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ type VM struct {
uuid string
name string

shouldRenewDHCP bool
delayedIsolation bool
clonedFromSuspended bool
}

type NetworkAdapterInfo struct {
Expand Down Expand Up @@ -67,8 +66,12 @@ func NewVMClonedFrom(ctx context.Context, vmNameFrom string) (*VM, error) {
return cloneFromDefault(ctx, vmInfoFrom.Name)
}

func (vm *VM) ClonedFromSuspended() bool {
return vm.clonedFromSuspended
}

func (vm *VM) Start(ctx context.Context) error {
if !vm.delayedIsolation {
if !vm.clonedFromSuspended {
if err := vm.isolate(ctx); err != nil {
return err
}
Expand All @@ -79,13 +82,14 @@ func (vm *VM) Start(ctx context.Context) error {
return fmt.Errorf("%w: failed to start VM %q: %v", ErrVMFailed, vm.Ident(), err)
}

if vm.shouldRenewDHCP {
if vm.clonedFromSuspended {
if err := vm.renewDHCP(ctx); err != nil {
return err
}
}

if vm.delayedIsolation {
// Isolation is delayed for suspended VMs because Parallels
// prevents us from modifying this setting before we start
// such VM
if err := vm.isolate(ctx); err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/abstract"
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/persistentworker/isolation/none"
"github.com/cirruslabs/cirrus-cli/internal/executor/instance/persistentworker/isolation/parallels"
"strings"
)

var ErrInvalidIsolation = errors.New("invalid isolation parameters")
Expand All @@ -20,11 +21,12 @@ func New(isolation *api.Isolation) (abstract.Instance, error) {
case *api.Isolation_None_:
return none.New()
case *api.Isolation_Parallels_:
if iso.Parallels.Platform != api.Platform_DARWIN {
return nil, fmt.Errorf("%w: only Darwin is currently supported", ErrInvalidIsolation)
if iso.Parallels.Platform != api.Platform_DARWIN && iso.Parallels.Platform != api.Platform_LINUX {
return nil, fmt.Errorf("%w: only Darwin and Linux are currently supported", ErrInvalidIsolation)
}

return parallels.New(iso.Parallels.Image, iso.Parallels.User, iso.Parallels.Password, "darwin")
return parallels.New(iso.Parallels.Image, iso.Parallels.User, iso.Parallels.Password,
strings.ToLower(iso.Parallels.Platform.String()))
default:
return nil, fmt.Errorf("%w: unsupported isolation type %T", ErrInvalidIsolation, iso)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/worker/worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ func TestWorker(t *testing.T) {
var isolation *api.Isolation

// Support Parallels isolation testing configured via environment variables
image, imageOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_VM")
user, userOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_USER")
password, passwordOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_SSH_PASSWORD")
image, imageOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_VM")
user, userOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_USER")
password, passwordOk := os.LookupEnv("CIRRUS_INTERNAL_PARALLELS_DARWIN_SSH_PASSWORD")
if imageOk && userOk && passwordOk {
t.Logf("Using Parallels VM %s for testing...", image)
sharedNetworkHostIP, err := parallels.SharedNetworkHostIP(context.Background())
Expand Down

0 comments on commit ad2878c

Please sign in to comment.