github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/netboot/ipxe/ipxe_test.go (about) 1 // Copyright 2017-2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ipxe 6 7 import ( 8 "context" 9 "fmt" 10 "io" 11 "net/url" 12 "reflect" 13 "strings" 14 "testing" 15 16 "github.com/u-root/u-root/pkg/boot" 17 "github.com/u-root/u-root/pkg/curl" 18 "github.com/u-root/u-root/pkg/uio" 19 "github.com/u-root/u-root/pkg/ulog/ulogtest" 20 ) 21 22 func mustReadAll(r io.ReaderAt) string { 23 if r == nil { 24 return "" 25 } 26 b, err := uio.ReadAll(r) 27 if err != nil { 28 return fmt.Sprintf("read error: %s", err) 29 } 30 return string(b) 31 } 32 33 type errorReader struct { 34 err error 35 } 36 37 func (e errorReader) ReadAt(p []byte, n int64) (int, error) { 38 return 0, e.err 39 } 40 41 func mustParseURL(s string) *url.URL { 42 u, err := url.Parse(s) 43 if err != nil { 44 panic(fmt.Sprintf("parsing %q failed: %v", s, err)) 45 } 46 return u 47 } 48 49 func TestParseURL(t *testing.T) { 50 for _, tt := range []struct { 51 filename string 52 wd *url.URL 53 want *url.URL 54 }{ 55 { 56 filename: "foobar", 57 wd: mustParseURL("http://[2001::1]:18282/files/more"), 58 want: mustParseURL("http://[2001::1]:18282/files/more/foobar"), 59 }, 60 { 61 filename: "/foobar", 62 wd: mustParseURL("http://[2001::1]:18282/files/more"), 63 want: mustParseURL("http://[2001::1]:18282/foobar"), 64 }, 65 { 66 filename: "http://[2002::2]/blabla", 67 wd: mustParseURL("http://[2001::1]:18282/files/more"), 68 want: mustParseURL("http://[2002::2]/blabla"), 69 }, 70 { 71 filename: "http://[2002::2]/blabla", 72 wd: nil, 73 want: mustParseURL("http://[2002::2]/blabla"), 74 }, 75 } { 76 got, err := parseURL(tt.filename, tt.wd) 77 if err != nil { 78 t.Errorf("parseURL(%q, %s) = %v, want %v", tt.filename, tt.wd, err, nil) 79 } 80 81 if !reflect.DeepEqual(got, tt.want) { 82 t.Errorf("parseURL(%q, %s) = %v, want %v", tt.filename, tt.wd, got, tt.want) 83 } 84 } 85 } 86 87 func TestIpxeConfig(t *testing.T) { 88 content1 := "1111" 89 content2 := "2222" 90 content512_1 := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 91 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 92 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 93 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 94 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 95 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 96 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 97 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 98 content512_2 := "h123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 99 "he23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 100 "hell456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 101 "hello56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 102 "hellow6789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 103 "hellowo789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 104 "hellowor89abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 105 "helloworl9abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 106 content1024 := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 107 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 108 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 109 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 110 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 111 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 112 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 113 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 114 "h123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 115 "he23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 116 "hell456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 117 "hello56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 118 "hellow6789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 119 "hellowo789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 120 "hellowor89abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + 121 "helloworl9abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 122 123 for i, tt := range []struct { 124 desc string 125 schemeFunc func() curl.Schemes 126 curl *url.URL 127 want *boot.LinuxImage 128 err error 129 }{ 130 { 131 desc: "all files exist, simple config with no cmdline, one relative file path", 132 schemeFunc: func() curl.Schemes { 133 s := make(curl.Schemes) 134 fs := curl.NewMockScheme("http") 135 conf := `#!ipxe 136 kernel http://someplace.com/foobar/pxefiles/kernel 137 initrd initrd-file 138 boot` 139 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 140 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 141 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file", content2) 142 s.Register(fs.Scheme, fs) 143 return s 144 }, 145 curl: &url.URL{ 146 Scheme: "http", 147 Host: "someplace.com", 148 Path: "/foobar/pxefiles/ipxeconfig", 149 }, 150 want: &boot.LinuxImage{ 151 Kernel: strings.NewReader(content1), 152 Initrd: strings.NewReader(content2), 153 }, 154 }, 155 { 156 desc: "all files exist, simple config with no cmdline, one relative file path, concatenate initrd", 157 schemeFunc: func() curl.Schemes { 158 s := make(curl.Schemes) 159 fs := curl.NewMockScheme("http") 160 conf := `#!ipxe 161 kernel http://someplace.com/foobar/pxefiles/kernel 162 initrd initrd-file.001,initrd-file.002 163 boot` 164 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 165 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 166 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.001", content512_1) 167 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.002", content512_2) 168 s.Register(fs.Scheme, fs) 169 return s 170 }, 171 curl: &url.URL{ 172 Scheme: "http", 173 Host: "someplace.com", 174 Path: "/foobar/pxefiles/ipxeconfig", 175 }, 176 want: &boot.LinuxImage{ 177 Kernel: strings.NewReader(content1), 178 Initrd: strings.NewReader(content1024), 179 }, 180 }, 181 { 182 desc: "all files exist, simple config, no initrd", 183 schemeFunc: func() curl.Schemes { 184 s := make(curl.Schemes) 185 fs := curl.NewMockScheme("http") 186 conf := `#!ipxe 187 kernel http://someplace.com/foobar/pxefiles/kernel 188 boot` 189 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 190 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 191 s.Register(fs.Scheme, fs) 192 return s 193 }, 194 curl: &url.URL{ 195 Scheme: "http", 196 Host: "someplace.com", 197 Path: "/foobar/pxefiles/ipxeconfig", 198 }, 199 want: &boot.LinuxImage{ 200 Kernel: strings.NewReader(content1), 201 }, 202 }, 203 { 204 desc: "comments and blank lines", 205 schemeFunc: func() curl.Schemes { 206 s := make(curl.Schemes) 207 fs := curl.NewMockScheme("http") 208 conf := `#!ipxe 209 # the next line is blank 210 211 kernel http://someplace.com/foobar/pxefiles/kernel 212 boot` 213 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 214 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 215 s.Register(fs.Scheme, fs) 216 return s 217 }, 218 curl: &url.URL{ 219 Scheme: "http", 220 Host: "someplace.com", 221 Path: "/foobar/pxefiles/ipxeconfig", 222 }, 223 want: &boot.LinuxImage{ 224 Kernel: strings.NewReader(content1), 225 }, 226 }, 227 { 228 desc: "kernel does not exist, simple config", 229 schemeFunc: func() curl.Schemes { 230 s := make(curl.Schemes) 231 fs := curl.NewMockScheme("http") 232 conf := `#!ipxe 233 kernel http://someplace.com/foobar/pxefiles/kernel 234 boot` 235 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 236 s.Register(fs.Scheme, fs) 237 return s 238 }, 239 curl: &url.URL{ 240 Scheme: "http", 241 Host: "someplace.com", 242 Path: "/foobar/pxefiles/ipxeconfig", 243 }, 244 want: &boot.LinuxImage{ 245 Kernel: errorReader{&curl.URLError{ 246 URL: &url.URL{ 247 Scheme: "http", 248 Host: "someplace.com", 249 Path: "/foobar/pxefiles/kernel", 250 }, 251 Err: curl.ErrNoSuchFile, 252 }}, 253 Initrd: nil, 254 Cmdline: "", 255 }, 256 }, 257 { 258 desc: "config file does not exist", 259 schemeFunc: func() curl.Schemes { 260 s := make(curl.Schemes) 261 fs := curl.NewMockScheme("http") 262 s.Register(fs.Scheme, fs) 263 return s 264 }, 265 curl: &url.URL{ 266 Scheme: "http", 267 Host: "someplace.com", 268 Path: "/foobar/pxefiles/ipxeconfig", 269 }, 270 err: &curl.URLError{ 271 URL: &url.URL{ 272 Scheme: "http", 273 Host: "someplace.com", 274 Path: "/foobar/pxefiles/ipxeconfig", 275 }, 276 Err: curl.ErrNoSuchHost, 277 }, 278 }, 279 { 280 desc: "invalid config", 281 schemeFunc: func() curl.Schemes { 282 s := make(curl.Schemes) 283 fs := curl.NewMockScheme("http") 284 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", "") 285 s.Register(fs.Scheme, fs) 286 return s 287 }, 288 curl: &url.URL{ 289 Scheme: "http", 290 Host: "someplace.com", 291 Path: "/foobar/pxefiles/ipxeconfig", 292 }, 293 err: ErrNotIpxeScript, 294 }, 295 { 296 desc: "empty config", 297 schemeFunc: func() curl.Schemes { 298 s := make(curl.Schemes) 299 fs := curl.NewMockScheme("http") 300 conf := `#!ipxe` 301 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 302 s.Register(fs.Scheme, fs) 303 return s 304 }, 305 curl: &url.URL{ 306 Scheme: "http", 307 Host: "someplace.com", 308 Path: "/foobar/pxefiles/ipxeconfig", 309 }, 310 want: &boot.LinuxImage{}, 311 }, 312 { 313 desc: "valid config with kernel cmdline args", 314 schemeFunc: func() curl.Schemes { 315 s := make(curl.Schemes) 316 fs := curl.NewMockScheme("http") 317 conf := `#!ipxe 318 kernel http://someplace.com/foobar/pxefiles/kernel earlyprintk=ttyS0 printk=ttyS0 319 boot` 320 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 321 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 322 s.Register(fs.Scheme, fs) 323 return s 324 }, 325 curl: &url.URL{ 326 Scheme: "http", 327 Host: "someplace.com", 328 Path: "/foobar/pxefiles/ipxeconfig", 329 }, 330 want: &boot.LinuxImage{ 331 Kernel: strings.NewReader(content1), 332 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 333 }, 334 }, 335 { 336 desc: "multi-scheme valid config", 337 schemeFunc: func() curl.Schemes { 338 conf := `#!ipxe 339 kernel tftp://1.2.3.4/foobar/pxefiles/kernel 340 initrd http://someplace.com/someinitrd.gz 341 boot` 342 343 tftp := curl.NewMockScheme("tftp") 344 tftp.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1) 345 346 http := curl.NewMockScheme("http") 347 http.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 348 http.Add("someplace.com", "/someinitrd.gz", content2) 349 350 s := make(curl.Schemes) 351 s.Register(tftp.Scheme, tftp) 352 s.Register(http.Scheme, http) 353 return s 354 }, 355 curl: &url.URL{ 356 Scheme: "http", 357 Host: "someplace.com", 358 Path: "/foobar/pxefiles/ipxeconfig", 359 }, 360 want: &boot.LinuxImage{ 361 Kernel: strings.NewReader(content1), 362 Initrd: strings.NewReader(content2), 363 }, 364 }, 365 { 366 desc: "valid config with unsupported cmds", 367 schemeFunc: func() curl.Schemes { 368 s := make(curl.Schemes) 369 fs := curl.NewMockScheme("http") 370 conf := `#!ipxe 371 kernel http://someplace.com/foobar/pxefiles/kernel 372 initrd http://someplace.com/someinitrd.gz 373 set ip 0.0.0.0 374 boot` 375 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 376 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 377 fs.Add("someplace.com", "/someinitrd.gz", content2) 378 s.Register(fs.Scheme, fs) 379 return s 380 }, 381 curl: &url.URL{ 382 Scheme: "http", 383 Host: "someplace.com", 384 Path: "/foobar/pxefiles/ipxeconfig", 385 }, 386 want: &boot.LinuxImage{ 387 Kernel: strings.NewReader(content1), 388 Initrd: strings.NewReader(content2), 389 }, 390 }, 391 } { 392 t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) { 393 got, err := ParseConfig(context.Background(), ulogtest.Logger{t}, tt.curl, tt.schemeFunc()) 394 if !reflect.DeepEqual(err, tt.err) { 395 t.Errorf("ParseConfig() got %v, want %v", err, tt.err) 396 return 397 } else if err != nil { 398 return 399 } 400 want := tt.want 401 402 // Same kernel? 403 if !uio.ReaderAtEqual(got.Kernel, want.Kernel) { 404 t.Errorf("got kernel %s, want %s", mustReadAll(got.Kernel), mustReadAll(want.Kernel)) 405 } 406 // Same initrd? 407 if !uio.ReaderAtEqual(got.Initrd, want.Initrd) { 408 t.Errorf("got initrd %s, want %s", mustReadAll(got.Initrd), mustReadAll(want.Initrd)) 409 } 410 // Same cmdline? 411 if got.Cmdline != want.Cmdline { 412 t.Errorf("got cmdline %s, want %s", got.Cmdline, want.Cmdline) 413 } 414 }) 415 } 416 }