github.com/coreos/mantle@v0.13.0/kola/tests/rpmostree/deployments.go (about)

     1  // Copyright 2018 Red Hat, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpmostree
    16  
    17  import (
    18  	"reflect"
    19  	"regexp"
    20  
    21  	"github.com/coreos/mantle/kola/cluster"
    22  	"github.com/coreos/mantle/kola/register"
    23  	"github.com/coreos/mantle/kola/tests/util"
    24  	"github.com/coreos/mantle/platform/conf"
    25  )
    26  
    27  func init() {
    28  	register.Register(&register.Test{
    29  		Run:         rpmOstreeUpgradeRollback,
    30  		ClusterSize: 1,
    31  		Name:        "rpmostree.upgrade-rollback",
    32  		Distros:     []string{"fcos", "rhcos"},
    33  		FailFast:    true,
    34  	})
    35  	register.Register(&register.Test{
    36  		Run:         rpmOstreeInstallUninstall,
    37  		ClusterSize: 1,
    38  		Name:        "rpmostree.install-uninstall",
    39  		// this Ignition config lands the EPEL repo + key
    40  		UserData: conf.Ignition(`{
    41    "ignition": {
    42      "version": "2.2.0"
    43    },
    44    "storage": {
    45      "files": [
    46        {
    47          "filesystem": "root",
    48          "group": {
    49            "name": "root"
    50          },
    51          "path": "/etc/yum.repos.d/epel.repo",
    52          "user": {
    53            "name": "root"
    54          },
    55          "contents": {
    56            "source": "data:,%5Bepel%5D%0Aname%3DExtra%20Packages%20for%20Enterprise%20Linux%207%20-%20%24basearch%0Ametalink%3Dhttps%3A%2F%2Fmirrors.fedoraproject.org%2Fmetalink%3Frepo%3Depel-7%26arch%3D%24basearch%0Afailovermethod%3Dpriority%0Aenabled%3D1%0Agpgcheck%3D1%0Agpgkey%3Dfile%3A%2F%2F%2Fetc%2Fpki%2Frpm-gpg%2FRPM-GPG-KEY-EPEL-7%0A"
    57          },
    58          "mode": 420
    59        },
    60        {
    61          "filesystem": "root",
    62          "group": {
    63            "name": "root"
    64          },
    65          "path": "/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7",
    66          "user": {
    67            "name": "root"
    68          },
    69          "contents": {
    70            "source": "data:,-----BEGIN%20PGP%20PUBLIC%20KEY%20BLOCK-----%0AVersion%3A%20GnuPG%20v1.4.11%20(GNU%2FLinux)%0A%0AmQINBFKuaIQBEAC1UphXwMqCAarPUH%2FZsOFslabeTVO2pDk5YnO96f%2BrgZB7xArB%0AOSeQk7B90iqSJ85%2Fc72OAn4OXYvT63gfCeXpJs5M7emXkPsNQWWSju99lW%2BAqSNm%0AjYWhmRlLRGl0OO7gIwj776dIXvcMNFlzSPj00N2xAqjMbjlnV2n2abAE5gq6VpqP%0AvFXVyfrVa%2FualogDVmf6h2t4Rdpifq8qTHsHFU3xpCz%2BT6%2FdGWKGQ42ZQfTaLnDM%0AjToAsmY0AyevkIbX6iZVtzGvanYpPcWW4X0RDPcpqfFNZk643xI4lsZ%2BY2Er9Yu5%0AS%2F8x0ly%2BtmmIokaE0wwbdUu740YTZjCesroYWiRg5zuQ2xfKxJoV5E%2BEh%2BtYwGDJ%0An6HfWhRgnudRRwvuJ45ztYVtKulKw8QQpd2STWrcQQDJaRWmnMooX%2FPATTjCBExB%0A9dkz38Druvk7IkHMtsIqlkAOQMdsX1d3Tov6BE2XDjIG0zFxLduJGbVwc%2F6rIc95%0AT055j36Ez0HrjxdpTGOOHxRqMK5m9flFbaxxtDnS7w77WqzW7HjFrD0VeTx2vnjj%0AGqchHEQpfDpFOzb8LTFhgYidyRNUflQY35WLOzLNV%2BpV3eQ3Jg11UFwelSNLqfQf%0AuFRGc%2BzcwkNjHh5yPvm9odR1BIfqJ6sKGPGbtPNXo7ERMRypWyRz0zi0twARAQAB%0AtChGZWRvcmEgRVBFTCAoNykgPGVwZWxAZmVkb3JhcHJvamVjdC5vcmc%2BiQI4BBMB%0AAgAiBQJSrmiEAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBqL66iNSxk%0A5cfGD%2F4spqpsTjtDM7qpytKLHKruZtvuWiqt5RfvT9ww9GUUFMZ4ZZGX4nUXg49q%0AixDLayWR8ddG%2Fs5kyOi3C0uX%2F6inzaYyRg%2BBh70brqKUK14F1BrrPi29eaKfG%2BGu%0AMFtXdBG2a7OtPmw3yuKmq9Epv6B0mP6E5KSdvSRSqJWtGcA6wRS%2FwDzXJENHp5re%0A9Ism3CYydpy0GLRA5wo4fPB5uLdUhLEUDvh2KK%2F%2FfMjja3o0L%2BSNz8N0aDZyn5Ax%0ACU9RB3EHcTecFgoy5umRj99BZrebR1NO%2B4gBrivIfdvD4fJNfNBHXwhSH9ACGCNv%0AHnXVjHQF9iHWApKkRIeh8Fr2n5dtfJEF7SEX8GbX7FbsWo29kXMrVgNqHNyDnfAB%0AVoPubgQdtJZJkVZAkaHrMu8AytwT62Q4eNqmJI1aWbZQNI5jWYqc6RKuCK6%2FF99q%0AthFT9gJO17%2ByRuL6Uv2%2FvgzVR1RGdwVLKwlUjGPAjYflpCQwWMAASxiv9uPyYPHc%0AErSrbRG0wjIfAR3vus1OSOx3xZHZpXFfmQTsDP7zVROLzV98R3JwFAxJ4%2FxqeON4%0AvCPFU6OsT3lWQ8w7il5ohY95wmujfr6lk89kEzJdOTzcn7DBbUru33CQMGKZ3Evt%0ARjsC7FDbL017qxS%2BZVA%2FHGkyfiu4cpgV8VUnbql5eAZ%2B1Ll6Dw%3D%3D%0A%3DhdPa%0A-----END%20PGP%20PUBLIC%20KEY%20BLOCK-----%0A"
    71          },
    72          "mode": 420
    73        }
    74      ]
    75    }
    76  }`),
    77  		UserDataV3: conf.Ignition(`{
    78    "ignition": {
    79      "version": "3.0.0"
    80    },
    81    "storage": {
    82      "files": [
    83        {
    84          "group": {
    85            "name": "root"
    86          },
    87          "path": "/etc/yum.repos.d/epel.repo",
    88          "user": {
    89            "name": "root"
    90          },
    91          "contents": {
    92            "source": "data:,%5Bepel%5D%0Aname%3DExtra%20Packages%20for%20Enterprise%20Linux%207%20-%20%24basearch%0Ametalink%3Dhttps%3A%2F%2Fmirrors.fedoraproject.org%2Fmetalink%3Frepo%3Depel-7%26arch%3D%24basearch%0Afailovermethod%3Dpriority%0Aenabled%3D1%0Agpgcheck%3D1%0Agpgkey%3Dfile%3A%2F%2F%2Fetc%2Fpki%2Frpm-gpg%2FRPM-GPG-KEY-EPEL-7%0A"
    93          },
    94          "mode": 420
    95        },
    96        {
    97          "group": {
    98            "name": "root"
    99          },
   100          "path": "/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7",
   101          "user": {
   102            "name": "root"
   103          },
   104          "contents": {
   105            "source": "data:,-----BEGIN%20PGP%20PUBLIC%20KEY%20BLOCK-----%0AVersion%3A%20GnuPG%20v1.4.11%20(GNU%2FLinux)%0A%0AmQINBFKuaIQBEAC1UphXwMqCAarPUH%2FZsOFslabeTVO2pDk5YnO96f%2BrgZB7xArB%0AOSeQk7B90iqSJ85%2Fc72OAn4OXYvT63gfCeXpJs5M7emXkPsNQWWSju99lW%2BAqSNm%0AjYWhmRlLRGl0OO7gIwj776dIXvcMNFlzSPj00N2xAqjMbjlnV2n2abAE5gq6VpqP%0AvFXVyfrVa%2FualogDVmf6h2t4Rdpifq8qTHsHFU3xpCz%2BT6%2FdGWKGQ42ZQfTaLnDM%0AjToAsmY0AyevkIbX6iZVtzGvanYpPcWW4X0RDPcpqfFNZk643xI4lsZ%2BY2Er9Yu5%0AS%2F8x0ly%2BtmmIokaE0wwbdUu740YTZjCesroYWiRg5zuQ2xfKxJoV5E%2BEh%2BtYwGDJ%0An6HfWhRgnudRRwvuJ45ztYVtKulKw8QQpd2STWrcQQDJaRWmnMooX%2FPATTjCBExB%0A9dkz38Druvk7IkHMtsIqlkAOQMdsX1d3Tov6BE2XDjIG0zFxLduJGbVwc%2F6rIc95%0AT055j36Ez0HrjxdpTGOOHxRqMK5m9flFbaxxtDnS7w77WqzW7HjFrD0VeTx2vnjj%0AGqchHEQpfDpFOzb8LTFhgYidyRNUflQY35WLOzLNV%2BpV3eQ3Jg11UFwelSNLqfQf%0AuFRGc%2BzcwkNjHh5yPvm9odR1BIfqJ6sKGPGbtPNXo7ERMRypWyRz0zi0twARAQAB%0AtChGZWRvcmEgRVBFTCAoNykgPGVwZWxAZmVkb3JhcHJvamVjdC5vcmc%2BiQI4BBMB%0AAgAiBQJSrmiEAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBqL66iNSxk%0A5cfGD%2F4spqpsTjtDM7qpytKLHKruZtvuWiqt5RfvT9ww9GUUFMZ4ZZGX4nUXg49q%0AixDLayWR8ddG%2Fs5kyOi3C0uX%2F6inzaYyRg%2BBh70brqKUK14F1BrrPi29eaKfG%2BGu%0AMFtXdBG2a7OtPmw3yuKmq9Epv6B0mP6E5KSdvSRSqJWtGcA6wRS%2FwDzXJENHp5re%0A9Ism3CYydpy0GLRA5wo4fPB5uLdUhLEUDvh2KK%2F%2FfMjja3o0L%2BSNz8N0aDZyn5Ax%0ACU9RB3EHcTecFgoy5umRj99BZrebR1NO%2B4gBrivIfdvD4fJNfNBHXwhSH9ACGCNv%0AHnXVjHQF9iHWApKkRIeh8Fr2n5dtfJEF7SEX8GbX7FbsWo29kXMrVgNqHNyDnfAB%0AVoPubgQdtJZJkVZAkaHrMu8AytwT62Q4eNqmJI1aWbZQNI5jWYqc6RKuCK6%2FF99q%0AthFT9gJO17%2ByRuL6Uv2%2FvgzVR1RGdwVLKwlUjGPAjYflpCQwWMAASxiv9uPyYPHc%0AErSrbRG0wjIfAR3vus1OSOx3xZHZpXFfmQTsDP7zVROLzV98R3JwFAxJ4%2FxqeON4%0AvCPFU6OsT3lWQ8w7il5ohY95wmujfr6lk89kEzJdOTzcn7DBbUru33CQMGKZ3Evt%0ARjsC7FDbL017qxS%2BZVA%2FHGkyfiu4cpgV8VUnbql5eAZ%2B1Ll6Dw%3D%3D%0A%3DhdPa%0A-----END%20PGP%20PUBLIC%20KEY%20BLOCK-----%0A"
   106          },
   107          "mode": 420
   108        }
   109      ]
   110    }
   111  }`),
   112  
   113  		Distros: []string{"fcos", "rhcos"},
   114  		Flags:   []register.Flag{register.RequiresInternetAccess}, // these need network to retrieve bits
   115  	})
   116  }
   117  
   118  // rpmOstreeUpgradeRollback simulates an upgrade by creating a local branch, making
   119  // a commit to the branch, and rebases the host to said commit.  After a successful
   120  // "upgrade", the host is rolled back to the original deployment.
   121  func rpmOstreeUpgradeRollback(c cluster.TestCluster) {
   122  	var newBranch string = "local-branch"
   123  	var newVersion string = "kola-test-1.0"
   124  
   125  	m := c.Machines()[0]
   126  
   127  	originalStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   128  	if err != nil {
   129  		c.Fatal(err)
   130  	}
   131  
   132  	if len(originalStatus.Deployments) < 1 {
   133  		c.Fatalf(`Unexpected results from "rpm-ostree status"; received: %v`, originalStatus)
   134  	}
   135  
   136  	c.Run("upgrade", func(c cluster.TestCluster) {
   137  		// create a local branch to act as our upgrade target
   138  		originalCsum := originalStatus.Deployments[0].Checksum
   139  		createBranch := "sudo ostree refs --create " + newBranch + " " + originalCsum
   140  		c.MustSSH(m, createBranch)
   141  
   142  		// make a commit to the new branch
   143  		createCommit := "sudo ostree commit -b " + newBranch + " --tree ref=" + originalCsum + " --add-metadata-string version=" + newVersion
   144  		newCommit := c.MustSSH(m, createCommit)
   145  
   146  		// use "rpm-ostree rebase" to get to the "new" commit
   147  		c.MustSSH(m, "sudo rpm-ostree rebase :"+newBranch)
   148  
   149  		// get latest rpm-ostree status output to check validity
   150  		postUpgradeStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   151  		if err != nil {
   152  			c.Fatal(err)
   153  		}
   154  
   155  		// should have an additional deployment
   156  		if len(postUpgradeStatus.Deployments) != len(originalStatus.Deployments)+1 {
   157  			c.Fatalf("Expected %d deployments; found %d deployments", len(originalStatus.Deployments)+1, len(postUpgradeStatus.Deployments))
   158  		}
   159  
   160  		// reboot into new deployment
   161  		rebootErr := m.Reboot()
   162  		if rebootErr != nil {
   163  			c.Fatalf("Failed to reboot machine: %v", err)
   164  		}
   165  
   166  		// get latest rpm-ostree status output
   167  		postRebootStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   168  		if err != nil {
   169  			c.Fatal(err)
   170  		}
   171  
   172  		// should have 2 deployments, the previously booted deployment and the test deployment due to rpm-ostree pruning
   173  		if len(postRebootStatus.Deployments) != 2 {
   174  			c.Fatalf("Expected %d deployments; found %d deployment", 2, len(postRebootStatus.Deployments))
   175  		}
   176  
   177  		// origin should be new branch
   178  		if postRebootStatus.Deployments[0].Origin != newBranch {
   179  			c.Fatalf(`New deployment origin is incorrect; expected %q, got %q`, newBranch, postRebootStatus.Deployments[0].Origin)
   180  		}
   181  
   182  		// new deployment should be booted
   183  		if !postRebootStatus.Deployments[0].Booted {
   184  			c.Fatalf("New deployment is not reporting as booted")
   185  		}
   186  
   187  		// checksum should be new commit
   188  		if postRebootStatus.Deployments[0].Checksum != string(newCommit) {
   189  			c.Fatalf(`New deployment checksum is incorrect; expected %q, got %q`, newCommit, postRebootStatus.Deployments[0].Checksum)
   190  		}
   191  
   192  		// version should be new version string
   193  		if postRebootStatus.Deployments[0].Version != newVersion {
   194  			c.Fatalf(`New deployment version is incorrect; expected %q, got %q`, newVersion, postRebootStatus.Deployments[0].Checksum)
   195  		}
   196  	})
   197  
   198  	c.Run("rollback", func(c cluster.TestCluster) {
   199  		// rollback to original deployment
   200  		c.MustSSH(m, "sudo rpm-ostree rollback")
   201  
   202  		newRebootErr := m.Reboot()
   203  		if newRebootErr != nil {
   204  			c.Fatalf("Failed to reboot machine: %v", err)
   205  		}
   206  
   207  		rollbackStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   208  		if err != nil {
   209  			c.Fatal(err)
   210  		}
   211  
   212  		// still 2 deployments...
   213  		if len(rollbackStatus.Deployments) != 2 {
   214  			c.Fatalf("Expected %d deployments; found %d deployments", 2, len(rollbackStatus.Deployments))
   215  		}
   216  
   217  		// validate we are back to the original deployment by comparing the
   218  		// the two rpmOstreeDeployment structs
   219  		if !reflect.DeepEqual(originalStatus.Deployments[0], rollbackStatus.Deployments[0]) {
   220  			c.Fatalf(`Differences found in "rpm-ostree status"; original %v, current: %v`, originalStatus.Deployments[0], rollbackStatus.Deployments[0])
   221  		}
   222  
   223  		// cleanup our mess
   224  		cleanupErr := rpmOstreeCleanup(c, m)
   225  		if cleanupErr != nil {
   226  			c.Fatal(cleanupErr)
   227  		}
   228  	})
   229  }
   230  
   231  // rpmOstreeInstallUninstall verifies that we can install a package
   232  // and then uninstall it
   233  //
   234  // 'bcrypt' is available in EPEL and installs on Fedora 29 and RHEL 8
   235  //
   236  // NOTE: we could be churning on the package choice going forward as
   237  // we need something that is a) small, b) has no dependencies, and c)
   238  // can be installed on Fedora + RHEL from the EPEL repo that we are
   239  // currently using.  We've already had to swap from `fpaste` to `bcrypt`
   240  func rpmOstreeInstallUninstall(c cluster.TestCluster) {
   241  	var installPkgName = "bcrypt"
   242  	var installPkgBin = "/bin/bcrypt"
   243  
   244  	m := c.Machines()[0]
   245  
   246  	originalStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   247  	if err != nil {
   248  		c.Fatal(err)
   249  	}
   250  
   251  	if len(originalStatus.Deployments) < 1 {
   252  		c.Fatal(`Unexpected results from "rpm-ostree status"; no deployments?`)
   253  	}
   254  
   255  	originalCsum := originalStatus.Deployments[0].Checksum
   256  
   257  	c.Run("install", func(c cluster.TestCluster) {
   258  		// install package and reboot
   259  		c.MustSSH(m, "sudo rpm-ostree install "+installPkgName)
   260  
   261  		installRebootErr := m.Reboot()
   262  		if installRebootErr != nil {
   263  			c.Fatalf("Failed to reboot machine: %v", installRebootErr)
   264  		}
   265  
   266  		postInstallStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   267  		if err != nil {
   268  			c.Fatal(err)
   269  		}
   270  
   271  		if len(postInstallStatus.Deployments) != 2 {
   272  			c.Fatalf(`Expected two deployments, found %d deployments`, len(postInstallStatus.Deployments))
   273  		}
   274  
   275  		// check the command is present, in the rpmdb, and usable
   276  		cmdOut := c.MustSSH(m, "command -v "+installPkgName)
   277  		if string(cmdOut) != installPkgBin {
   278  			c.Fatalf(`%q binary in unexpected location. expectd %q, got %q`, installPkgName, installPkgBin, string(cmdOut))
   279  		}
   280  
   281  		rpmOut := c.MustSSH(m, "rpm -q "+installPkgName)
   282  		// forcing use of x86_64; may need to adapt this in the future
   283  		rpmRegex := "^" + installPkgName + ".*x86_64"
   284  		rpmMatch := regexp.MustCompile(rpmRegex).MatchString(string(rpmOut))
   285  		if !rpmMatch {
   286  			c.Fatalf(`Output from "rpm -q" was unexpected: %q`, string(rpmOut))
   287  		}
   288  
   289  		// package should be in the metadata
   290  		var reqPkg bool = false
   291  		for _, pkg := range postInstallStatus.Deployments[0].RequestedPackages {
   292  			if pkg == installPkgName {
   293  				reqPkg = true
   294  				break
   295  			}
   296  		}
   297  		if !reqPkg {
   298  			c.Fatalf(`Unable to find "%q" in requested-packages: %v`, installPkgName, postInstallStatus.Deployments[0].RequestedPackages)
   299  		}
   300  
   301  		var installPkg bool = false
   302  		for _, pkg := range postInstallStatus.Deployments[0].Packages {
   303  			if pkg == installPkgName {
   304  				installPkg = true
   305  				break
   306  			}
   307  		}
   308  		if !installPkg {
   309  			c.Fatalf(`Unable to find "%q" in packages: %v`, installPkgName, postInstallStatus.Deployments[0].Packages)
   310  		}
   311  
   312  		// checksum should be different
   313  		if postInstallStatus.Deployments[0].Checksum == originalCsum {
   314  			c.Fatalf(`Commit IDs incorrectly matched after package install`)
   315  		}
   316  	})
   317  
   318  	// uninstall the package
   319  	c.Run("uninstall", func(c cluster.TestCluster) {
   320  		c.MustSSH(m, "sudo rpm-ostree uninstall "+installPkgName)
   321  
   322  		uninstallRebootErr := m.Reboot()
   323  		if uninstallRebootErr != nil {
   324  			c.Fatalf("Failed to reboot machine: %v", uninstallRebootErr)
   325  		}
   326  
   327  		postUninstallStatus, err := util.GetRpmOstreeStatusJSON(c, m)
   328  		if err != nil {
   329  			c.Fatal(err)
   330  		}
   331  
   332  		// check the metadata to make sure everything went well
   333  		if len(postUninstallStatus.Deployments) != 2 {
   334  			c.Fatal("Expected %d deployments, got %d", 2, len(postUninstallStatus.Deployments))
   335  		}
   336  
   337  		if postUninstallStatus.Deployments[0].Checksum != originalCsum {
   338  			c.Fatalf(`Checksum is incorrect; expected %q, got %q`, originalCsum, postUninstallStatus.Deployments[0].Checksum)
   339  		}
   340  
   341  		if len(postUninstallStatus.Deployments[0].RequestedPackages) != 0 {
   342  			c.Fatalf(`Found unexpected requested-packages: %q`, postUninstallStatus.Deployments[0].RequestedPackages)
   343  		}
   344  
   345  		if len(postUninstallStatus.Deployments[0].Packages) != 0 {
   346  			c.Fatalf(`Found unexpected packages: %q`, postUninstallStatus.Deployments[0].Packages)
   347  		}
   348  
   349  		// cleanup our mess
   350  		cleanupErr := rpmOstreeCleanup(c, m)
   351  		if cleanupErr != nil {
   352  			c.Fatal(cleanupErr)
   353  		}
   354  	})
   355  }