github.com/coreos/mantle@v0.13.0/kola/tests/ostree/basic.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 ostree 16 17 import ( 18 "fmt" 19 "regexp" 20 "strings" 21 "time" 22 23 "github.com/coreos/mantle/kola/cluster" 24 "github.com/coreos/mantle/kola/register" 25 "github.com/coreos/mantle/kola/tests/util" 26 "github.com/coreos/mantle/platform" 27 ) 28 29 // the "basic" test is only supported on 'rhcos' for now because of how 30 // the refs are defined. if 'fcos' goes in the same direction, we can 31 // expand support there. 32 func init() { 33 register.Register(®ister.Test{ 34 Run: ostreeBasicTest, 35 ClusterSize: 1, 36 Name: "ostree.basic", 37 Distros: []string{"rhcos"}, 38 FailFast: true, 39 }) 40 41 register.Register(®ister.Test{ 42 Run: ostreeRemoteTest, 43 ClusterSize: 1, 44 Name: "ostree.remote", 45 Distros: []string{"fcos", "rhcos"}, 46 Flags: []register.Flag{register.RequiresInternetAccess}, // need network to contact remote 47 FailFast: true, 48 }) 49 } 50 51 type ostreeAdminStatus struct { 52 Checksum string 53 Origin string 54 Version string 55 } 56 57 // getOstreeRemotes returns the current number of ostree remotes on a machine 58 func getOstreeRemotes(c cluster.TestCluster, m platform.Machine) (int, []string) { 59 remoteListOut := string(c.MustSSH(m, "ostree remote list")) 60 numRemotes := 0 61 // If we get anything other than an empty string calculate the results 62 // NOTE: This is needed as splitting "" ends up providing a count of 1 63 // when the count should be 0 64 remoteListRaw := strings.Split(remoteListOut, "\n") 65 if remoteListOut != "" { 66 numRemotes = len(remoteListRaw) 67 } 68 return numRemotes, remoteListRaw 69 } 70 71 // getOstreeAdminStatus stuffs the important output of `ostree admin status` 72 // into an `ostreeAdminStatus` struct 73 func getOstreeAdminStatus(c cluster.TestCluster, m platform.Machine) (ostreeAdminStatus, error) { 74 oaStatus := ostreeAdminStatus{} 75 76 oasOutput, err := c.SSH(m, "ostree admin status") 77 if err != nil { 78 return oaStatus, fmt.Errorf(`Could not get "ostree admin status": %v`, err) 79 } 80 81 oasSplit := strings.Split(string(oasOutput), "\n") 82 if len(oasSplit) < 3 { 83 return oaStatus, fmt.Errorf(`Unexpected output from "ostree admin status": %v`, string(oasOutput)) 84 } 85 86 // we use a bunch of regexps to find the content in each line of output 87 // from "ostree admin status". the `match` for each line sticks the 88 // captured group as the last element of the array; that is used as the 89 // value for each field of the struct 90 reCsum, _ := regexp.Compile(`^\* [\w\-]+ ([0-9a-f]+)\.\d`) 91 csumMatch := reCsum.FindStringSubmatch(oasSplit[0]) 92 if csumMatch == nil { 93 return oaStatus, fmt.Errorf(`Could not parse first line from "ostree admin status": %q`, oasSplit[0]) 94 } 95 oaStatus.Checksum = csumMatch[len(csumMatch)-1] 96 97 reVersion, _ := regexp.Compile(`^Version: (.*)`) 98 versionMatch := reVersion.FindStringSubmatch(strings.TrimSpace(oasSplit[1])) 99 if versionMatch == nil { 100 return oaStatus, fmt.Errorf(`Could not parse second line from "ostree admin status": %q`, oasSplit[1]) 101 } 102 oaStatus.Version = versionMatch[len(versionMatch)-1] 103 104 reOrigin, _ := regexp.Compile(`^origin refspec: (.*)`) 105 originMatch := reOrigin.FindStringSubmatch(strings.TrimSpace(oasSplit[2])) 106 if originMatch == nil { 107 return oaStatus, fmt.Errorf(`Could not parse third line from "ostree admin status": %q`, oasSplit[2]) 108 } 109 oaStatus.Origin = originMatch[len(originMatch)-1] 110 111 return oaStatus, nil 112 } 113 114 // ostreeBasicTest performs sanity checks on the output from `ostree admin status`, 115 // `ostree rev-parse`, and `ostree show` by comparing to the output from `rpm-ostree status` 116 func ostreeBasicTest(c cluster.TestCluster) { 117 m := c.Machines()[0] 118 119 ros, err := util.GetRpmOstreeStatusJSON(c, m) 120 if err != nil { 121 c.Fatal(err) 122 } 123 124 oas, err := getOstreeAdminStatus(c, m) 125 if err != nil { 126 c.Fatal(err) 127 } 128 129 if len(ros.Deployments) < 1 { 130 c.Fatalf(`Did not find any deployments?!`) 131 } 132 133 // verify the output from `ostree admin status` 134 c.Run("admin status", func(c cluster.TestCluster) { 135 if oas.Checksum != ros.Deployments[0].Checksum { 136 c.Fatalf(`Checksums do not match; expected %q, got %q`, ros.Deployments[0].Checksum, oas.Checksum) 137 } 138 if oas.Version != ros.Deployments[0].Version { 139 c.Fatalf(`Versions do not match; expected %q, got %q`, ros.Deployments[0].Version, oas.Version) 140 } 141 if oas.Origin != ros.Deployments[0].Origin { 142 c.Fatalf(`Origins do not match; expected %q, got %q`, ros.Deployments[0].Origin, oas.Origin) 143 } 144 }) 145 146 // verify the output from `ostree rev-parse` 147 // this is kind of moot since the origin for RHCOS is just 148 // the checksum now 149 c.Run("rev-parse", func(c cluster.TestCluster) { 150 // check the output of `ostree rev-parse` 151 oRevParseOut := c.MustSSH(m, ("ostree rev-parse " + oas.Origin)) 152 153 if string(oRevParseOut) != oas.Checksum { 154 c.Fatalf(`Checksum from "ostree rev-parse" does not match; expected %q, got %q`, oas.Checksum, string(oRevParseOut)) 155 } 156 }) 157 158 // verify the output of 'ostree show' 159 c.Run("show", func(c cluster.TestCluster) { 160 oShowOut := c.MustSSH(m, ("ostree show " + oas.Checksum)) 161 oShowOutSplit := strings.Split(string(oShowOut), "\n") 162 // we need at least the first 4 lines (commit, ContentChecksum, Date, Version) 163 // to proceed safely 164 if len(oShowOutSplit) < 4 { 165 c.Fatalf(`Unexpected output from "ostree show": %q`, string(oShowOut)) 166 } 167 168 // convert the 'timestamp' from `rpm-ostree status` into a date that 169 // we can compare to the date in `ostree admin status` 170 // also, wtf is up with formatting date/time in golang?! 171 timeFormat := "2006-01-02 15:04:05 +0000" 172 tsUnix := time.Unix(ros.Deployments[0].Timestamp, 0).UTC() 173 tsFormatted := tsUnix.Format(timeFormat) 174 oShowDate := strings.TrimPrefix(oShowOutSplit[2], "Date: ") 175 176 if oShowDate != tsFormatted { 177 c.Fatalf(`Dates do not match; expected %q, got %q`, tsFormatted, oShowDate) 178 } 179 180 oVersionSplit := strings.Fields(oShowOutSplit[3]) 181 if len(oVersionSplit) < 2 { 182 c.Fatalf(`Unexpected content in "Version" field of "ostree show" output: %q`, oShowOutSplit[3]) 183 } 184 if oVersionSplit[1] != ros.Deployments[0].Version { 185 c.Fatalf(`Versions do not match; expected %q, got %q`, ros.Deployments[0].Version, oVersionSplit[1]) 186 } 187 }) 188 } 189 190 // ostreeRemoteTest verifies the `ostree remote` functionality; 191 // specifically: `add`, `delete`, `list`, `refs`, `show-url`, `summary` 192 func ostreeRemoteTest(c cluster.TestCluster) { 193 m := c.Machines()[0] 194 195 // Get the initial amount of remotes configured 196 initialRemotesNum, _ := getOstreeRemotes(c, m) 197 198 // TODO: if this remote ever changes, update the `refMatch` regexp 199 // in the `ostree remote summary` test below 200 remoteName := "custom" 201 remoteUrl := "https://dl.fedoraproject.org/atomic/repo/" 202 203 // verify `ostree remote add` is successful 204 c.Run("add", func(c cluster.TestCluster) { 205 osRemoteAddCmd := "sudo ostree remote add --no-gpg-verify " + remoteName + " " + remoteUrl 206 c.MustSSH(m, osRemoteAddCmd) 207 }) 208 209 // verify `ostree remote list` 210 c.Run("list", func(c cluster.TestCluster) { 211 osRemoteListOut := c.MustSSH(m, "ostree remote list -u") 212 213 osRemoteListSplit := strings.Split(string(osRemoteListOut), "\n") 214 // should have original remote + newly added remote 215 if len(osRemoteListSplit) != initialRemotesNum+1 { 216 c.Fatalf(`Did not find expected amount of ostree remotes: %q. Expected %d`, string(osRemoteListOut), osRemoteListSplit) 217 } 218 219 var remoteFound bool = false 220 for _, v := range osRemoteListSplit { 221 lSplit := strings.Fields(v) 222 if len(lSplit) != 2 { 223 c.Fatalf(`Unexpected format of ostree remote entry: %q`, v) 224 } 225 if lSplit[0] == remoteName && lSplit[1] == remoteUrl { 226 remoteFound = true 227 } 228 } 229 if !remoteFound { 230 c.Fatalf(`Added remote was not found: %v`, string(osRemoteListOut)) 231 } 232 }) 233 234 // verify `ostree remote show-url` 235 c.Run("show-url", func(c cluster.TestCluster) { 236 osRemoteShowUrlOut := c.MustSSH(m, ("ostree remote show-url " + remoteName)) 237 if string(osRemoteShowUrlOut) != remoteUrl { 238 c.Fatalf(`URLs don't match; expected %q, got %q`, remoteName, string(osRemoteShowUrlOut)) 239 } 240 }) 241 242 // verify `ostree remote refs` 243 c.Run("refs", func(c cluster.TestCluster) { 244 osRemoteRefsOut := c.MustSSH(m, ("ostree remote refs " + remoteName)) 245 if len(strings.Split(string(osRemoteRefsOut), "\n")) < 1 { 246 c.Fatalf(`Did not receive expected amount of refs from remote: %v`, string(osRemoteRefsOut)) 247 } 248 }) 249 250 // verify `ostree remote summary` 251 c.Run("summary", func(c cluster.TestCluster) { 252 remoteRefsOut := c.MustSSH(m, ("ostree remote refs " + remoteName)) 253 remoteRefsOutSplit := strings.Split(string(remoteRefsOut), "\n") 254 remoteRefsCount := len(remoteRefsOutSplit) 255 if remoteRefsCount < 1 { 256 c.Fatalf(`Did not find any refs on ostree remote: %q`, string(remoteRefsOut)) 257 } 258 259 osRemoteSummaryOut := c.MustSSH(m, ("ostree remote summary " + remoteName)) 260 if len(strings.Split(string(osRemoteSummaryOut), "\n")) < 1 { 261 c.Fatalf(`Did not receive expected summary content from remote: %v`, string(osRemoteSummaryOut)) 262 } 263 264 // the remote summary *should* include the same amount of 265 // refs as found in `ostree remote refs` 266 var refCount int = 0 267 268 // this matches the line from the output that includes the name of the ref. 269 // the original remote used in this test is a Fedora remote, so we are checking 270 // for refs particular to that remote. 271 // TODO: if the remote ever changes, we'll have to change this regexp too. 272 refMatch := regexp.MustCompile(`^\* fedora.*`) 273 remoteSummaryOutSplit := strings.Split(string(osRemoteSummaryOut), "\n") 274 // the remote should have at least one ref in the summary 275 if len(remoteSummaryOutSplit) < 4 { 276 c.Fatalf(`Did not find any refs in the remote summary: %q`, string(osRemoteSummaryOut)) 277 } 278 for _, v := range remoteSummaryOutSplit { 279 if refMatch.MatchString(v) { 280 refCount += 1 281 } 282 } 283 284 if refCount == 0 { 285 c.Fatalf(`Did not find any refs in the remote summary that matched!`) 286 } 287 288 if refCount != remoteRefsCount { 289 c.Fatalf(`Did not find correct number of refs in remote summary; expected %q, got %q`, remoteRefsCount, refCount) 290 } 291 }) 292 293 // verify `ostree remote delete` 294 c.Run("delete", func(c cluster.TestCluster) { 295 preRemotesOut := c.MustSSH(m, "ostree remote list") 296 preNumRemotes := len(strings.Split(string(preRemotesOut), "\n")) 297 298 if preNumRemotes < 1 { 299 c.Fatalf(`No remotes configured on host: %q`, string(preRemotesOut)) 300 } 301 302 c.MustSSH(m, ("sudo ostree remote delete " + remoteName)) 303 304 delNumRemotes, delRemoteListOut := getOstreeRemotes(c, m) 305 if delNumRemotes >= preNumRemotes { 306 c.Fatalf(`Number of remotes did not decrease after "ostree delete": %s`, delRemoteListOut) 307 } 308 }) 309 }