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  }