github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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/mvdan/u-root-coreutils/pkg/boot" 17 "github.com/mvdan/u-root-coreutils/pkg/curl" 18 "github.com/mvdan/u-root-coreutils/pkg/uio" 19 "github.com/mvdan/u-root-coreutils/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, premature end", 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` 163 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 164 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 165 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file", content2) 166 s.Register(fs.Scheme, fs) 167 return s 168 }, 169 curl: &url.URL{ 170 Scheme: "http", 171 Host: "someplace.com", 172 Path: "/foobar/pxefiles/ipxeconfig", 173 }, 174 want: &boot.LinuxImage{ 175 Kernel: strings.NewReader(content1), 176 Initrd: strings.NewReader(content2), 177 }, 178 }, 179 { 180 desc: "all files exist, simple config with no cmdline, one relative file path, concatenate initrd", 181 schemeFunc: func() curl.Schemes { 182 s := make(curl.Schemes) 183 fs := curl.NewMockScheme("http") 184 conf := `#!ipxe 185 kernel http://someplace.com/foobar/pxefiles/kernel 186 initrd initrd-file.001,initrd-file.002 187 boot` 188 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 189 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 190 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.001", content512_1) 191 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.002", content512_2) 192 s.Register(fs.Scheme, fs) 193 return s 194 }, 195 curl: &url.URL{ 196 Scheme: "http", 197 Host: "someplace.com", 198 Path: "/foobar/pxefiles/ipxeconfig", 199 }, 200 want: &boot.LinuxImage{ 201 Kernel: strings.NewReader(content1), 202 Initrd: strings.NewReader(content1024), 203 }, 204 }, 205 { 206 desc: "all files exist, simple config with no cmdline, one relative file path, multiline initrd", 207 schemeFunc: func() curl.Schemes { 208 s := make(curl.Schemes) 209 fs := curl.NewMockScheme("http") 210 conf := `#!ipxe 211 kernel http://someplace.com/foobar/pxefiles/kernel 212 initrd initrd-file.001 213 initrd initrd-file.002 214 boot` 215 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 216 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 217 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.001", content512_1) 218 fs.Add("someplace.com", "/foobar/pxefiles/initrd-file.002", content512_2) 219 s.Register(fs.Scheme, fs) 220 return s 221 }, 222 curl: &url.URL{ 223 Scheme: "http", 224 Host: "someplace.com", 225 Path: "/foobar/pxefiles/ipxeconfig", 226 }, 227 want: &boot.LinuxImage{ 228 Kernel: strings.NewReader(content1), 229 Initrd: strings.NewReader(content1024), 230 }, 231 }, 232 { 233 desc: "all files exist, simple config, no initrd", 234 schemeFunc: func() curl.Schemes { 235 s := make(curl.Schemes) 236 fs := curl.NewMockScheme("http") 237 conf := `#!ipxe 238 kernel http://someplace.com/foobar/pxefiles/kernel 239 boot` 240 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 241 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 242 s.Register(fs.Scheme, fs) 243 return s 244 }, 245 curl: &url.URL{ 246 Scheme: "http", 247 Host: "someplace.com", 248 Path: "/foobar/pxefiles/ipxeconfig", 249 }, 250 want: &boot.LinuxImage{ 251 Kernel: strings.NewReader(content1), 252 }, 253 }, 254 { 255 desc: "comments and blank lines", 256 schemeFunc: func() curl.Schemes { 257 s := make(curl.Schemes) 258 fs := curl.NewMockScheme("http") 259 conf := `#!ipxe 260 # the next line is blank 261 262 kernel http://someplace.com/foobar/pxefiles/kernel 263 boot` 264 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 265 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 266 s.Register(fs.Scheme, fs) 267 return s 268 }, 269 curl: &url.URL{ 270 Scheme: "http", 271 Host: "someplace.com", 272 Path: "/foobar/pxefiles/ipxeconfig", 273 }, 274 want: &boot.LinuxImage{ 275 Kernel: strings.NewReader(content1), 276 }, 277 }, 278 { 279 desc: "kernel does not exist, simple config", 280 schemeFunc: func() curl.Schemes { 281 s := make(curl.Schemes) 282 fs := curl.NewMockScheme("http") 283 conf := `#!ipxe 284 kernel http://someplace.com/foobar/pxefiles/kernel 285 boot` 286 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 287 s.Register(fs.Scheme, fs) 288 return s 289 }, 290 curl: &url.URL{ 291 Scheme: "http", 292 Host: "someplace.com", 293 Path: "/foobar/pxefiles/ipxeconfig", 294 }, 295 want: &boot.LinuxImage{ 296 Kernel: errorReader{&curl.URLError{ 297 URL: &url.URL{ 298 Scheme: "http", 299 Host: "someplace.com", 300 Path: "/foobar/pxefiles/kernel", 301 }, 302 Err: curl.ErrNoSuchFile, 303 }}, 304 Initrd: nil, 305 Cmdline: "", 306 }, 307 }, 308 { 309 desc: "config file does not exist", 310 schemeFunc: func() curl.Schemes { 311 s := make(curl.Schemes) 312 fs := curl.NewMockScheme("http") 313 s.Register(fs.Scheme, fs) 314 return s 315 }, 316 curl: &url.URL{ 317 Scheme: "http", 318 Host: "someplace.com", 319 Path: "/foobar/pxefiles/ipxeconfig", 320 }, 321 err: &curl.URLError{ 322 URL: &url.URL{ 323 Scheme: "http", 324 Host: "someplace.com", 325 Path: "/foobar/pxefiles/ipxeconfig", 326 }, 327 Err: curl.ErrNoSuchHost, 328 }, 329 }, 330 { 331 desc: "invalid config", 332 schemeFunc: func() curl.Schemes { 333 s := make(curl.Schemes) 334 fs := curl.NewMockScheme("http") 335 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", "") 336 s.Register(fs.Scheme, fs) 337 return s 338 }, 339 curl: &url.URL{ 340 Scheme: "http", 341 Host: "someplace.com", 342 Path: "/foobar/pxefiles/ipxeconfig", 343 }, 344 err: ErrNotIpxeScript, 345 }, 346 { 347 desc: "empty config", 348 schemeFunc: func() curl.Schemes { 349 s := make(curl.Schemes) 350 fs := curl.NewMockScheme("http") 351 conf := `#!ipxe` 352 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 353 s.Register(fs.Scheme, fs) 354 return s 355 }, 356 curl: &url.URL{ 357 Scheme: "http", 358 Host: "someplace.com", 359 Path: "/foobar/pxefiles/ipxeconfig", 360 }, 361 want: &boot.LinuxImage{}, 362 }, 363 { 364 desc: "valid config with kernel cmdline args", 365 schemeFunc: func() curl.Schemes { 366 s := make(curl.Schemes) 367 fs := curl.NewMockScheme("http") 368 conf := `#!ipxe 369 kernel http://someplace.com/foobar/pxefiles/kernel earlyprintk=ttyS0 printk=ttyS0 370 boot` 371 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 372 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 373 s.Register(fs.Scheme, fs) 374 return s 375 }, 376 curl: &url.URL{ 377 Scheme: "http", 378 Host: "someplace.com", 379 Path: "/foobar/pxefiles/ipxeconfig", 380 }, 381 want: &boot.LinuxImage{ 382 Kernel: strings.NewReader(content1), 383 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 384 }, 385 }, 386 { 387 desc: "multi-scheme valid config", 388 schemeFunc: func() curl.Schemes { 389 conf := `#!ipxe 390 kernel tftp://1.2.3.4/foobar/pxefiles/kernel 391 initrd http://someplace.com/someinitrd.gz 392 boot` 393 394 tftp := curl.NewMockScheme("tftp") 395 tftp.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1) 396 397 http := curl.NewMockScheme("http") 398 http.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 399 http.Add("someplace.com", "/someinitrd.gz", content2) 400 401 s := make(curl.Schemes) 402 s.Register(tftp.Scheme, tftp) 403 s.Register(http.Scheme, http) 404 return s 405 }, 406 curl: &url.URL{ 407 Scheme: "http", 408 Host: "someplace.com", 409 Path: "/foobar/pxefiles/ipxeconfig", 410 }, 411 want: &boot.LinuxImage{ 412 Kernel: strings.NewReader(content1), 413 Initrd: strings.NewReader(content2), 414 }, 415 }, 416 { 417 desc: "valid config with unsupported cmds", 418 schemeFunc: func() curl.Schemes { 419 s := make(curl.Schemes) 420 fs := curl.NewMockScheme("http") 421 conf := `#!ipxe 422 kernel http://someplace.com/foobar/pxefiles/kernel 423 initrd http://someplace.com/someinitrd.gz 424 set ip 0.0.0.0 425 boot` 426 fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf) 427 fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1) 428 fs.Add("someplace.com", "/someinitrd.gz", content2) 429 s.Register(fs.Scheme, fs) 430 return s 431 }, 432 curl: &url.URL{ 433 Scheme: "http", 434 Host: "someplace.com", 435 Path: "/foobar/pxefiles/ipxeconfig", 436 }, 437 want: &boot.LinuxImage{ 438 Kernel: strings.NewReader(content1), 439 Initrd: strings.NewReader(content2), 440 }, 441 }, 442 } { 443 t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) { 444 got, err := ParseConfig(context.Background(), ulogtest.Logger{t}, tt.curl, tt.schemeFunc()) 445 if !reflect.DeepEqual(err, tt.err) { 446 t.Errorf("ParseConfig() got %v, want %v", err, tt.err) 447 return 448 } else if err != nil { 449 return 450 } 451 want := tt.want 452 453 // Same kernel? 454 if !uio.ReaderAtEqual(got.Kernel, want.Kernel) { 455 t.Errorf("got kernel %s, want %s", mustReadAll(got.Kernel), mustReadAll(want.Kernel)) 456 } 457 // Same initrd? 458 if !uio.ReaderAtEqual(got.Initrd, want.Initrd) { 459 t.Errorf("got initrd %s, want %s", mustReadAll(got.Initrd), mustReadAll(want.Initrd)) 460 } 461 // Same cmdline? 462 if got.Cmdline != want.Cmdline { 463 t.Errorf("got cmdline %s, want %s", got.Cmdline, want.Cmdline) 464 } 465 }) 466 } 467 }