diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 4440590ec8..f3ee9fe8ea 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -37,6 +37,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" mcfginformersv1 "github.com/openshift/machine-config-operator/pkg/generated/informers/externalversions/machineconfiguration.openshift.io/v1" mcfglistersv1 "github.com/openshift/machine-config-operator/pkg/generated/listers/machineconfiguration.openshift.io/v1" ) @@ -49,7 +50,7 @@ type Daemon struct { name string // os the operating system the MCD is running on - os OperatingSystem + os osrelease.OperatingSystem // mock is set if we're running as non-root, probably under unit tests mock bool @@ -219,9 +220,9 @@ func New( err error ) - hostos := OperatingSystem{} + hostos := osrelease.OperatingSystem{} if !mock { - hostos, err = GetHostRunningOS() + hostos, err = osrelease.GetHostRunningOS() if err != nil { hostOS.WithLabelValues("unsupported", "").Set(1) return nil, fmt.Errorf("checking operating system: %w", err) diff --git a/pkg/daemon/kernelargs.go b/pkg/daemon/kernelargs.go index 8e3a5e95eb..4928e098e3 100644 --- a/pkg/daemon/kernelargs.go +++ b/pkg/daemon/kernelargs.go @@ -11,6 +11,7 @@ import ( _ "crypto/sha256" "github.com/golang/glog" + "github.com/openshift/machine-config-operator/pkg/daemon/osrelease" "github.com/openshift/machine-config-operator/pkg/daemon/pivot/types" ) @@ -29,7 +30,7 @@ var tuneableRHCOSArgsAllowlist = map[string]bool{ // isArgTuneable returns if the argument provided is allowed to be modified func isArgTunable(arg string) (bool, error) { - os, err := GetHostRunningOS() + os, err := osrelease.GetHostRunningOS() if err != nil { return false, fmt.Errorf("failed to get OS for determining whether kernel arg is tuneable: %w", err) } diff --git a/pkg/daemon/osrelease.go b/pkg/daemon/osrelease.go deleted file mode 100644 index 27dcd6f600..0000000000 --- a/pkg/daemon/osrelease.go +++ /dev/null @@ -1,81 +0,0 @@ -package daemon - -import ( - "strings" - - "github.com/ashcrow/osrelease" -) - -// OperatingSystem is a wrapper around a subset of the os-release fields -// and also tracks whether ostree is in use. -type OperatingSystem struct { - // ID is the ID field from the os-release - ID string - // VariantID is the VARIANT_ID field from the os-release - VariantID string - // VersionID is the VERSION_ID field from the os-release - VersionID string -} - -// IsEL is true if the OS is an Enterprise Linux variant, -// i.e. RHEL CoreOS (RHCOS) or CentOS Stream CoreOS (SCOS) -func (os OperatingSystem) IsEL() bool { - return os.ID == "rhcos" || os.ID == "scos" -} - -// IsEL9 is true if the OS is RHCOS 9 or SCOS 9 -func (os OperatingSystem) IsEL9() bool { - return os.IsEL() && os.VersionID == "9" -} - -// IsFCOS is true if the OS is Fedora CoreOS -func (os OperatingSystem) IsFCOS() bool { - return os.ID == "fedora" && os.VariantID == "coreos" -} - -// IsSCOS is true if the OS is SCOS -func (os OperatingSystem) IsSCOS() bool { - return os.ID == "scos" -} - -// IsCoreOSVariant is true if the OS is FCOS or a derivative (ostree+Ignition) -// which includes SCOS and RHCOS. -func (os OperatingSystem) IsCoreOSVariant() bool { - // In RHCOS8 the variant id is not specified. SCOS (future RHCOS9) and FCOS have VARIANT_ID=coreos. - return os.VariantID == "coreos" || os.IsEL() -} - -// IsLikeTraditionalRHEL7 is true if the OS is traditional RHEL7 or CentOS7: -// yum based + kickstart/cloud-init (not Ignition). -func (os OperatingSystem) IsLikeTraditionalRHEL7() bool { - // Today nothing else is going to show up with a version ID of 7 - if len(os.VersionID) > 2 { - return strings.HasPrefix(os.VersionID, "7.") - } - return os.VersionID == "7" -} - -// ToPrometheusLabel returns a value we historically fed to Prometheus -func (os OperatingSystem) ToPrometheusLabel() string { - // We historically upper cased this - return strings.ToUpper(os.ID) -} - -// GetHostRunningOS reads os-release to generate the OperatingSystem data. -func GetHostRunningOS() (OperatingSystem, error) { - libPath := "/usr/lib/os-release" - etcPath := "/etc/os-release" - - ret := OperatingSystem{} - - or, err := osrelease.NewWithOverrides(etcPath, libPath) - if err != nil { - return ret, err - } - - ret.ID = or.ID - ret.VariantID = or.VARIANT_ID - ret.VersionID = or.VERSION_ID - - return ret, nil -} diff --git a/pkg/daemon/osrelease/osrelease.go b/pkg/daemon/osrelease/osrelease.go new file mode 100644 index 0000000000..cef17a7321 --- /dev/null +++ b/pkg/daemon/osrelease/osrelease.go @@ -0,0 +1,151 @@ +package osrelease + +import ( + "os" + "path/filepath" + "strings" + + "github.com/ashcrow/osrelease" +) + +// OS Release Paths +const ( + EtcOSReleasePath string = "/etc/os-release" + LibOSReleasePath string = "/usr/lib/os-release" +) + +// OS IDs +const ( + coreos string = "coreos" + fedora string = "fedora" + rhcos string = "rhcos" + scos string = "scos" +) + +// OperatingSystem is a wrapper around a subset of the os-release fields +// and also tracks whether ostree is in use. +type OperatingSystem struct { + // id is the ID field from the os-release + id string + // variantID is the VARIANT_ID field from the os-release + variantID string + // version is the VERSION, RHEL_VERSION, or VERSION_ID field from the os-release + version string + // osrelease is the underlying struct from github.com/ashcrow/osrelease + osrelease osrelease.OSRelease +} + +func newOperatingSystem(etcPath, libPath string) (OperatingSystem, error) { + ret := OperatingSystem{} + + or, err := osrelease.NewWithOverrides(etcPath, libPath) + if err != nil { + return ret, err + } + + ret.id = or.ID + ret.variantID = or.VARIANT_ID + ret.version = getOSVersion(or) + ret.osrelease = or + + return ret, nil +} + +// Returns the underlying OSRelease struct if additional parameters are needed. +func (os OperatingSystem) OSRelease() osrelease.OSRelease { + return os.osrelease +} + +// IsEL is true if the OS is an Enterprise Linux variant, +// i.e. RHEL CoreOS (RHCOS) or CentOS Stream CoreOS (SCOS) +func (os OperatingSystem) IsEL() bool { + return os.id == rhcos || os.id == scos +} + +// IsEL9 is true if the OS is RHCOS 9 or SCOS 9 +func (os OperatingSystem) IsEL9() bool { + return os.IsEL() && strings.HasPrefix(os.version, "9.") || os.version == "9" +} + +// IsFCOS is true if the OS is Fedora CoreOS +func (os OperatingSystem) IsFCOS() bool { + return os.id == fedora && os.variantID == coreos +} + +// IsSCOS is true if the OS is SCOS +func (os OperatingSystem) IsSCOS() bool { + return os.id == scos +} + +// IsCoreOSVariant is true if the OS is FCOS or a derivative (ostree+Ignition) +// which includes SCOS and RHCOS. +func (os OperatingSystem) IsCoreOSVariant() bool { + // In RHCOS8 the variant id is not specified. SCOS (future RHCOS9) and FCOS have VARIANT_ID=coreos. + return os.variantID == coreos || os.IsEL() +} + +// IsLikeTraditionalRHEL7 is true if the OS is traditional RHEL7 or CentOS7: +// yum based + kickstart/cloud-init (not Ignition). +func (os OperatingSystem) IsLikeTraditionalRHEL7() bool { + // Today nothing else is going to show up with a version ID of 7 + if len(os.version) > 2 { + return strings.HasPrefix(os.version, "7.") + } + return os.version == "7" +} + +// ToPrometheusLabel returns a value we historically fed to Prometheus +func (os OperatingSystem) ToPrometheusLabel() string { + // We historically upper cased this + return strings.ToUpper(os.id) +} + +// GetHostRunningOS reads os-release to generate the OperatingSystem data. +func GetHostRunningOS() (OperatingSystem, error) { + return newOperatingSystem(EtcOSReleasePath, LibOSReleasePath) +} + +// Generates the OperatingSystem data from strings which contain the desired +// content. Mostly useful for testing purposes. +func LoadOSRelease(etcOSReleaseContent, libOSReleaseContent string) (OperatingSystem, error) { + tempDir, err := os.MkdirTemp("", "") + if err != nil { + return OperatingSystem{}, err + } + + defer os.RemoveAll(tempDir) + + etcOSReleasePath := filepath.Join(tempDir, "etc-os-release") + libOSReleasePath := filepath.Join(tempDir, "lib-os-release") + + if err := os.WriteFile(etcOSReleasePath, []byte(etcOSReleaseContent), 0o644); err != nil { + return OperatingSystem{}, err + } + + if err := os.WriteFile(libOSReleasePath, []byte(libOSReleaseContent), 0o644); err != nil { + return OperatingSystem{}, err + } + + return newOperatingSystem(etcOSReleasePath, libOSReleasePath) +} + +// Determines the OS version based upon the contents of the RHEL_VERSION, VERSION or VERSION_ID fields. +func getOSVersion(or osrelease.OSRelease) string { + // If we have the RHEL_VERSION field, we should use that value instead. + if rhelVersion, ok := or.ADDITIONAL_FIELDS["RHEL_VERSION"]; ok { + return rhelVersion + } + + // If we have the OPENSHIFT_VERSION field, we can compute the OS version. + if openshiftVersion, ok := or.ADDITIONAL_FIELDS["OPENSHIFT_VERSION"]; ok { + // Move the "." from the middle of the OpenShift version to the end; e.g., 4.12 becomes 412. + openshiftVersion := strings.ReplaceAll(openshiftVersion, ".", "") + "." + if strings.HasPrefix(or.VERSION, openshiftVersion) { + // Strip the OpenShift Version prefix from the VERSION field, if it is found. + return strings.ReplaceAll(or.VERSION, openshiftVersion, "") + } + } + + // Fallback to the VERSION_ID field + return or.VERSION_ID +} diff --git a/pkg/daemon/osrelease/osrelease_test.go b/pkg/daemon/osrelease/osrelease_test.go new file mode 100644 index 0000000000..637f166a66 --- /dev/null +++ b/pkg/daemon/osrelease/osrelease_test.go @@ -0,0 +1,219 @@ +package osrelease + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsLikeTraditionalRHEL7(t *testing.T) { + t.Parallel() + + var testOS OperatingSystem + testOS.version = "7" + assert.True(t, testOS.IsLikeTraditionalRHEL7()) + testOS.version = "7.5" + assert.True(t, testOS.IsLikeTraditionalRHEL7()) + testOS.version = "8" + assert.False(t, testOS.IsLikeTraditionalRHEL7()) + testOS.version = "6.8" + assert.False(t, testOS.IsLikeTraditionalRHEL7()) +} + +func TestOSRelease(t *testing.T) { + t.Parallel() + + rhcos86OSReleaseContents := `NAME="Red Hat Enterprise Linux CoreOS" +ID="rhcos" +ID_LIKE="rhel fedora" +VERSION="412.86.202301311551-0" +VERSION_ID="4.12" +PLATFORM_ID="platform:el8" +PRETTY_NAME="Red Hat Enterprise Linux CoreOS 412.86.202301311551-0 (Ootpa)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:redhat:enterprise_linux:8::coreos" +HOME_URL="https://www.redhat.com/" +DOCUMENTATION_URL="https://docs.openshift.com/container-platform/4.12/" +BUG_REPORT_URL="https://access.redhat.com/labs/rhir/" +REDHAT_BUGZILLA_PRODUCT="OpenShift Container Platform" +REDHAT_BUGZILLA_PRODUCT_VERSION="4.12" +REDHAT_SUPPORT_PRODUCT="OpenShift Container Platform" +REDHAT_SUPPORT_PRODUCT_VERSION="4.12" +OPENSHIFT_VERSION="4.12" +RHEL_VERSION="8.6" +OSTREE_VERSION="412.86.202301311551-0"` + + rhcos90OSReleaseContents := `NAME="Red Hat Enterprise Linux CoreOS" +ID="rhcos" +ID_LIKE="rhel fedora" +VERSION="413.90.202212151724-0" +VERSION_ID="4.13" +VARIANT="CoreOS" +VARIANT_ID=coreos +PLATFORM_ID="platform:el9" +PRETTY_NAME="Red Hat Enterprise Linux CoreOS 413.90.202212151724-0 (Plow)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:redhat:enterprise_linux:9::coreos" +HOME_URL="https://www.redhat.com/" +DOCUMENTATION_URL="https://docs.openshift.com/container-platform/4.13/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_BUGZILLA_PRODUCT="OpenShift Container Platform" +REDHAT_BUGZILLA_PRODUCT_VERSION="4.13" +REDHAT_SUPPORT_PRODUCT="OpenShift Container Platform" +REDHAT_SUPPORT_PRODUCT_VERSION="4.13" +OPENSHIFT_VERSION="4.13" +RHEL_VERSION="9.0" +OSTREE_VERSION="413.90.202212151724-0"` + + fedora37ServerOSReleaseContents := `NAME="Fedora Linux" +VERSION="37 (Server Edition)" +ID=fedora +VERSION_ID=37 +VERSION_CODENAME="" +PLATFORM_ID="platform:f37" +PRETTY_NAME="Fedora Linux 37 (Server Edition)" +ANSI_COLOR="0;38;2;60;110;180" +LOGO=fedora-logo-icon +CPE_NAME="cpe:/o:fedoraproject:fedora:37" +HOME_URL="https://fedoraproject.org/" +DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f37/system-administrators-guide/" +SUPPORT_URL="https://ask.fedoraproject.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_BUGZILLA_PRODUCT="Fedora" +REDHAT_BUGZILLA_PRODUCT_VERSION=37 +REDHAT_SUPPORT_PRODUCT="Fedora" +REDHAT_SUPPORT_PRODUCT_VERSION=37 +SUPPORT_END=2023-11-14 +VARIANT="Server Edition" +VARIANT_ID=server` + + scosOSReleaseContents := `NAME="CentOS Stream CoreOS" +ID="scos" +ID_LIKE="rhel fedora" +VERSION="412.9.202211241749-0" +VERSION_ID="4.12" +VARIANT="CoreOS" +VARIANT_ID=coreos +PLATFORM_ID="platform:el9" +PRETTY_NAME="CentOS Stream CoreOS 412.9.202211241749-0" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:9::coreos" +HOME_URL="https://centos.org/" +DOCUMENTATION_URL="https://docs.okd.io/latest/welcome/index.html" +BUG_REPORT_URL="https://access.redhat.com/labs/rhir/" +REDHAT_BUGZILLA_PRODUCT="OpenShift Container Platform" +REDHAT_BUGZILLA_PRODUCT_VERSION="4.12" +REDHAT_SUPPORT_PRODUCT="OpenShift Container Platform" +REDHAT_SUPPORT_PRODUCT_VERSION="4.12" +OPENSHIFT_VERSION="4.12" +OSTREE_VERSION="412.9.202211241749-0"` + + fcosOSReleaseContents := `NAME="Fedora Linux" +VERSION="37.20230126.20.0 (CoreOS)" +ID=fedora +VERSION_ID=37 +VERSION_CODENAME="" +PLATFORM_ID="platform:f37" +PRETTY_NAME="Fedora CoreOS 37.20230126.20.0" +ANSI_COLOR="0;38;2;60;110;180" +LOGO=fedora-logo-icon +CPE_NAME="cpe:/o:fedoraproject:fedora:37" +HOME_URL="https://getfedora.org/coreos/" +DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-coreos/" +SUPPORT_URL="https://github.com/coreos/fedora-coreos-tracker/" +BUG_REPORT_URL="https://github.com/coreos/fedora-coreos-tracker/" +REDHAT_BUGZILLA_PRODUCT="Fedora" +REDHAT_BUGZILLA_PRODUCT_VERSION=37 +REDHAT_SUPPORT_PRODUCT="Fedora" +REDHAT_SUPPORT_PRODUCT_VERSION=37 +SUPPORT_END=2023-11-14 +VARIANT="CoreOS" +VARIANT_ID=coreos +OSTREE_VERSION='37.20230126.20.0'` + + testCases := []struct { + Name string + OSReleaseContents string + IsEL bool + IsEL9 bool + IsFCOS bool + IsSCOS bool + IsCoreOSVariant bool + IsLikeTraditionalRHEL7 bool + ToPrometheusLabel string + }{ + { + Name: "RHCOS 8.6", + OSReleaseContents: rhcos86OSReleaseContents, + IsEL: true, + IsEL9: false, + IsFCOS: false, + IsSCOS: false, + IsCoreOSVariant: true, + IsLikeTraditionalRHEL7: false, + ToPrometheusLabel: "RHCOS", + }, + { + Name: "RHCOS 9.0", + OSReleaseContents: rhcos90OSReleaseContents, + IsEL: true, + IsEL9: true, + IsFCOS: false, + IsSCOS: false, + IsCoreOSVariant: true, + IsLikeTraditionalRHEL7: false, + ToPrometheusLabel: "RHCOS", + }, + { + Name: "Fedora 37 Server", + OSReleaseContents: fedora37ServerOSReleaseContents, + IsEL: false, + IsEL9: false, + IsFCOS: false, + IsSCOS: false, + IsCoreOSVariant: false, + IsLikeTraditionalRHEL7: false, + ToPrometheusLabel: "FEDORA", + }, + { + Name: "SCOS", + OSReleaseContents: scosOSReleaseContents, + IsEL: true, + IsEL9: true, + IsFCOS: false, + IsSCOS: true, + IsCoreOSVariant: true, + IsLikeTraditionalRHEL7: false, + ToPrometheusLabel: "SCOS", + }, + { + Name: "FCOS", + OSReleaseContents: fcosOSReleaseContents, + IsEL: false, + IsEL9: false, + IsFCOS: true, + IsSCOS: false, + IsCoreOSVariant: true, + IsLikeTraditionalRHEL7: false, + ToPrometheusLabel: "FEDORA", + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + os, err := LoadOSRelease(testCase.OSReleaseContents, testCase.OSReleaseContents) + require.NoError(t, err) + + assert.Equal(t, testCase.IsEL, os.IsEL(), "expected IsEL() to be %v", testCase.IsEL) + assert.Equal(t, testCase.IsEL9, os.IsEL9(), "expected IsEL9() to be %v", testCase.IsEL9) + assert.Equal(t, testCase.IsCoreOSVariant, os.IsCoreOSVariant(), "expected IsCoreOSVariant() to be %v", testCase.IsCoreOSVariant) + assert.Equal(t, testCase.IsFCOS, os.IsFCOS(), "expected IsFCOS() to be %v", testCase.IsFCOS) + assert.Equal(t, testCase.IsSCOS, os.IsSCOS(), "expected IsSCOS() to be %v", testCase.IsSCOS) + assert.Equal(t, testCase.IsLikeTraditionalRHEL7, os.IsLikeTraditionalRHEL7(), "expected IsLikeTraditionalRHEL7() to be %v", testCase.IsLikeTraditionalRHEL7) + assert.Equal(t, testCase.ToPrometheusLabel, os.ToPrometheusLabel(), "expected ToPrometheusLabel() to be %s, got %s", testCase.ToPrometheusLabel, os.ToPrometheusLabel()) + }) + } +} diff --git a/pkg/daemon/osrelease_test.go b/pkg/daemon/osrelease_test.go deleted file mode 100644 index 393a21f05e..0000000000 --- a/pkg/daemon/osrelease_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package daemon - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestIsLikeTraditionalRHEL7(t *testing.T) { - var testOS OperatingSystem - testOS.VersionID = "7" - assert.True(t, testOS.IsLikeTraditionalRHEL7()) - testOS.VersionID = "7.5" - assert.True(t, testOS.IsLikeTraditionalRHEL7()) - testOS.VersionID = "8" - assert.False(t, testOS.IsLikeTraditionalRHEL7()) - testOS.VersionID = "6.8" - assert.False(t, testOS.IsLikeTraditionalRHEL7()) -} diff --git a/pkg/daemon/rpm-ostree.go b/pkg/daemon/rpm-ostree.go index 9be46ef200..f8601eefdf 100644 --- a/pkg/daemon/rpm-ostree.go +++ b/pkg/daemon/rpm-ostree.go @@ -14,6 +14,7 @@ import ( rpmostreeclient "github.com/coreos/rpmostree-client-go/pkg/client" "github.com/golang/glog" "github.com/opencontainers/go-digest" + "github.com/openshift/machine-config-operator/pkg/daemon/osrelease" pivotutils "github.com/openshift/machine-config-operator/pkg/daemon/pivot/utils" "gopkg.in/yaml.v2" ) @@ -355,7 +356,7 @@ func useKubeletConfigSecrets() error { return err } - runningos, err := GetHostRunningOS() + runningos, err := osrelease.GetHostRunningOS() if err != nil { return err } diff --git a/pkg/daemon/update_test.go b/pkg/daemon/update_test.go index e41ed48613..cb724168b2 100644 --- a/pkg/daemon/update_test.go +++ b/pkg/daemon/update_test.go @@ -16,6 +16,7 @@ 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" @@ -29,7 +30,7 @@ func newMockDaemon() Daemon { return Daemon{ mock: true, name: "nodeName", - os: OperatingSystem{}, + os: osrelease.OperatingSystem{}, kubeClient: k8sfake.NewSimpleClientset(), bootedOSImageURL: "test", } diff --git a/test/e2e/node_os_test.go b/test/e2e/node_os_test.go new file mode 100644 index 0000000000..5d5b90e0bf --- /dev/null +++ b/test/e2e/node_os_test.go @@ -0,0 +1,86 @@ +package e2e + +import ( + "context" + "strings" + "testing" + + "github.com/openshift/machine-config-operator/test/framework" + "github.com/openshift/machine-config-operator/test/helpers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + rhcos string = "Red Hat Enterprise Linux CoreOS" + scos string = "CentOS Stream CoreOS" + fcos string = "Fedora Linux" +) + +// Verifies that the OS on each node is identifiable using the mechanism the +// MCD does. This is done to ensure that we get an early warning if the +// contents of /etc/os-release or /usr/lib/os-release unexpectedly change. +func TestOSDetection(t *testing.T) { + cs := framework.NewClientSet("") + nodes, err := cs.CoreV1Interface.Nodes().List(context.TODO(), metav1.ListOptions{}) + require.NoError(t, err) + + for _, node := range nodes.Items { + node := node + t.Run("", func(t *testing.T) { + t.Parallel() + + nodeOSRelease := helpers.GetOSReleaseForNode(t, cs, node) + + assert.Equal(t, nodeOSRelease.EtcContent, nodeOSRelease.LibContent) + assert.True(t, nodeOSRelease.OS.IsCoreOSVariant(), "expected IsCoreOSVariant() to be true: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsLikeTraditionalRHEL7(), "expected IsLikeTraditionalRHEL7() to be false: %s", nodeOSRelease.EtcContent) + + switch { + case strings.Contains(nodeOSRelease.EtcContent, rhcos): + assertRHCOS(t, nodeOSRelease, node) + case strings.Contains(nodeOSRelease.EtcContent, scos): + assertSCOS(t, nodeOSRelease, node) + case strings.Contains(nodeOSRelease.EtcContent, fcos): + assertFCOS(t, nodeOSRelease, node) + default: + t.Fatalf("unknown OS on node %s detected: %s", node.Name, nodeOSRelease.EtcContent) + } + }) + } +} + +func assertRHCOS(t *testing.T, nodeOSRelease helpers.NodeOSRelease, node corev1.Node) { + assert.True(t, nodeOSRelease.OS.IsEL(), "expected IsEL() to be true: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsSCOS(), "expected IsSCOS() to be false: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsFCOS(), "expected IsFCOS() to be false: %s", nodeOSRelease.EtcContent) + + rhelVersion := nodeOSRelease.OS.OSRelease().ADDITIONAL_FIELDS["RHEL_VERSION"] + + if strings.Contains(nodeOSRelease.EtcContent, "RHEL_VERSION=\"8.") { // This pattern intentionally unterminated so it matches all RHCOS 8 versions + t.Logf("Identified %s %s on node %s", rhcos, rhelVersion, node.Name) + assert.False(t, nodeOSRelease.OS.IsEL9(), "expected < RHCOS 9.0: %s", nodeOSRelease.EtcContent) + } + + if strings.Contains(nodeOSRelease.EtcContent, "RHEL_VERSION=\"9.") { // This pattern intentionally unterminated so it matches all RHCOS 9 versions + t.Logf("Identified %s %s on node %s", rhcos, rhelVersion, node.Name) + assert.True(t, nodeOSRelease.OS.IsEL9(), "expected >= RHCOS 9.0+: %s", nodeOSRelease.EtcContent) + } +} + +func assertSCOS(t *testing.T, nodeOSRelease helpers.NodeOSRelease, node corev1.Node) { + t.Logf("Identified %s on node %s", scos, node.Name) + assert.True(t, nodeOSRelease.OS.IsEL(), "expected IsEL() to be true: %s", nodeOSRelease.EtcContent) + assert.True(t, nodeOSRelease.OS.IsEL9(), "expected IsEL9() to be true: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsFCOS(), "expected IsFCOS() to be false: %s", nodeOSRelease.EtcContent) +} + +func assertFCOS(t *testing.T, nodeOSRelease helpers.NodeOSRelease, node corev1.Node) { + t.Logf("Identified OS %s on node %s", fcos, node.Name) + assert.False(t, nodeOSRelease.OS.IsEL(), "expected IsEL() to be false: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsEL9(), "expected IsEL9() to be false: %s", nodeOSRelease.EtcContent) + assert.False(t, nodeOSRelease.OS.IsSCOS(), "expected IsSCOS() to be false: %s", nodeOSRelease.EtcContent) + assert.True(t, nodeOSRelease.OS.IsFCOS(), "expected IsFCOS() to be true: %s", nodeOSRelease.EtcContent) +} diff --git a/test/helpers/utils.go b/test/helpers/utils.go index dcc40b93de..c2af35ac2f 100644 --- a/test/helpers/utils.go +++ b/test/helpers/utils.go @@ -17,6 +17,7 @@ import ( ign3types "github.com/coreos/ignition/v2/config/v3_2/types" mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" "github.com/openshift/machine-config-operator/pkg/daemon/constants" + "github.com/openshift/machine-config-operator/pkg/daemon/osrelease" "github.com/openshift/machine-config-operator/test/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -584,6 +585,31 @@ func OverrideGlobalPathVar(t *testing.T, name string, val *string) func() { } } +type NodeOSRelease struct { + // The contents of the /etc/os-release file + EtcContent string + // The contents of the /usr/lib/os-release file + LibContent string + // The parsed contents + OS osrelease.OperatingSystem +} + +// Retrieves the /etc/os-release and /usr/lib/os-release file contents on a +// given node and parses it through the OperatingSystem code. +func GetOSReleaseForNode(t *testing.T, cs *framework.ClientSet, node corev1.Node) NodeOSRelease { + etcOSReleaseContent := ExecCmdOnNode(t, cs, node, "cat", filepath.Join("/rootfs", osrelease.EtcOSReleasePath)) + libOSReleaseContent := ExecCmdOnNode(t, cs, node, "cat", filepath.Join("/rootfs", osrelease.LibOSReleasePath)) + + os, err := osrelease.LoadOSRelease(etcOSReleaseContent, libOSReleaseContent) + require.NoError(t, err) + + return NodeOSRelease{ + EtcContent: etcOSReleaseContent, + LibContent: libOSReleaseContent, + OS: os, + } +} + func mcdForNode(cs *framework.ClientSet, node *corev1.Node) (*corev1.Pod, error) { // find the MCD pod that has spec.nodeNAME = node.Name and get its name: listOptions := metav1.ListOptions{