Skip to content

Commit

Permalink
Install to the current boot device when CoreosImage is set
Browse files Browse the repository at this point in the history
This indicates the container image that should be installed and booted
for the installed host. It also indicates that we should install to the
currently booted disk, in a new stateroot, rather than expecting a
device path.

https://issues.redhat.com/browse/MGMT-19100
  • Loading branch information
carbonin committed Jan 10, 2025
1 parent 34bae2a commit 928a493
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 37 deletions.
2 changes: 2 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Config struct {
SkipInstallationDiskCleanup bool
EnableSkipMcoReboot bool
NotifyNumReboots bool
CoreosImage string
}

func printHelpAndExit(err error) {
Expand Down Expand Up @@ -79,6 +80,7 @@ func (c *Config) ProcessArgs(args []string) {
flagSet.BoolVar(&c.SkipInstallationDiskCleanup, "skip-installation-disk-cleanup", false, "Skip installation disk cleanup gives disk management to coreos-installer in case needed")
flagSet.BoolVar(&c.EnableSkipMcoReboot, "enable-skip-mco-reboot", false, "indicate assisted installer to generate settings to match MCO requirements for skipping reboot after firstboot")
flagSet.BoolVar(&c.NotifyNumReboots, "notify-num-reboots", false, "indicate number of reboots should be notified as event")
flagSet.StringVar(&c.CoreosImage, "coreos-image", "", "CoreOS image to install to the local disk")

var installerArgs string
flagSet.StringVar(&installerArgs, "installer-args", "", "JSON array of additional coreos-installer arguments")
Expand Down
6 changes: 6 additions & 0 deletions src/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ var _ = Describe("ProcessArgs", func() {
Expect(config.InfraEnvID).To(Equal("9f2a26d7-10a6-4be0-b1c2-e895ad3b04b8"))
})

It("should set coreos image when provided", func() {
config := &Config{}
arguments := []string{"--role", string(models.HostRoleBootstrap), "--infra-env-id", "9f2a26d7-10a6-4be0-b1c2-e895ad3b04b8", "--cluster-id", "0ae63135-5f7c-431e-9c72-0efaf2cb83b8", "--coreos-image", "example.com/coreos/os:latest"}
config.ProcessArgs(arguments)
Expect(config.CoreosImage).To(Equal("example.com/coreos/os:latest"))
})
})

var _ = Describe("SetInstallerArgs", func() {
Expand Down
31 changes: 21 additions & 10 deletions src/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,17 @@ func (i *installer) InstallNode() error {
}
}

if i.EnableSkipMcoReboot {
i.skipMcoReboot(ignitionPath)
}
// we can't do any of this if we're installing to the current boot device
if i.Config.CoreosImage == "" {
if i.EnableSkipMcoReboot {
i.skipMcoReboot(ignitionPath)
}

if err = i.ops.SetBootOrder(i.Device); err != nil {
i.log.WithError(err).Warnf("Failed to set boot order")
// Ignore the error for now so it doesn't fail the installation in case it fails
//return err
if err = i.ops.SetBootOrder(i.Device); err != nil {
i.log.WithError(err).Warnf("Failed to set boot order")
// Ignore the error for now so it doesn't fail the installation in case it fails
//return err
}
}

if isBootstrap {
Expand Down Expand Up @@ -318,7 +321,7 @@ func convertToOverwriteKargs(args []string) []string {
}

func (i *installer) cleanupInstallDevice() {
if i.DryRunEnabled || i.Config.SkipInstallationDiskCleanup {
if i.DryRunEnabled || i.Config.SkipInstallationDiskCleanup || i.Config.CoreosImage != "" {
i.log.Infof("skipping installation disk cleanup")
} else {
err := i.cleanupDevice.CleanupInstallDevice(i.Config.Device)
Expand Down Expand Up @@ -362,7 +365,11 @@ func (i *installer) writeImageToDisk(ignitionPath string) error {
interval := time.Second
liveLogger := coreos_logger.NewCoreosInstallerLogWriter(i.log, i.inventoryClient, i.Config.InfraEnvID, i.Config.HostID)
err := utils.Retry(3, interval, i.log, func() error {
return i.ops.WriteImageToDisk(liveLogger, ignitionPath, i.Device, i.Config.InstallerArgs)
if i.Config.CoreosImage == "" {
return i.ops.WriteImageToDisk(liveLogger, ignitionPath, i.Device, i.Config.InstallerArgs)
} else {
return i.ops.WriteImageToLocalDevice(liveLogger, ignitionPath)
}
})
if err != nil {
i.log.WithError(err).Error("Failed to write image to disk")
Expand Down Expand Up @@ -988,7 +995,11 @@ func RunInstaller(installerConfig *config.Config, logger *logrus.Logger) error {
)

// Try to format requested disks. May fail formatting some disks, this is not an error.
ai.FormatDisks()
if installerConfig.CoreosImage == "" {
ai.FormatDisks()
} else {
logger.Info("not attempting disk format when installing to boot device")
}

if err = ai.InstallNode(); err != nil {
ai.UpdateHostInstallProgress(models.HostStageFailed, err.Error())
Expand Down
81 changes: 54 additions & 27 deletions src/installer/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
mockops.EXPECT().WriteImageToDisk(gomock.Any(), filepath.Join(InstallDir, "master-host-id.ign"), device, extra).Return(nil).Times(1)
}

setBootOrderSuccess := func(extra interface{}) {
setBootOrderSuccess := func() {
mockops.EXPECT().SetBootOrder(device).Return(nil).Times(1)
}

Expand Down Expand Up @@ -366,7 +366,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
reportLogProgressSuccess()
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
ironicAgentDoesntExist()
rebootSuccess()
Expand Down Expand Up @@ -399,7 +399,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
reportLogProgressSuccess()
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
ironicAgentDoesntExist()
rebootSuccess()
Expand Down Expand Up @@ -431,7 +431,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
//HostRoleMaster flow:
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand Down Expand Up @@ -471,7 +471,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
reportLogProgressSuccess()
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
ironicAgentDoesntExist()
rebootSuccess()
Expand Down Expand Up @@ -501,7 +501,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
//HostRoleMaster flow:
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
getEncapsulatedMcSuccess(nil)
overwriteImageSuccess()
ret := installerObj.InstallNode()
Expand Down Expand Up @@ -531,7 +531,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
//HostRoleMaster flow:
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand All @@ -553,7 +553,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
downloadFileSuccess(bootstrapIgn)
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
extractSecretFromIgnitionSuccess()
getEncapsulatedMcSuccess(nil)
overwriteImageSuccess()
Expand All @@ -577,7 +577,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
//HostRoleMaster flow:
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
getEncapsulatedMcSuccess(nil)
overwriteImageSuccess()
ret := installerObj.InstallNode()
Expand Down Expand Up @@ -663,7 +663,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
//HostRoleMaster flow:
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(gomock.Any())
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand Down Expand Up @@ -762,18 +762,23 @@ var _ = Describe("installer HostRoleMaster role", func() {
})
})
Context("Master role", func() {
installerArgs := []string{"-n", "--append-karg", "nameserver=8.8.8.8"}
conf := config.Config{Role: string(models.HostRoleMaster),
ClusterID: "cluster-id",
InfraEnvID: "infra-env-id",
HostID: "host-id",
Device: "/dev/vda",
URL: "https://assisted-service.com:80",
OpenshiftVersion: openShiftVersion,
InstallerArgs: installerArgs,
EnableSkipMcoReboot: withEnableSkipMcoReboot,
}
var (
conf config.Config
installerArgs []string
)

BeforeEach(func() {
installerArgs = []string{"-n", "--append-karg", "nameserver=8.8.8.8"}
conf = config.Config{Role: string(models.HostRoleMaster),
ClusterID: "cluster-id",
InfraEnvID: "infra-env-id",
HostID: "host-id",
Device: "/dev/vda",
URL: "https://assisted-service.com:80",
OpenshiftVersion: openShiftVersion,
InstallerArgs: installerArgs,
EnableSkipMcoReboot: withEnableSkipMcoReboot,
}
installerObj = NewAssistedInstaller(l, conf, mockops, mockbmclient, k8sBuilder, mockIgnition, cleanupDevice)
evaluateDiskSymlinkSuccess()

Expand All @@ -788,7 +793,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
mkdirSuccess(InstallDir)
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(installerArgs)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(false)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand All @@ -799,6 +804,28 @@ var _ = Describe("installer HostRoleMaster role", func() {
Expect(ret).Should(BeNil())
})

It("installs locally and does not skip reboot, clean install device, or set boot order when coreosImage is set", func() {
installerObj.Config.CoreosImage = "example.com/coreos/os:latest"
updateProgressSuccess([][]string{{string(models.HostStageStartingInstallation), conf.Role},
{string(models.HostStageInstalling), conf.Role},
{string(models.HostStageWritingImageToDisk)},
{string(models.HostStageRebooting)},
})
cleanupDevice.EXPECT().CleanupInstallDevice(device).Times(0)
mkdirSuccess(InstallDir)
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
mockops.EXPECT().WriteImageToLocalDevice(gomock.Any(), filepath.Join(InstallDir, "master-host-id.ign")).Return(nil).Times(1)
mockops.EXPECT().SetBootOrder(device).Times(0)
uploadLogsSuccess(false)
reportLogProgressSuccess()
ironicAgentDoesntExist()
rebootSuccess()
mockops.EXPECT().GetEncapsulatedMC(gomock.Any()).Times(0)
mockops.EXPECT().OverwriteOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
ret := installerObj.InstallNode()
Expect(ret).Should(BeNil())
})

It("HostRoleMaster role happy flow with skipping disk cleanup", func() {
installerObj.Config.SkipInstallationDiskCleanup = true
cleanupDevice.EXPECT().CleanupInstallDevice(gomock.Any()).Return(nil).Times(0)
Expand All @@ -811,7 +838,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
mkdirSuccess(InstallDir)
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(installerArgs)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(false)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand Down Expand Up @@ -878,7 +905,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
mkdirSuccess(InstallDir)
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
writeToDiskSuccess(installerArgs)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(false)
reportLogProgressSuccess()
getEncapsulatedMcSuccess(nil)
Expand Down Expand Up @@ -933,7 +960,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
uploadLogsSuccess(false)
reportLogProgressSuccess()
writeToDiskSuccess(installerArgs)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
getEncapsulatedMcSuccess(nil)
overwriteImageSuccess()
ironicAgentDoesntExist()
Expand Down Expand Up @@ -985,7 +1012,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
mkdirSuccess(InstallDir)
downloadHostIgnitionSuccess(infraEnvId, hostId, "worker-host-id.ign")
mockops.EXPECT().WriteImageToDisk(gomock.Any(), filepath.Join(InstallDir, "worker-host-id.ign"), device, nil).Return(nil).Times(1)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
// failure must do nothing
reportLogProgressSuccess()
mockops.EXPECT().UploadInstallationLogs(false).Return("", errors.Errorf("Dummy")).Times(1)
Expand Down Expand Up @@ -1087,7 +1114,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
singleNodeMergeIgnitionSuccess()
downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign")
mockops.EXPECT().WriteImageToDisk(gomock.Any(), singleNodeMasterIgnitionPath, device, nil).Return(nil).Times(1)
setBootOrderSuccess(gomock.Any())
setBootOrderSuccess()
uploadLogsSuccess(true)
reportLogProgressSuccess()
ironicAgentDoesntExist()
Expand Down
14 changes: 14 additions & 0 deletions src/ops/mock_ops.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions src/ops/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
type Ops interface {
Mkdir(dirName string) error
WriteImageToDisk(liveLogger io.Writer, ignitionPath string, device string, extraArgs []string) error
WriteImageToLocalDevice(liveLogger io.Writer, ignitionPath string) error
Reboot(delay string) error
SetBootOrder(device string) error
ExtractFromIgnition(ignitionPath string, fileToExtract string) error
Expand Down Expand Up @@ -124,6 +125,71 @@ func (o *ops) SystemctlAction(action string, args ...string) error {
return errors.Wrapf(err, "Failed executing systemctl %s %s", action, args)
}

var ostreeOutputRegex = regexp.MustCompile(`Imported: (\w+)`)

func (o *ops) importOSTreeCommit(liveLogger io.Writer) (string, error) {
ostreeReleasePullSpec := fmt.Sprintf("ostree-unverified-registry:%s", o.installerConfig.CoreosImage)
out, err := o.ExecPrivilegeCommand(liveLogger, "ostree", "container", "unencapsulate", "--authfile", "/root/.docker/config.json", "--quiet", "--repo", "/ostree/repo", ostreeReleasePullSpec)
if err != nil {
return "", errors.Wrapf(err, "failed to unencapsulate rhcos payload image: %s", out)
}

matches := ostreeOutputRegex.FindStringSubmatch(out)
if matches == nil {
return "", fmt.Errorf("got unexpected output from unencapsulate: \"%s\"", out)
}

return matches[1], nil
}

func (o *ops) WriteImageToLocalDevice(liveLogger io.Writer, ignitionPath string) error {
out, err := o.ExecPrivilegeCommand(liveLogger, "mount", "/sysroot", "-o", "remount,rw")
if err != nil {
return errors.Wrapf(err, "failed to remount sysroot: %s", out)
}
out, err = o.ExecPrivilegeCommand(liveLogger, "mount", "/boot", "-o", "remount,rw")
if err != nil {
return errors.Wrapf(err, "failed to remount boot: %s", out)
}

out, err = o.ExecPrivilegeCommand(liveLogger, "ostree", "admin", "stateroot-init", "install")
if err != nil {
return errors.Wrapf(err, "failed creating new stateroot: %s", out)
}

commit, err := o.importOSTreeCommit(liveLogger)
if err != nil {
return err
}
o.log.Infof("imported commit %s", commit)

out, err = o.ExecPrivilegeCommand(liveLogger, "ostree", "admin", "deploy",
"--stateroot", "install",
"--karg", "$ignition_firstboot",
"--karg", defaultIgnitionPlatformId,
commit)
if err != nil {
return errors.Wrapf(err, "failed to deploy commit to stateroot: %s", out)
}

out, err = o.ExecPrivilegeCommand(liveLogger, "mkdir", "/boot/ignition")
if err != nil {
return errors.Wrapf(err, "failed to create ignition directory: %s", out)
}

out, err = o.ExecPrivilegeCommand(liveLogger, "cp", ignitionPath, "/boot/ignition/config.ign")
if err != nil {
return errors.Wrapf(err, "failed to copy ignition file: %s", out)
}

out, err = o.ExecPrivilegeCommand(liveLogger, "touch", "/boot/ignition.firstboot")
if err != nil {
return errors.Wrapf(err, "failed to write ignition marker file: %s", out)
}

return nil
}

func (o *ops) WriteImageToDisk(liveLogger io.Writer, ignitionPath string, device string, extraArgs []string) error {
allArgs := installerArgs(ignitionPath, device, extraArgs)
o.log.Infof("Writing image and ignition to disk with arguments: %v", allArgs)
Expand Down
Loading

0 comments on commit 928a493

Please sign in to comment.