blob: 77d13c607e0c7a6d3b87a7b4347a302c70a9dd64 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package provisionserver
import (
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/info"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/mock_services"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/services"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/firmwareservice"
"go.chromium.org/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"path/filepath"
"strings"
"testing"
"github.com/golang/mock/gomock"
conf "go.chromium.org/chromiumos/config/go"
build_api "go.chromium.org/chromiumos/config/go/build/api"
"go.chromium.org/chromiumos/config/go/test/api"
lab_api "go.chromium.org/chromiumos/config/go/test/lab/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",
},
nil,
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("touch"), gomock.Eq([]string{"/var/tmp/provision_failed"})).Return("", nil),
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),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_KERN.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot4 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot4 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_ROOT.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot5 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot5 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mktemp"), gomock.Eq([]string{"-d"})).Return("temporary_dir", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mount"), gomock.Eq([]string{"-o", "ro", "root_diskroot5", "temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("temporary_dir/postinst"), gomock.Eq([]string{"root_diskroot5"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("umount"), gomock.Eq([]string{"temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rmdir"), gomock.Eq([]string{"temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("cat"), gomock.Eq([]string{"/etc/lsb-release"})).Return("CHROMEOS_RELEASE_BOARD=(not_raven)", 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("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/.update_available", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new"})).Return("", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/stateful.tgz"), gomock.Eq("tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition --selinux -xzf -")).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), []string{"-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().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/var/cache/dlc/1/package/dlc_a"})).Return(nil)
sam.EXPECT().CopyData(gomock.Any(), gomock.Any(), gomock.Eq("/var/cache/dlc/1/package/dlc_a/dlc.img")).Return(nil)
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("start"), gomock.Eq([]string{"dlcservice"})).Times(1).Return("", nil)
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("chown"), gomock.Eq([]string{"-R", "dlcservice:dlcservice", "/var/cache/dlc"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("chmod"), gomock.Eq([]string{"-R", "0755", "/var/cache/dlc"})),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed provision-dlc state: %v", err)
}
//INSTALL MINIOS
st = st.Next()
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("cgpt"), gomock.Eq([]string{"show", "-t", "root_disk", "-i", "9"})).Return("not 09845860-705F-4BB5-B16C-8A8A099CAF52", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("cgpt"), gomock.Eq([]string{"show", "-t", "root_disk", "-i", "10"})).Return("not 09845860-705F-4BB5-B16C-8A8A099CAF52", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_MINIOS.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot9 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot9 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_MINIOS.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot10 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot10 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed install-minios state: %v", err)
}
if st.Next() != nil {
t.Fatalf("installminios 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",
},
nil,
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("touch"), gomock.Eq([]string{"/var/tmp/provision_failed"})).Return("", nil),
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),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_KERN.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot4 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot4 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_ROOT.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot5 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot5 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mktemp"), gomock.Eq([]string{"-d"})).Return("temporary_dir", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mount"), gomock.Eq([]string{"-o", "ro", "root_diskroot5", "temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("temporary_dir/postinst"), gomock.Eq([]string{"root_diskroot5"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("umount"), gomock.Eq([]string{"temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rmdir"), gomock.Eq([]string{"temporary_dir"})).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, failed to remove temporary directory, 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",
},
nil,
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
// Serial Portion
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("touch"), gomock.Eq([]string{"/var/tmp/provision_failed"})).Return("", nil),
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),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_KERN.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot4 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot4 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/full_dev_part_ROOT.bin.gz"),
gomock.Eq("gzip -d | dd of=root_diskroot5 obs=2M \npipestatus=(\"${PIPESTATUS[@]}\")\nif [[ \"${pipestatus[0]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Fetching path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[1]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Decompressing path/to/image failed.\" >&2\n exit 1\nelif [[ \"${pipestatus[2]}\" -ne 0 ]]; then\n echo \"$(date --rfc-3339=seconds) ERROR: Writing to root_diskroot5 failed.\" >&2\n exit 1\nfi")).Times(1).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mktemp"), gomock.Eq([]string{"-d"})).Return("temporary_dir", nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("mount"), gomock.Eq([]string{"-o", "ro", "root_diskroot5", "temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("temporary_dir/postinst"), gomock.Eq([]string{"root_diskroot5"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("umount"), gomock.Eq([]string{"temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("rmdir"), gomock.Eq([]string{"temporary_dir"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("cat"), gomock.Eq([]string{"/etc/lsb-release"})).Return("CHROMEOS_RELEASE_BOARD=(not_raven)", 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",
},
nil,
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().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/.update_available", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new"})).Return("", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/stateful.tgz"), gomock.Eq("tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition --selinux -xzf -")).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), []string{"-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",
},
nil,
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().RunCmd(gomock.Any(), gomock.Eq("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/.update_available", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new"})).Return("", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/stateful.tgz"), gomock.Eq("tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition --selinux -xzf -")).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, 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",
},
nil,
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",
},
nil,
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)
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("chown"), gomock.Eq([]string{"-R", "dlcservice:dlcservice", "/var/cache/dlc"})),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("chmod"), gomock.Eq([]string{"-R", "0755", "/var/cache/dlc"})),
)
// 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 TestPostInstallOverwriteWhenSpecified(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",
},
&conf.StoragePath{
HostType: conf.StoragePath_GS,
Path: "path/to/image/overwite.tar",
},
false,
[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
)
ctx := context.Background()
// Install -> PostInstall
st := cs.GetFirstState().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("rm"), gomock.Eq([]string{"-rf", "/mnt/stateful_partition/.update_available", "/mnt/stateful_partition/var_new", "/mnt/stateful_partition/dev_image_new"})).Return("", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("gs://path/to/image/stateful.tgz"), gomock.Eq("tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition --selinux -xzf -")).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), []string{"-n", "clobber", ">", "/mnt/stateful_partition/.update_available"}).Return("", nil),
sam.EXPECT().PipeData(gomock.Any(), gomock.Eq("path/to/image/overwite.tar"), gomock.Eq("tar xf - -C /")).Return(nil),
sam.EXPECT().Restart(gomock.Any()).Return(nil),
)
if err := st.Execute(ctx); err != nil {
t.Fatalf("failed post-install 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"}},
"",
"/var/lib/imageloader/lacros",
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
gomock.InOrder(
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1"})).Return(nil),
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/lacros_compressed.squash"), gomock.Eq("/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"}},
"",
"/var/lib/imageloader/lacros",
)
ctx := context.Background()
// INSTALL
st := cs.GetFirstState()
gomock.InOrder(
sam.EXPECT().CreateDirectories(gomock.Any(), gomock.Eq([]string{"/var/lib/imageloader/lacros/v1"})).Return(nil),
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/lacros_compressed.squash"), gomock.Eq("/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().PipeData(gomock.Any(), "path/to/image", gomock.Eq("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().PipeData(gomock.Any(), "path/to/image", gomock.Eq("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!")
}
}
func TestFirmwareProvisioningSSHStates(t *testing.T) {
fakeGSPath := "gs://test-archive.tar.gz"
fakeGSFilename := filepath.Base(fakeGSPath)
apImageWithinArchive := "image.bin"
ecImageWithinArchive := "ec.bin"
imagesWithinArchive := strings.Join([]string{
"foo",
apImageWithinArchive,
"bar",
ecImageWithinArchive,
}, "\n") // as reported by tar
makeRequest := func(main_rw, main_ro, ec_ro bool) *api.InstallFirmwareRequest {
fakePayload := &build_api.FirmwarePayload{FirmwareImage: &build_api.FirmwarePayload_FirmwareImagePath{FirmwareImagePath: &conf.StoragePath{HostType: conf.StoragePath_GS, Path: fakeGSPath}}}
FirmwareConfig := build_api.FirmwareConfig{}
if main_rw {
FirmwareConfig.MainRwPayload = fakePayload
}
if main_ro {
FirmwareConfig.MainRoPayload = fakePayload
}
if ec_ro {
FirmwareConfig.EcRoPayload = fakePayload
}
fmt.Printf("FirmwareConfig %#v \n", &FirmwareConfig)
return &api.InstallFirmwareRequest{FirmwareConfig: &FirmwareConfig}
}
fakeDutProto := &lab_api.Dut{
Id: &lab_api.Dut_Id{Value: "dee you tee"},
DutType: &lab_api.Dut_Chromeos{Chromeos: &lab_api.Dut_ChromeOS{
DutModel: &lab_api.DutModel{
BuildTarget: "test_board",
ModelName: "test_model",
},
}},
}
checkStateName := func(st services.ServiceState, expectedStateName string) {
if st == nil {
if len(expectedStateName) > 0 {
t.Fatalf("expected state %v. got: nil state", expectedStateName)
}
return
}
stateName := st.Name()
if stateName != expectedStateName {
t.Fatalf("expected state %v. got: %v", expectedStateName, stateName)
}
}
type TestCase struct {
// inputs
main_rw, main_ro, ec_ro bool
// expected outputs
updateRw, updateRo bool
expectConstructorError bool
}
testCases := []TestCase{
{ /*in*/ false, false, false /*out*/, false, false /*err*/, true},
{ /*in*/ true, false, false /*out*/, true, false /*err*/, false},
{ /*in*/ false, true, false /*out*/, false, true /*err*/, false},
{ /*in*/ false, false, true /*out*/, false, true /*err*/, false},
{ /*in*/ false, true, true /*out*/, false, true /*err*/, false},
{ /*in*/ true, true, true /*out*/, true, true /*err*/, false},
{ /*in*/ true, true, false /*out*/, true, true /*err*/, false},
}
// Set up the mock.
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sam := mock_services.NewMockServiceAdapterInterface(ctrl)
for _, testCase := range testCases {
// Create FirmwareService.
ctx := context.Background()
fws, err := firmwareservice.NewFirmwareServiceFromExistingConnection(
ctx,
fakeDutProto,
sam,
nil,
makeRequest(testCase.main_rw, testCase.main_ro, testCase.ec_ro),
)
// Check if init error is expected/got.
if err != nil {
if testCase.expectConstructorError {
continue
}
t.Fatalf("failed to create FirmwareService with test case %#v: %v", testCase, err)
}
if err == nil && testCase.expectConstructorError {
t.Fatalf("expected constructor error for test case %#v. got: %v", testCase, err)
}
// Check expected states.
if testCase.updateRo != fws.UpdateRo() {
t.Fatalf("test case %#v expects updateRo to be %v. got: %v.", testCase, testCase.updateRo, fws.UpdateRo())
}
if testCase.updateRw != fws.UpdateRw() {
t.Fatalf("test case %#v expects updateRw to be %v. got: %v.", testCase, testCase.updateRw, fws.UpdateRw())
}
// Start with the first state of the service.
st := fws.GetFirstState()
// Confirm state name is Prepare.
checkStateName(st, firmwareservice.PrepareStateName)
// Set mock expectations.
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "mktemp", gomock.Any()).Return("", nil),
sam.EXPECT().CopyData(gomock.Any(), gomock.Eq(fakeGSPath), gomock.Eq(fakeGSFilename)).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), "tar", gomock.Any()).Return(imagesWithinArchive, nil),
)
// Execute the state and proceed.
err = st.Execute(ctx)
if err != nil {
t.Fatal(err)
}
log.Printf("early state: %v\n", st.Name())
st = st.Next()
log.Printf("early state after Next(): %v\n", st.Name())
if testCase.updateRo {
// Confirm state name is RO.
checkStateName(st, firmwareservice.UpdateRoStateName)
// Set mock expectations.
expectedFutilityImageArgs := []string{}
if testCase.main_ro {
expectedFutilityImageArgs = append(expectedFutilityImageArgs, "--image="+apImageWithinArchive)
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "cd", gomock.Any()).Return("", nil), // tar
)
}
if testCase.ec_ro {
expectedFutilityImageArgs = append(expectedFutilityImageArgs, "--ec_image="+ecImageWithinArchive)
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "cd", gomock.Any()).Return("", nil), // tar
)
}
expectedFutilityArgs := append([]string{"update", "--mode=recovery"}, expectedFutilityImageArgs...)
expectedFutilityArgs = append(expectedFutilityArgs, "--wp=0")
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "futility", expectedFutilityArgs).Return("", nil),
)
// Execute the state and proceed.
err := st.Execute(ctx)
if err != nil {
t.Fatal(err)
}
st = st.Next()
}
if testCase.updateRw {
// Confirm state name is RW.
checkStateName(st, firmwareservice.UpdateRwStateName)
// Set mock expectations.
expectedFutilityArgs := []string{"update", "--mode=recovery", "--image=" + apImageWithinArchive, "--wp=1"}
gomock.InOrder(
sam.EXPECT().RunCmd(gomock.Any(), "cd", gomock.Any()).Return("", nil), // tar
sam.EXPECT().RunCmd(gomock.Any(), "futility", expectedFutilityArgs).Return("", nil),
)
// Execute the state and proceed.
err := st.Execute(ctx)
if err != nil {
t.Fatal(err)
}
st = st.Next()
}
// Confirm state name is postinstall.
checkStateName(st, firmwareservice.PostInstallStateName)
// Set mock expectations.
gomock.InOrder(
sam.EXPECT().DeleteDirectory(gomock.Any(), "").Return(nil),
sam.EXPECT().Restart(gomock.Any()).Return(nil),
sam.EXPECT().RunCmd(gomock.Any(), "true", nil).Return("", nil),
)
// Execute the state and proceed.
err = st.Execute(ctx)
if err != nil {
t.Fatal(err)
}
st = st.Next()
// Confirm no states left.
checkStateName(st, "")
}
}