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  }