Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloud-init): Implement upcoming cloud-init WSL support #506

Merged
merged 31 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
823cc7c
Implement CloudInit object to write cloud-init files
EduardGomezEscandell Jan 23, 2024
52f9460
Cloud-init test golden files
EduardGomezEscandell Feb 5, 2024
72fec67
Use cloudinit in proservices and Landscape
EduardGomezEscandell Feb 9, 2024
9b7839a
Update Landscape tests
EduardGomezEscandell Feb 5, 2024
823242f
Remove task provisioning
EduardGomezEscandell Mar 27, 2024
92fb914
Add end-to-end test validating cloud-init functionality
EduardGomezEscandell Feb 16, 2024
a99ba06
Fix wrong decorate message
CarlosNihelton Apr 3, 2024
455f6d2
Add a test case in proservices breaking cloud-init initialization
CarlosNihelton Apr 5, 2024
0c0c038
Fix removeErr logic in TestUninstall
CarlosNihelton Apr 5, 2024
9edc2ad
Marshall the cloud-init data heading in a single line
CarlosNihelton Apr 5, 2024
870fe9c
Test Landscape assign host with an empty UID
CarlosNihelton Apr 5, 2024
57060d7
t.Skip() cloud-init end-to-end test case
CarlosNihelton Apr 5, 2024
230532b
Ensures landscape server stops at t.Cleanup
CarlosNihelton Apr 15, 2024
570d225
Removes "Success" prefix from test case names
CarlosNihelton Apr 15, 2024
3d64826
Makes WriteAgentData private and testing Update (Notify)
CarlosNihelton Apr 15, 2024
298c712
Log if we leave undesired temp files in writeFileInDir
CarlosNihelton Apr 15, 2024
9f67749
Stylistic else if in RemoveDistroData
CarlosNihelton Apr 15, 2024
7894e8e
Cloudinit's TestNew to check if returned is nil.
CarlosNihelton Apr 15, 2024
42c55e7
Ensures cloudinit.New returns an empty object on failure
CarlosNihelton Apr 15, 2024
5cc063f
Adds missing "Setup: " prefix in require assertions.
CarlosNihelton Apr 15, 2024
3d573f7
Fix function name in TestWriteDistroData's require clauses
CarlosNihelton Apr 15, 2024
d637d0d
Remove the emptyData test case field
CarlosNihelton Apr 15, 2024
c64f2b6
Rename test case in executor TestInstall
CarlosNihelton Apr 15, 2024
ad997f9
Avoid pointer to string in the middle of executor's TestInstall
CarlosNihelton Apr 15, 2024
d62b25b
Skipping all end-to-end test cases that depend on cloud-init
CarlosNihelton Apr 15, 2024
924ea42
Cloudinit's TestNew to check if returned is not nil
CarlosNihelton Apr 16, 2024
097109d
Adds missing test case in distro_tesrt :)
CarlosNihelton Apr 16, 2024
b920c2d
Proservices TestNew to exercise the notification closures
CarlosNihelton Apr 16, 2024
a4fb4f6
Stop proservices before require clauses in test
CarlosNihelton Apr 17, 2024
0cb6f35
Remove the golden assertions out of the defer clause
CarlosNihelton Apr 17, 2024
d3426c8
[NEW!!!] Only stop proservices if err is nil
CarlosNihelton Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions end-to-end/cloud_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package endtoend_test

import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"

landscapeapi "github.com/canonical/landscape-hostagent-api"
"github.com/canonical/ubuntu-pro-for-wsl/common/testutils"
"github.com/stretchr/testify/require"
wsl "github.com/ubuntu/gowsl"
)

func TestCloudInitIntegration(t *testing.T) {
// TODO: Remove this line when cloud-init support for UP4W is released.
// Follow this PR for more information: https://github.com/canonical/cloud-init/pull/5116
t.Skip("This test depends on cloud-init support for UP4W being released.")
currentFuncName := t.Name()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

testSetup(t)
defer logWindowsAgentOnError(t)

landscape := NewLandscape(t, ctx)
writeUbuntuProRegistry(t, "LandscapeConfig", landscape.ClientConfig)

serverDone := make(chan struct{})
go func() {
defer close(serverDone)
landscape.Serve()
}()
t.Cleanup(func() {
landscape.Stop()
<-serverDone
})
defer landscape.LogOnError(t)

hostname, err := os.Hostname()
require.NoError(t, err, "Setup: could not test machine's hostname")

proToken := os.Getenv(proTokenEnv)
require.NotEmptyf(t, proToken, "Setup: environment variable %q should contain a valid pro token, but is empty", proTokenEnv)
writeUbuntuProRegistry(t, "UbuntuProToken", proToken)

cleanup := startAgent(t, ctx, currentFuncName)
defer cleanup()

info := landscape.RequireReceivedInfo(t, proToken, nil, hostname)

out, err := os.ReadFile(filepath.Join(testutils.TestFixturePath(t), "user-data.yaml"))
require.NoError(t, err, "Setup: could not read cloud-init file")
cloudInitUserData := string(out)

err = landscape.service.SendCommand(ctx, info.UID, &landscapeapi.Command{
Cmd: &landscapeapi.Command_Install_{
Install: &landscapeapi.Command_Install{
Id: strings.Split(referenceDistroAppx, ".")[1], // CanonicalGroupLimited.[UbuntuPreview]
Cloudinit: &cloudInitUserData,
},
},
})
require.NoError(t, err, "Setup: could not send install command")

distro := wsl.NewDistro(ctx, referenceDistro)

//nolint:errcheck // Nothing we can do about it
defer distro.Unregister()

require.Eventually(t, func() bool {
ok, err := distro.IsRegistered()
if err != nil {
t.Logf("could not determine if distro is registered: %v", err)
return false
}
return ok
}, time.Minute, time.Second, "Distro should have been registered")

defer logWslProServiceOnError(t, ctx, distro)

runCommand(t, ctx, time.Minute, distro, "cloud-init status --wait")

require.Eventually(t, func() bool {
attached, err := distroIsProAttached(t, ctx, distro)
if err != nil {
t.Logf("could not determine if distro is attached: %v", err)
return false
}
return attached
}, 10*time.Second, time.Second, "distro should have been Pro attached")

userName := runCommand(t, ctx, 10*time.Second, distro, "whoami")
require.Equal(t, "testuser", userName, "cloud-init should have configured the default user")

landscape.RequireReceivedInfo(t, proToken, []wsl.Distro{distro}, hostname)
landscape.RequireUninstallCommand(t, ctx, distro, info)
}

//nolint:revive // t always goes before ctx
func runCommand(t *testing.T, ctx context.Context, timeout time.Duration, distro wsl.Distro, comand string) string {
t.Helper()

ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

out, err := distro.Command(ctx, comand).CombinedOutput()
if err == nil {
return string(out)
}

// We check the context to see if it was a timeout.
// This makes the error message easier to understand.

select {
case <-ctx.Done():
require.Fail(t, "Timed out waiting for cloud-init to finish")
default:
}

require.NoError(t, err, "could not determine if cloud-init is done: %s. Output: %s", out, out)
return ""
}
11 changes: 7 additions & 4 deletions end-to-end/landscape_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (l landscape) Stop() {
}

// RequireReceivedInfo checks that a connection to Landscape was made and the proper information was sent.
func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistro gowsl.Distro, wantHostname string) landscapemockservice.HostInfo {
func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistros []gowsl.Distro, wantHostname string) landscapemockservice.HostInfo {
t.Helper()

require.Eventually(t, func() bool {
Expand All @@ -122,9 +122,12 @@ func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistr
// Validate token
require.Equal(t, wantToken, info.Token, "Landscape did not receive the right pro token")

// Validate distro
require.Len(t, info.Instances, 1, "Landscape did not receive the right number of distros")
require.Equal(t, wantDistro.Name(), info.Instances[0].ID, "Landscape did not receive the right distro name from the agent")
// Validate distros
gotDistros := make([]string, 0)
for _, instance := range info.Instances {
gotDistros = append(gotDistros, instance.ID)
}
require.ElementsMatch(t, wantDistros, gotDistros, "Landscape did not receive the right distros")

// Validate hostname
require.Equal(t, wantHostname, info.Hostname, "Landscape did not receive the right hostname from the agent")
Expand Down
19 changes: 15 additions & 4 deletions end-to-end/manual_token_input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import (
)

func TestManualTokenInput(t *testing.T) {
// TODO: Remove this line when cloud-init support for UP4W is released.
// Follow this PR for more information: https://github.com/canonical/cloud-init/pull/5116
t.Skip("This test depends on cloud-init support for UP4W being released.")

type whenToken int
const (
never whenToken = iota
Expand Down Expand Up @@ -44,9 +48,16 @@ func TestManualTokenInput(t *testing.T) {
landscape := NewLandscape(t, ctx)
writeUbuntuProRegistry(t, "LandscapeConfig", landscape.ClientConfig)

go landscape.Serve()
serverDone := make(chan struct{})
go func() {
defer close(serverDone)
landscape.Serve()
}()
t.Cleanup(func() {
landscape.Stop()
<-serverDone
})
defer landscape.LogOnError(t)
defer landscape.Stop()

hostname, err := os.Hostname()
require.NoError(t, err, "Setup: could not test machine's hostname")
Expand All @@ -63,7 +74,7 @@ func TestManualTokenInput(t *testing.T) {

defer logWslProServiceOnError(t, ctx, d)

out, err := d.Command(ctx, "exit 0").CombinedOutput()
out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)

// ... or after registration, but never both.
Expand Down Expand Up @@ -94,7 +105,7 @@ func TestManualTokenInput(t *testing.T) {
return attached
}, maxTimeout, time.Second, "distro should have been Pro attached")

info := landscape.RequireReceivedInfo(t, os.Getenv(proTokenEnv), d, hostname)
info := landscape.RequireReceivedInfo(t, os.Getenv(proTokenEnv), []wsl.Distro{d}, hostname)
landscape.RequireUninstallCommand(t, ctx, d, info)
})
}
Expand Down
19 changes: 15 additions & 4 deletions end-to-end/organization_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (
)

func TestOrganizationProvidedToken(t *testing.T) {
// TODO: Remove this line when cloud-init support for UP4W is released.
// Follow this PR for more information: https://github.com/canonical/cloud-init/pull/5116
t.Skip("This test depends on cloud-init support for UP4W being released.")

type whenToken int
const (
never whenToken = iota
Expand Down Expand Up @@ -43,9 +47,16 @@ func TestOrganizationProvidedToken(t *testing.T) {
landscape := NewLandscape(t, ctx)
writeUbuntuProRegistry(t, "LandscapeConfig", landscape.ClientConfig)

go landscape.Serve()
serverDone := make(chan struct{})
go func() {
defer close(serverDone)
landscape.Serve()
}()
t.Cleanup(func() {
landscape.Stop()
<-serverDone
})
defer landscape.LogOnError(t)
defer landscape.Stop()

hostname, err := os.Hostname()
require.NoError(t, err, "Setup: could not test machine's hostname")
Expand All @@ -65,7 +76,7 @@ func TestOrganizationProvidedToken(t *testing.T) {

defer logWslProServiceOnError(t, ctx, d)

out, err := d.Command(ctx, "exit 0").CombinedOutput()
out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)

if tc.whenToken == afterDistroRegistration {
Expand Down Expand Up @@ -99,7 +110,7 @@ func TestOrganizationProvidedToken(t *testing.T) {
return attached
}, maxTimeout, time.Second, "distro should have been Pro attached")

info := landscape.RequireReceivedInfo(t, proToken, d, hostname)
info := landscape.RequireReceivedInfo(t, proToken, []wsl.Distro{d}, hostname)
landscape.RequireUninstallCommand(t, ctx, d, info)
})
}
Expand Down
19 changes: 15 additions & 4 deletions end-to-end/purchase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const (
)

func TestPurchase(t *testing.T) {
// TODO: Remove this line when cloud-init support for UP4W is released.
// Follow this PR for more information: https://github.com/canonical/cloud-init/pull/5116
t.Skip("This test depends on cloud-init support for UP4W being released.")

type whenToken int
const (
never whenToken = iota
Expand Down Expand Up @@ -58,9 +62,16 @@ func TestPurchase(t *testing.T) {
landscape := NewLandscape(t, ctx)
writeUbuntuProRegistry(t, "LandscapeConfig", landscape.ClientConfig)

go landscape.Serve()
serverDone := make(chan struct{})
go func() {
defer close(serverDone)
landscape.Serve()
}()
t.Cleanup(func() {
landscape.Stop()
<-serverDone
})
defer landscape.LogOnError(t)
defer landscape.Stop()

hostname, err := os.Hostname()
require.NoError(t, err, "Setup: could not test machine's hostname")
Expand Down Expand Up @@ -121,7 +132,7 @@ func TestPurchase(t *testing.T) {

defer logWslProServiceOnError(t, ctx, d)

out, err := d.Command(ctx, "exit 0").CombinedOutput()
out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)

// ... or after registration, but never both.
Expand Down Expand Up @@ -153,7 +164,7 @@ func TestPurchase(t *testing.T) {
return attached
}, maxTimeout, time.Second, "distro should have been Pro attached")

landscape.RequireReceivedInfo(t, token, d, hostname)
landscape.RequireReceivedInfo(t, token, []wsl.Distro{d}, hostname)
// Skipping because we know it to be broken
// See https://warthogs.atlassian.net/browse/UDENG-1810
//
Expand Down
13 changes: 13 additions & 0 deletions end-to-end/testdata/TestCloudInitIntegration/user-data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#cloud-config
users:
- name: testuser
gecos: Test User
sudo: ALL=(ALL) NOPASSWD:ALL
groups: [adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,users,netdev]
shell: /bin/bash
write_files:
- path: /etc/wsl.conf
append: true
content: |
[user]
default=testuser
1 change: 1 addition & 0 deletions gui/packages/ubuntupro/end_to_end/end_to_end_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void main(List<String> args) {

const testCases = {
'TestOrganizationProvidedToken': testOrganizationProvidedToken,
'TestCloudInitIntegration': testOrganizationProvidedToken,
'TestManualTokenInput': testManualTokenInput,
'TestPurchase': testPurchase,
};
Expand Down
Loading
Loading