blob: 9502e25b81041fa764d946f7bb5f6266f1588ca0 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"chromiumos/test/provision/cmd/provisionserver/bootstrap/info"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/mock_services"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice"
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"github.com/golang/mock/gomock"
conf "go.chromium.org/chromiumos/config/go"
"go.chromium.org/chromiumos/config/go/test/api"
)
func TestCrosInstallStateTransitions(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s"})).Return(fmt.Sprintf("root%s", info.PartitionNumRootA), nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s", "-d"})).Return("root_disk", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq(info.DlcLibDir)).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-f", "/var/cache/dlc/*/*/dlc_b/verified"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
)
// Concurrent portion
sam.EXPECT().CopyData(gomock.Any(), gomock.Any()).Return("1", nil).AnyTimes()
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot5 obs=2M"})).Times(1).Return("", nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot4 obs=2M"})).Times(1).Return("", nil)
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{
"tmpmnt=$(mktemp -d)",
"&&",
"mount -o ro root_diskroot5 ${tmpmnt}",
"&&",
"${tmpmnt}/postinst root_diskroot5",
"&&",
"{ umount ${tmpmnt} || true; }",
"&&",
"{ rmdir ${tmpmnt} || true; }"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("crossystem"), gomock.Eq([]string{"clear_tpm_owner_request=1"})).Return("", nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed install state: %v", err)
}
// POST INSTALL
st = st.Next()
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), gomock.Eq([]string{"'fast keepimg'", ">", "/mnt/stateful_partition/factory_install_reset"})).Return("", nil),
sam.EXPECT().Restart(gomock.Any()).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{"rm -rf /mnt/stateful_partition/.update_available /mnt/stateful_partition/var_new /mnt/stateful_partition/dev_image_new", "&&", "curl 1 | tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition -xzf -", "&&", "echo -n clobber > /mnt/stateful_partition/.update_available"})).Return("", nil),
sam.EXPECT().Restart(gomock.Any()).Return(nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed post-install state: %v", err)
}
// VERIFY (Currently empty)
st = st.Next()
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed verify state: %v", err)
}
//PROVISION DLC
st = st.Next()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s"})).Return(fmt.Sprintf("root%s", info.PartitionNumRootA), nil),
)
// Concurrent Portion
// Return not verfied so we can test full case:
sam.EXPECT().PathExists(gomock.Any(), "/var/lib/dlcservice/dlc/1/dlc_a/verified").Return(false, nil)
sam.EXPECT().RunCmd(gomock.Any(), "", []string{"mkdir", "-p", "/var/cache/dlc/1/package/dlc_a", "&&", "curl", "--output", "/var/cache/dlc/1/package/dlc_a/dlc.img", "1"}).Return("", nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Times(1).Return("", nil)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed provision-dlc state: %v", err)
}
if st.Next() != nil {
t.Fatalf("provision should be the last step")
}
}
func TestInstallPostInstallFailureCausesReversal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s"})).Return(fmt.Sprintf("root%s", info.PartitionNumRootA), nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s", "-d"})).Return("root_disk", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq(info.DlcLibDir)).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-f", "/var/cache/dlc/*/*/dlc_b/verified"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
)
// Concurrent portion
sam.EXPECT().CopyData(gomock.Any(), gomock.Any()).Return("1", nil).AnyTimes()
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot5 obs=2M"})).Times(1).Return("", nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot4 obs=2M"})).Times(1).Return("", nil)
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{
"tmpmnt=$(mktemp -d)",
"&&",
"mount -o ro root_diskroot5 ${tmpmnt}",
"&&",
"${tmpmnt}/postinst root_diskroot5",
"&&",
"{ umount ${tmpmnt} || true; }",
"&&",
"{ rmdir ${tmpmnt} || true; }"})).Return("", fmt.Errorf("postinstall error")),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new", "/mnt/stateful_partition/.update_available"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("/postinst"), gomock.Eq([]string{"root_diskroot3", "2>&1"})).Return("", nil),
)
if err := st.Execute(ctx); err.Error() != "failed to set next kernel, postinstall error" {
t.Fatalf("expected specific error, instead got: %v", err)
}
}
func TestInstallClearTPMFailureCausesReversal(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s"})).Return(fmt.Sprintf("root%s", info.PartitionNumRootA), nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s", "-d"})).Return("root_disk", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq(info.DlcLibDir)).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-f", "/var/cache/dlc/*/*/dlc_b/verified"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
)
// Concurrent portion
sam.EXPECT().CopyData(gomock.Any(), gomock.Any()).Return("1", nil).AnyTimes()
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot5 obs=2M"})).Times(1).Return("", nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl"), gomock.Eq([]string{"1", "|", "gzip -d", "|", "dd of=root_diskroot4 obs=2M"})).Times(1).Return("", nil)
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{
"tmpmnt=$(mktemp -d)",
"&&",
"mount -o ro root_diskroot5 ${tmpmnt}",
"&&",
"${tmpmnt}/postinst root_diskroot5",
"&&",
"{ umount ${tmpmnt} || true; }",
"&&",
"{ rmdir ${tmpmnt} || true; }"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("crossystem"), gomock.Eq([]string{"clear_tpm_owner_request=1"})).Return("", fmt.Errorf("clear TPM error")),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new", "/mnt/stateful_partition/.update_available"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("/postinst"), gomock.Eq([]string{"root_diskroot3", "2>&1"})).Return("", nil),
)
if err := st.Execute(ctx); err.Error() != "failed to clear TPM, clear TPM error" {
t.Fatalf("expected specific error, instead got: %v", err)
}
}
func TestPostInstallStatePreservesStatefulWhenRequested(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
true, // <- preserve stateful
[]*api.InstallCrosRequest_DLCSpec{},
)
ctx := context.Background()
// Install -> PostInstall
st := cs.GetFirstState().Next()
gomock.InOrder(
// Delete steps elided due to preserve stateful
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/stateful.tzg")).Return("url", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{"rm -rf /mnt/stateful_partition/.update_available /mnt/stateful_partition/var_new /mnt/stateful_partition/dev_image_new", "&&", "curl url | tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition -xzf -", "&&", "echo -n clobber > /mnt/stateful_partition/.update_available"})).Return("", nil),
sam.EXPECT().Restart(gomock.Any()).Return(nil),
)
// Nothing should be run, so no need to use mock expect
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed post-install state: %v", err)
}
}
func TestPostInstallStatefulFailsGetsReversed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
true, // <- preserve stateful
[]*api.InstallCrosRequest_DLCSpec{},
)
ctx := context.Background()
// Install -> PostInstall
st := cs.GetFirstState().Next()
gomock.InOrder(
// Delete steps elided due to preserve stateful
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
// Simulated error:
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/stateful.tzg")).Return("", errors.New("some copy error")),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new", "/mnt/stateful_partition/.update_available"})).Return("", nil),
)
// Nothing should be run, so no need to use mock expect
if err := st.Execute(ctx); err.Error() != "failed to provision stateful, failed to install stateful partition, failed to get GS Cache URL, some copy error" {
t.Fatalf("Post install should've failed with specific error, instead got: %v", err)
}
}
func TestProvisionDLCWithEmptyDLCsDoesNotExecute(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
false,
[]*api.InstallCrosRequest_DLCSpec{},
)
ctx := context.Background()
// Install -> PostInstall -> Verify -> ProvisionDLC
st := cs.GetFirstState().Next().Next().Next()
// Nothing should be run, so no need to use mock expect
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed provision-dlc state: %v", err)
}
}
func TestProvisionDLCWhenVerifyIsTrueDoesNotExecuteInstall(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := crosservice.NewCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// Install -> PostInstall -> Verify -> ProvisionDLC
st := cs.GetFirstState().Next().Next().Next()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"dlcservice"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rootdev"), gomock.Eq([]string{"-s"})).Return(fmt.Sprintf("root%s", info.PartitionNumRootA), nil),
)
// Concurrent Portion
// Return verfied so install stops there
sam.EXPECT().PathExists(gomock.Any(), "/var/lib/dlcservice/dlc/1/dlc_a/verified").Return(true, nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Times(1).Return("", nil)
// Nothing should be run, so no need to use mock expect
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed provision-dlc state: %v", err)
}
}
func TestLaCrOSInstallStateTransitions(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := lacrosservice.NewLaCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
&lacrosservice.LaCrOSMetadata{Content: struct {
Version string "json:\"version\""
}{"v1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
gomock.InOrder(
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/lacros_compressed.squash")).Return("first.url", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{"mkdir", "-p", "/var/lib/imageloader/lacros/v1", "&&", "curl", "first.url", "--output", "/var/lib/imageloader/lacros/v1/image.squash"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stat"), gomock.Eq([]string{"-c%s", "/var/lib/imageloader/lacros/v1/image.squash"})).Return(" 100 ", nil),
// Ensure dd runs if value isn't multiple of 4096:
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("dd"), gomock.Eq([]string{"if=/dev/zero", "bs=1", "count=3996", "seek=100", "of=/var/lib/imageloader/lacros/v1/image.squash"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("verity"), gomock.Eq([]string{"mode=create", "alg=sha256", "payload=/var/lib/imageloader/lacros/v1/image.squash", "payload_blocks=1", "hashtree=/var/lib/imageloader/lacros/v1/hashtree", "salt=random", ">", "/var/lib/imageloader/lacros/v1/table"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("cat"), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1/hashtree", ">>", "/var/lib/imageloader/lacros/v1/image.squash"})).Return("", nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed install state: %v", err)
}
// POST INSTALL
st = st.Next()
json_data, err := json.MarshalIndent(struct {
ManifestVersion int `json:"manifest-version"`
FsType string `json:"fs-type"`
Version string `json:"version"`
ImageSha256Hash string `json:"image-sha256-hash"`
TableSha256Hash string `json:"table-sha256-hash"`
}{
ManifestVersion: 1,
FsType: "squashfs",
Version: "v1",
ImageSha256Hash: "image_hash",
TableSha256Hash: "table_hash",
}, "", " ")
if err != nil {
t.Fatalf("failed to marshal expected data, %v", err)
}
component_json_data, err := json.MarshalIndent(struct {
ManifestVersion int `json:"manifest-version"`
Name string `json:"name"`
Version string `json:"version"`
ImageName string `json:"imageName"`
Squash bool `json:"squash"`
FsType string `json:"fsType"`
IsRemovable bool `json:"isRemovable"`
}{
ManifestVersion: 2,
Name: "lacros",
Version: "v1",
ImageName: "image.squash",
Squash: true,
FsType: "squashfs",
IsRemovable: false,
}, "", " ")
if err != nil {
t.Fatalf("failed to marshal expected component data, %v", err)
}
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("sha256sum"), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1/image.squash", "|", "cut", "-d' '", "-f1"})).Return("image_hash", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("sha256sum"), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1/table", "|", "cut", "-d' '", "-f1"})).Return("table_hash", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), gomock.Eq([]string{"'" + string(json_data) + "'", ">", "/var/lib/imageloader/lacros/v1/imageloader.json"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), gomock.Eq([]string{"'" + string(component_json_data) + "'", ">", "/var/lib/imageloader/lacros/v1/manifest.json"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), gomock.Eq([]string{"'v1'", ">", "/var/lib/imageloader/lacros/latest-version"})).Return("", nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed post-install state: %v", err)
}
// VERIFY (Currently empty)
st = st.Next()
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed verify state: %v", err)
}
if st.Next() != nil {
t.Fatalf("verify should be the last step")
}
}
func TestLacrosAlignedDoesNotExtendAlignment(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
cs := lacrosservice.NewLaCrOSServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
&lacrosservice.LaCrOSMetadata{Content: struct {
Version string "json:\"version\""
}{"v1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
gomock.InOrder(
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/lacros_compressed.squash")).Return("first.url", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{"mkdir", "-p", "/var/lib/imageloader/lacros/v1", "&&", "curl", "first.url", "--output", "/var/lib/imageloader/lacros/v1/image.squash"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stat"), gomock.Eq([]string{"-c%s", "/var/lib/imageloader/lacros/v1/image.squash"})).Return(" 4096 ", nil),
// Ensure dd doesn't run if value is multiple of 4096:
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("verity"), gomock.Eq([]string{"mode=create", "alg=sha256", "payload=/var/lib/imageloader/lacros/v1/image.squash", "payload_blocks=1", "hashtree=/var/lib/imageloader/lacros/v1/hashtree", "salt=random", ">", "/var/lib/imageloader/lacros/v1/table"})).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("cat"), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1/hashtree", ">>", "/var/lib/imageloader/lacros/v1/image.squash"})).Return("", nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed install state: %v", err)
}
}
func TestAshInstallStateTransitions(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
as := ashservice.NewAshServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
)
ctx := context.Background()
// PREPARE
st := as.GetFirstState()
gomock.InOrder(
sam.EXPECT().DeleteDirectory(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy")).Return(nil),
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/tmp/_provisioning_service_chrome_deploy"})).Return(nil),
sam.EXPECT().CopyData(gomock.Any(), "path/to/image").Return("image_url", nil),
sam.EXPECT().RunCmd(gomock.Any(), "", []string{"curl", "image_url", "|", "tar", "--ignore-command-error", "--overwrite", "--preserve-permissions", "--directory=/tmp/_provisioning_service_chrome_deploy", "-xf", "-"}).Return("", nil),
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/opt/google/chrome", "/usr/local/autotest/deps/chrome_test/test_src/out/Release/", "/usr/local/libexec/chrome-binary-tests/"})).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), "stop", []string{"ui"}).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), "lsof", []string{"/opt/google/chrome/chrome"}).Return("", errors.New("chrome is in use!")),
sam.EXPECT().RunCmd(gomock.Any(), "pkill", []string{"'chrome|session_manager'"}).Return("", nil),
// Make first kill soft fail so we test retry:
sam.EXPECT().RunCmd(gomock.Any(), "lsof", []string{"/opt/google/chrome/chrome"}).Return("", errors.New("chrome is ***still*** in use!")),
sam.EXPECT().RunCmd(gomock.Any(), "pkill", []string{"'chrome|session_manager'"}).Return("", nil),
// Now we let it progress
sam.EXPECT().RunCmd(gomock.Any(), "lsof", []string{"/opt/google/chrome/chrome"}).Return("", nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed prepare state: %v", err)
}
// INSTALL
st = st.Next()
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "mount", []string{"-o", "remount,rw", "/"}).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/ash_shell")).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), "rsync", []string{"-av", "/tmp/_provisioning_service_chrome_deploy/ash_shell", "/opt/google/chrome"}).Return("", nil),
// For all items after we make them exist so we don't need to double every item (we assume that the test isn't breakable here):
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/aura_demo")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/chrome")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/chrome-wrapper")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/chrome.pak")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/chrome_100_percent.pak")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/chrome_200_percent.pak")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/content_shell")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/content_shell.pak")).Return(false, nil),
// Testing this one specifically as it should map to the designated folder rather than the top-most:
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/extensions/")).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), "rsync", []string{"-av", "/tmp/_provisioning_service_chrome_deploy/extensions/", "/opt/google/chrome/extensions"}).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/lib/*.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libffmpegsumo.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libpdf.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libppGoogleNaClPluginChrome.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libosmesa.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libwidevinecdmadapter.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/libwidevinecdm.so")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/locales/")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/nacl_helper_bootstrap")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/nacl_irt_*.nexe")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/nacl_helper")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/resources/")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/resources.pak")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/xdg-settings")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/*.png")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/*test")).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), "rsync", []string{"-av", "/tmp/_provisioning_service_chrome_deploy/*test", "/usr/local/autotest/deps/chrome_test/test_src/out/Release"}).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/*test")).Return(true, nil),
sam.EXPECT().RunCmd(gomock.Any(), "rsync", []string{"-av", "/tmp/_provisioning_service_chrome_deploy/*test", "/usr/local/libexec/chrome-binary-tests"}).Return("", nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/*tests")).Return(false, nil),
sam.EXPECT().PathExists(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy/*tests")).Return(false, nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed install state: %v", err)
}
// POST INSTALL
st = st.Next()
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "killall", []string{"-HUP", "dbus-daemon"}).Return("", nil),
sam.EXPECT().RunCmd(gomock.Any(), "start", []string{"ui"}).Return("", nil),
sam.EXPECT().DeleteDirectory(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy")).Return(nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed post-install state: %v", err)
}
if st.Next() != nil {
t.Fatalf("provision should be the last step")
}
}
func TestPkillOnlyRunsForTenSeconds(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
as := ashservice.NewAshServiceFromExistingConnection(
sam,
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image",
},
)
ctx := context.Background()
// PREPARE
st := as.GetFirstState()
gomock.InOrder(
sam.EXPECT().DeleteDirectory(gomock.Any(), gomock.Eq("/tmp/_provisioning_service_chrome_deploy")).Return(nil),
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/tmp/_provisioning_service_chrome_deploy"})).Return(nil),
sam.EXPECT().CopyData(gomock.Any(), "path/to/image").Return("image_url", nil),
sam.EXPECT().RunCmd(gomock.Any(), "", []string{"curl", "image_url", "|", "tar", "--ignore-command-error", "--overwrite", "--preserve-permissions", "--directory=/tmp/_provisioning_service_chrome_deploy", "-xf", "-"}).Return("", nil),
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/opt/google/chrome", "/usr/local/autotest/deps/chrome_test/test_src/out/Release/", "/usr/local/libexec/chrome-binary-tests/"})).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), "stop", []string{"ui"}).Return("", nil),
)
sam.EXPECT().RunCmd(gomock.Any(), "lsof", []string{"/opt/google/chrome/chrome"}).Return("", errors.New("chrome is in use!")).AnyTimes()
sam.EXPECT().RunCmd(gomock.Any(), "pkill", []string{"'chrome|session_manager'"}).Return("", nil).AnyTimes()
if err := st.Execute(ctx); err == nil {
t.Fatalf("prepare should've failed!")
}
}