github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/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  
    91  	for i, tt := range []struct {
    92  		desc       string
    93  		schemeFunc func() curl.Schemes
    94  		curl       *url.URL
    95  		want       *boot.LinuxImage
    96  		err        error
    97  	}{
    98  		{
    99  			desc: "all files exist, simple config with no cmdline, one relative file path",
   100  			schemeFunc: func() curl.Schemes {
   101  				s := make(curl.Schemes)
   102  				fs := curl.NewMockScheme("http")
   103  				conf := `#!ipxe
   104  				kernel http://someplace.com/foobar/pxefiles/kernel
   105  				initrd initrd-file
   106  				boot`
   107  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   108  				fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1)
   109  				fs.Add("someplace.com", "/foobar/pxefiles/initrd-file", content2)
   110  				s.Register(fs.Scheme, fs)
   111  				return s
   112  			},
   113  			curl: &url.URL{
   114  				Scheme: "http",
   115  				Host:   "someplace.com",
   116  				Path:   "/foobar/pxefiles/ipxeconfig",
   117  			},
   118  			want: &boot.LinuxImage{
   119  				Kernel: strings.NewReader(content1),
   120  				Initrd: strings.NewReader(content2),
   121  			},
   122  		},
   123  		{
   124  			desc: "all files exist, simple config, no initrd",
   125  			schemeFunc: func() curl.Schemes {
   126  				s := make(curl.Schemes)
   127  				fs := curl.NewMockScheme("http")
   128  				conf := `#!ipxe
   129  				kernel http://someplace.com/foobar/pxefiles/kernel
   130  				boot`
   131  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   132  				fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1)
   133  				s.Register(fs.Scheme, fs)
   134  				return s
   135  			},
   136  			curl: &url.URL{
   137  				Scheme: "http",
   138  				Host:   "someplace.com",
   139  				Path:   "/foobar/pxefiles/ipxeconfig",
   140  			},
   141  			want: &boot.LinuxImage{
   142  				Kernel: strings.NewReader(content1),
   143  			},
   144  		},
   145  		{
   146  			desc: "comments and blank lines",
   147  			schemeFunc: func() curl.Schemes {
   148  				s := make(curl.Schemes)
   149  				fs := curl.NewMockScheme("http")
   150  				conf := `#!ipxe
   151  				# the next line is blank
   152  
   153  				kernel http://someplace.com/foobar/pxefiles/kernel
   154  				boot`
   155  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   156  				fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1)
   157  				s.Register(fs.Scheme, fs)
   158  				return s
   159  			},
   160  			curl: &url.URL{
   161  				Scheme: "http",
   162  				Host:   "someplace.com",
   163  				Path:   "/foobar/pxefiles/ipxeconfig",
   164  			},
   165  			want: &boot.LinuxImage{
   166  				Kernel: strings.NewReader(content1),
   167  			},
   168  		},
   169  		{
   170  			desc: "kernel does not exist, simple config",
   171  			schemeFunc: func() curl.Schemes {
   172  				s := make(curl.Schemes)
   173  				fs := curl.NewMockScheme("http")
   174  				conf := `#!ipxe
   175  				kernel http://someplace.com/foobar/pxefiles/kernel
   176  				boot`
   177  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   178  				s.Register(fs.Scheme, fs)
   179  				return s
   180  			},
   181  			curl: &url.URL{
   182  				Scheme: "http",
   183  				Host:   "someplace.com",
   184  				Path:   "/foobar/pxefiles/ipxeconfig",
   185  			},
   186  			want: &boot.LinuxImage{
   187  				Kernel: errorReader{&curl.URLError{
   188  					URL: &url.URL{
   189  						Scheme: "http",
   190  						Host:   "someplace.com",
   191  						Path:   "/foobar/pxefiles/kernel",
   192  					},
   193  					Err: curl.ErrNoSuchFile,
   194  				}},
   195  				Initrd:  nil,
   196  				Cmdline: "",
   197  			},
   198  		},
   199  		{
   200  			desc: "config file does not exist",
   201  			schemeFunc: func() curl.Schemes {
   202  				s := make(curl.Schemes)
   203  				fs := curl.NewMockScheme("http")
   204  				s.Register(fs.Scheme, fs)
   205  				return s
   206  			},
   207  			curl: &url.URL{
   208  				Scheme: "http",
   209  				Host:   "someplace.com",
   210  				Path:   "/foobar/pxefiles/ipxeconfig",
   211  			},
   212  			err: &curl.URLError{
   213  				URL: &url.URL{
   214  					Scheme: "http",
   215  					Host:   "someplace.com",
   216  					Path:   "/foobar/pxefiles/ipxeconfig",
   217  				},
   218  				Err: curl.ErrNoSuchHost,
   219  			},
   220  		},
   221  		{
   222  			desc: "invalid config",
   223  			schemeFunc: func() curl.Schemes {
   224  				s := make(curl.Schemes)
   225  				fs := curl.NewMockScheme("http")
   226  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", "")
   227  				s.Register(fs.Scheme, fs)
   228  				return s
   229  			},
   230  			curl: &url.URL{
   231  				Scheme: "http",
   232  				Host:   "someplace.com",
   233  				Path:   "/foobar/pxefiles/ipxeconfig",
   234  			},
   235  			err: ErrNotIpxeScript,
   236  		},
   237  		{
   238  			desc: "empty config",
   239  			schemeFunc: func() curl.Schemes {
   240  				s := make(curl.Schemes)
   241  				fs := curl.NewMockScheme("http")
   242  				conf := `#!ipxe`
   243  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   244  				s.Register(fs.Scheme, fs)
   245  				return s
   246  			},
   247  			curl: &url.URL{
   248  				Scheme: "http",
   249  				Host:   "someplace.com",
   250  				Path:   "/foobar/pxefiles/ipxeconfig",
   251  			},
   252  			want: &boot.LinuxImage{},
   253  		},
   254  		{
   255  			desc: "valid config with kernel cmdline args",
   256  			schemeFunc: func() curl.Schemes {
   257  				s := make(curl.Schemes)
   258  				fs := curl.NewMockScheme("http")
   259  				conf := `#!ipxe
   260  				kernel http://someplace.com/foobar/pxefiles/kernel earlyprintk=ttyS0 printk=ttyS0
   261  				boot`
   262  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   263  				fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1)
   264  				s.Register(fs.Scheme, fs)
   265  				return s
   266  			},
   267  			curl: &url.URL{
   268  				Scheme: "http",
   269  				Host:   "someplace.com",
   270  				Path:   "/foobar/pxefiles/ipxeconfig",
   271  			},
   272  			want: &boot.LinuxImage{
   273  				Kernel:  strings.NewReader(content1),
   274  				Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   275  			},
   276  		},
   277  		{
   278  			desc: "multi-scheme valid config",
   279  			schemeFunc: func() curl.Schemes {
   280  				conf := `#!ipxe
   281  				kernel tftp://1.2.3.4/foobar/pxefiles/kernel
   282                                  initrd http://someplace.com/someinitrd.gz
   283  				boot`
   284  
   285  				tftp := curl.NewMockScheme("tftp")
   286  				tftp.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   287  
   288  				http := curl.NewMockScheme("http")
   289  				http.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   290  				http.Add("someplace.com", "/someinitrd.gz", content2)
   291  
   292  				s := make(curl.Schemes)
   293  				s.Register(tftp.Scheme, tftp)
   294  				s.Register(http.Scheme, http)
   295  				return s
   296  			},
   297  			curl: &url.URL{
   298  				Scheme: "http",
   299  				Host:   "someplace.com",
   300  				Path:   "/foobar/pxefiles/ipxeconfig",
   301  			},
   302  			want: &boot.LinuxImage{
   303  				Kernel: strings.NewReader(content1),
   304  				Initrd: strings.NewReader(content2),
   305  			},
   306  		},
   307  		{
   308  			desc: "valid config with unsupported cmds",
   309  			schemeFunc: func() curl.Schemes {
   310  				s := make(curl.Schemes)
   311  				fs := curl.NewMockScheme("http")
   312  				conf := `#!ipxe
   313  				kernel http://someplace.com/foobar/pxefiles/kernel
   314                                  initrd http://someplace.com/someinitrd.gz
   315                                  set ip 0.0.0.0
   316  				boot`
   317  				fs.Add("someplace.com", "/foobar/pxefiles/ipxeconfig", conf)
   318  				fs.Add("someplace.com", "/foobar/pxefiles/kernel", content1)
   319  				fs.Add("someplace.com", "/someinitrd.gz", content2)
   320  				s.Register(fs.Scheme, fs)
   321  				return s
   322  			},
   323  			curl: &url.URL{
   324  				Scheme: "http",
   325  				Host:   "someplace.com",
   326  				Path:   "/foobar/pxefiles/ipxeconfig",
   327  			},
   328  			want: &boot.LinuxImage{
   329  				Kernel: strings.NewReader(content1),
   330  				Initrd: strings.NewReader(content2),
   331  			},
   332  		},
   333  	} {
   334  		t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) {
   335  			got, err := ParseConfig(context.Background(), ulogtest.Logger{t}, tt.curl, tt.schemeFunc())
   336  			if !reflect.DeepEqual(err, tt.err) {
   337  				t.Errorf("ParseConfig() got %v, want %v", err, tt.err)
   338  				return
   339  			} else if err != nil {
   340  				return
   341  			}
   342  			want := tt.want
   343  
   344  			// Same kernel?
   345  			if !uio.ReaderAtEqual(got.Kernel, want.Kernel) {
   346  				t.Errorf("got kernel %s, want %s", mustReadAll(got.Kernel), mustReadAll(want.Kernel))
   347  			}
   348  			// Same initrd?
   349  			if !uio.ReaderAtEqual(got.Initrd, want.Initrd) {
   350  				t.Errorf("got initrd %s, want %s", mustReadAll(got.Initrd), mustReadAll(want.Initrd))
   351  			}
   352  			// Same cmdline?
   353  			if got.Cmdline != want.Cmdline {
   354  				t.Errorf("got cmdline %s, want %s", got.Cmdline, want.Cmdline)
   355  			}
   356  		})
   357  	}
   358  }