Skip to content

Commit

Permalink
use extension container repo to upgrade rpm-ostree
Browse files Browse the repository at this point in the history
  • Loading branch information
cheesesashimi committed Feb 14, 2023
1 parent 517097d commit 5d97232
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 4 deletions.
14 changes: 13 additions & 1 deletion pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ import (
mcfglistersv1 "github.com/openshift/machine-config-operator/pkg/generated/listers/machineconfiguration.openshift.io/v1"
)

// Make this an interface so that we can easily inject mock versions of this.
type osRelease interface {
IsEL() bool
IsEL9() bool
IsCoreOSVariant() bool
IsFCOS() bool
IsSCOS() bool
IsLikeTraditionalRHEL7() bool
}

var _ osRelease = osrelease.OperatingSystem{}

// Daemon is the dispatch point for the functions of the agent on the
// machine. it keeps track of connections and the current state of the update
// process.
Expand All @@ -50,7 +62,7 @@ type Daemon struct {
name string

// os the operating system the MCD is running on
os osrelease.OperatingSystem
os osRelease

// mock is set if we're running as non-root, probably under unit tests
mock bool
Expand Down
88 changes: 87 additions & 1 deletion pkg/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"time"

"github.com/clarketm/json"
"github.com/containers/image/v5/types"
ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
"github.com/golang/glog"
corev1 "k8s.io/api/core/v1"
Expand All @@ -27,6 +28,7 @@ import (
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
"github.com/openshift/machine-config-operator/pkg/daemon/constants"
"github.com/openshift/machine-config-operator/pkg/daemon/osrelease"
pivottypes "github.com/openshift/machine-config-operator/pkg/daemon/pivot/types"
pivotutils "github.com/openshift/machine-config-operator/pkg/daemon/pivot/utils"
)
Expand Down Expand Up @@ -1644,9 +1646,86 @@ func (dn *Daemon) updateSSHKeys(newUsers []ign3types.PasswdUser) error {
return nil
}

// Gets the labels attached to the OS image and uses that to infer the OS version.
func (dn *Daemon) getNewOSVersion(imgURL string) (osr osrelease.OperatingSystem, err error) {
glog.Infof("Looking up image labels for new OS %q to infer new OS version", imgURL)

// The image inspection code is copy / pasted from the Rebase() method in rpm-ostree.go.
var imageData *types.ImageInspectInfo
if imageData, _, err = imageInspect(imgURL); err != nil {
if err != nil {
var podmanImgData *imageInspection
glog.Infof("Falling back to using podman inspect")
if podmanImgData, err = podmanInspect(imgURL); err != nil {
return
}
defer exec.Command("podman", "rmi", imgURL).Run()

osr, err = osrelease.InferFromOSImageLabels(podmanImgData.Labels)
return
}
}

osr, err = osrelease.InferFromOSImageLabels(imageData.Labels)
return
}

// Returns true if we're updating from RHCOS8 -> RHCOS9. Returns false for all
// other cases.
func (dn *Daemon) isRHCOS9Upgrade(newImageOS osRelease) bool {
return dn.os.IsEL() && !dn.os.IsEL9() && newImageOS.IsEL() && newImageOS.IsEL9()
}

// Prepares for an upgrade from RHCOS 8 to RHCOS 9 by doing the following:
// - Installing a newer version of rpm-ostree from the extensions container.
// - Restarting rpm-ostree.service.
//
// Assumes:
// - We've called ExtractExtensionsImage
// - We've called addExtensionsRepo or addLayeredExtensionsRepo.
func (dn *Daemon) prepForRHCOS9(newImageURL string) error {
// Gets the image labels for the new OS image and infers what the OS version
// is based upon their contents.
newOSInfo, err := dn.getNewOSVersion(newImageURL)
if err != nil {
return fmt.Errorf("could not get new OS version info: %w", err)
}

// If we don't have an RHCOS 8 -> RHCOS 9 upgrade, return early.
if !dn.isRHCOS9Upgrade(newOSInfo) {
return nil
}

glog.Infof("Detected RHCOS 8 -> RHCOS 9 upgrade, upgrading rpm-ostree first")

// Ensures that the extensions repo is available first.
if _, err := os.Stat(extensionsRepo); err != nil {
return fmt.Errorf("extensions repo not found: %w", err)
}

// Upgrades rpm-ostree.
// TODO(zzlotnik): Is this the right command to upgrade rpm-ostree?
if _, err := runGetOut("rpm", "-Uvh", "rpm-ostree"); err != nil {
return fmt.Errorf("could not install updated rpm-ostree: %w", err)
}

// Restarts the rpm-ostreed service.
glog.Info("Restarting rpm-ostree service")
if _, err := runGetOut("systemd", "restart", "rpm-ostreed.service"); err != nil {
return fmt.Errorf("could not restart rpm-ostreed: %w", err)
}

return nil
}

// updateOS updates the system OS to the one specified in newConfig
func (dn *Daemon) updateOS(config *mcfgv1.MachineConfig, osImageContentDir string) error {
newURL := config.Spec.OSImageURL

if err := dn.prepForRHCOS9(newURL); err != nil {
return fmt.Errorf("could not prepare for RHCOS 9 upgrade: %w", err)
}

glog.Infof("Updating OS to %s", newURL)
if _, err := dn.NodeUpdaterClient.Rebase(newURL, osImageContentDir); err != nil {
return fmt.Errorf("failed to update OS to %s : %w", newURL, err)
Expand Down Expand Up @@ -1719,6 +1798,7 @@ func (dn *Daemon) updateLayeredOS(config *mcfgv1.MachineConfig) error {
if err != nil {
return err
}

// If the host isn't new enough to understand the new container model natively, run as a privileged container.
// See https://github.com/coreos/rpm-ostree/pull/3961 and https://issues.redhat.com/browse/MCO-356
// This currently will incur a double reboot; see https://github.com/coreos/rpm-ostree/issues/4018
Expand All @@ -1727,7 +1807,13 @@ func (dn *Daemon) updateLayeredOS(config *mcfgv1.MachineConfig) error {
if err := dn.InplaceUpdateViaNewContainer(newURL); err != nil {
return err
}
} else if err := dn.NodeUpdaterClient.RebaseLayered(newURL); err != nil {
}

if err := dn.prepForRHCOS9(newURL); err != nil {
return fmt.Errorf("could not prepare for RHCOS 9 upgrade: %w", err)
}

if err := dn.NodeUpdaterClient.RebaseLayered(newURL); err != nil {
return fmt.Errorf("failed to update OS to %s : %w", newURL, err)
}

Expand Down
122 changes: 120 additions & 2 deletions pkg/daemon/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
"github.com/openshift/machine-config-operator/pkg/daemon/osrelease"
"github.com/openshift/machine-config-operator/test/helpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -25,12 +24,79 @@ import (
k8sfake "k8s.io/client-go/kubernetes/fake"
)

type fakeOS struct {
isEL bool
isEL9 bool
isFCOS bool
isSCOS bool
isCoreOSVariant bool
isLikeTraditionalRHEL7 bool
}

func (f fakeOS) IsEL() bool {
return f.isEL
}

func (f fakeOS) IsEL9() bool {
return f.isEL9
}

func (f fakeOS) IsFCOS() bool {
return f.isFCOS
}

func (f fakeOS) IsSCOS() bool {
return f.isSCOS
}

func (f fakeOS) IsCoreOSVariant() bool {
return f.isCoreOSVariant
}

func (f fakeOS) IsLikeTraditionalRHEL7() bool {
return f.isLikeTraditionalRHEL7
}

func rhcos8() fakeOS {
return fakeOS{
isEL: true,
isEL9: false,
isCoreOSVariant: true,
}
}

func rhcos9() fakeOS {
return fakeOS{
isEL: true,
isEL9: true,
isCoreOSVariant: true,
}
}

func fcos() fakeOS {
return fakeOS{
isFCOS: true,
isCoreOSVariant: true,
}
}

func scos() fakeOS {
return fakeOS{
isEL: true,
isEL9: true,
isSCOS: true,
isCoreOSVariant: true,
}
}

var _ osRelease = fakeOS{}

func newMockDaemon() Daemon {
// Create a Daemon instance with mocked clients
return Daemon{
mock: true,
name: "nodeName",
os: osrelease.OperatingSystem{},
os: fakeOS{},
kubeClient: k8sfake.NewSimpleClientset(),
bootedOSImageURL: "test",
}
Expand Down Expand Up @@ -752,3 +818,55 @@ func TestOriginalFileBackupRestore(t *testing.T) {
assert.Nil(t, err)

}

func TestRHCOS8to9Update(t *testing.T) {
testCases := []struct {
name string
nodeOS osRelease
imageOS osRelease
isRHCOS9Upgrade bool
}{
{
name: "With FCOS base",
nodeOS: fcos(),
imageOS: fcos(),
isRHCOS9Upgrade: false,
},
{
name: "With SCOS base",
nodeOS: scos(),
imageOS: scos(),
isRHCOS9Upgrade: false,
},
{
name: "With RHCOS8 base",
nodeOS: rhcos8(),
imageOS: rhcos9(),
isRHCOS9Upgrade: true,
},
{
name: "With RHCOS8 base to RHCOS8 new OS",
nodeOS: rhcos8(),
imageOS: rhcos8(),
isRHCOS9Upgrade: false,
},
{
name: "With RHCOS9 base",
nodeOS: rhcos9(),
imageOS: rhcos9(),
isRHCOS9Upgrade: false,
},
}

for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()

d := newMockDaemon()
d.os = testCase.nodeOS

assert.Equal(t, d.isRHCOS9Upgrade(testCase.imageOS), testCase.isRHCOS9Upgrade)
})
}
}

0 comments on commit 5d97232

Please sign in to comment.