github.com/moby/docker@v26.1.3+incompatible/libnetwork/etchosts/etchosts_test.go (about)

     1  package etchosts
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"golang.org/x/sync/errgroup"
    11  	"gotest.tools/v3/assert"
    12  	is "gotest.tools/v3/assert/cmp"
    13  )
    14  
    15  func TestBuildDefault(t *testing.T) {
    16  	file, err := os.CreateTemp("", "")
    17  	if err != nil {
    18  		t.Fatal(err)
    19  	}
    20  	defer os.Remove(file.Name())
    21  
    22  	// check that /etc/hosts has consistent ordering
    23  	for i := 0; i <= 5; i++ {
    24  		err = Build(file.Name(), nil)
    25  		if err != nil {
    26  			t.Fatal(err)
    27  		}
    28  
    29  		content, err := os.ReadFile(file.Name())
    30  		if err != nil {
    31  			t.Fatal(err)
    32  		}
    33  		expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
    34  
    35  		if expected != string(content) {
    36  			t.Fatalf("Expected to find '%s' got '%s'", expected, content)
    37  		}
    38  	}
    39  }
    40  
    41  func TestBuildNoIPv6(t *testing.T) {
    42  	d := t.TempDir()
    43  	filename := filepath.Join(d, "hosts")
    44  
    45  	err := BuildNoIPv6(filename, []Record{
    46  		{
    47  			Hosts: "another.example",
    48  			IP:    "fdbb:c59c:d015::3",
    49  		},
    50  		{
    51  			Hosts: "another.example",
    52  			IP:    "10.11.12.13",
    53  		},
    54  	})
    55  	assert.NilError(t, err)
    56  	content, err := os.ReadFile(filename)
    57  	assert.NilError(t, err)
    58  	assert.Check(t, is.DeepEqual(string(content), "127.0.0.1\tlocalhost\n10.11.12.13\tanother.example\n"))
    59  }
    60  
    61  func TestUpdate(t *testing.T) {
    62  	file, err := os.CreateTemp("", "")
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	defer os.Remove(file.Name())
    67  
    68  	if err := Build(file.Name(), []Record{
    69  		{
    70  			"testhostname.testdomainname testhostname",
    71  			"10.11.12.13",
    72  		},
    73  	}); err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	content, err := os.ReadFile(file.Name())
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  
    82  	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
    83  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
    84  	}
    85  
    86  	if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
    87  		t.Fatal(err)
    88  	}
    89  
    90  	content, err = os.ReadFile(file.Name())
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
    96  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
    97  	}
    98  }
    99  
   100  // This regression test ensures that when a host is given a new IP
   101  // via the Update function that other hosts which start with the
   102  // same name as the targeted host are not erroneously updated as well.
   103  // In the test example, if updating a host called "prefix", unrelated
   104  // hosts named "prefixAndMore" or "prefix2" or anything else starting
   105  // with "prefix" should not be changed. For more information see
   106  // GitHub issue #603.
   107  func TestUpdateIgnoresPrefixedHostname(t *testing.T) {
   108  	file, err := os.CreateTemp("", "")
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	defer os.Remove(file.Name())
   113  
   114  	if err := Build(file.Name(), []Record{
   115  		{
   116  			Hosts: "prefix",
   117  			IP:    "2.2.2.2",
   118  		},
   119  		{
   120  			Hosts: "prefixAndMore",
   121  			IP:    "3.3.3.3",
   122  		},
   123  		{
   124  			Hosts: "unaffectedHost",
   125  			IP:    "4.4.4.4",
   126  		},
   127  	}); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  
   131  	content, err := os.ReadFile(file.Name())
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	if expected := "2.2.2.2\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
   137  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   138  	}
   139  
   140  	if err := Update(file.Name(), "5.5.5.5", "prefix"); err != nil {
   141  		t.Fatal(err)
   142  	}
   143  
   144  	content, err = os.ReadFile(file.Name())
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  
   149  	if expected := "5.5.5.5\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
   150  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   151  	}
   152  }
   153  
   154  // This regression test covers the host prefix issue for the
   155  // Delete function. In the test example, if deleting a host called
   156  // "prefix", an unrelated host called "prefixAndMore" should not
   157  // be deleted. For more information see GitHub issue #603.
   158  func TestDeleteIgnoresPrefixedHostname(t *testing.T) {
   159  	file, err := os.CreateTemp("", "")
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer os.Remove(file.Name())
   164  
   165  	err = Build(file.Name(), nil)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	if err := Add(file.Name(), []Record{
   171  		{
   172  			Hosts: "prefix",
   173  			IP:    "1.1.1.1",
   174  		},
   175  		{
   176  			Hosts: "prefixAndMore",
   177  			IP:    "2.2.2.2",
   178  		},
   179  	}); err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	if err := Delete(file.Name(), []Record{
   184  		{
   185  			Hosts: "prefix",
   186  			IP:    "1.1.1.1",
   187  		},
   188  	}); err != nil {
   189  		t.Fatal(err)
   190  	}
   191  
   192  	content, err := os.ReadFile(file.Name())
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  
   197  	if expected := "2.2.2.2\tprefixAndMore\n"; !bytes.Contains(content, []byte(expected)) {
   198  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   199  	}
   200  
   201  	if expected := "1.1.1.1\tprefix\n"; bytes.Contains(content, []byte(expected)) {
   202  		t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
   203  	}
   204  }
   205  
   206  func TestAddEmpty(t *testing.T) {
   207  	file, err := os.CreateTemp("", "")
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	defer os.Remove(file.Name())
   212  
   213  	err = Build(file.Name(), nil)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	if err := Add(file.Name(), []Record{}); err != nil {
   219  		t.Fatal(err)
   220  	}
   221  }
   222  
   223  func TestAdd(t *testing.T) {
   224  	file, err := os.CreateTemp("", "")
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	defer os.Remove(file.Name())
   229  
   230  	err = Build(file.Name(), nil)
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  
   235  	if err := Add(file.Name(), []Record{
   236  		{
   237  			Hosts: "testhostname",
   238  			IP:    "2.2.2.2",
   239  		},
   240  	}); err != nil {
   241  		t.Fatal(err)
   242  	}
   243  
   244  	content, err := os.ReadFile(file.Name())
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	if expected := "2.2.2.2\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
   250  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   251  	}
   252  }
   253  
   254  func TestDeleteEmpty(t *testing.T) {
   255  	file, err := os.CreateTemp("", "")
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	defer os.Remove(file.Name())
   260  
   261  	err = Build(file.Name(), nil)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	if err := Delete(file.Name(), []Record{}); err != nil {
   267  		t.Fatal(err)
   268  	}
   269  }
   270  
   271  func TestDeleteNewline(t *testing.T) {
   272  	file, err := os.CreateTemp("", "")
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	defer os.Remove(file.Name())
   277  
   278  	b := []byte("\n")
   279  	if _, err := file.Write(b); err != nil {
   280  		t.Fatal(err)
   281  	}
   282  
   283  	rec := []Record{
   284  		{
   285  			Hosts: "prefix",
   286  			IP:    "2.2.2.2",
   287  		},
   288  	}
   289  	if err := Delete(file.Name(), rec); err != nil {
   290  		t.Fatal(err)
   291  	}
   292  }
   293  
   294  func TestDelete(t *testing.T) {
   295  	file, err := os.CreateTemp("", "")
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	defer os.Remove(file.Name())
   300  
   301  	err = Build(file.Name(), nil)
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  
   306  	if err := Add(file.Name(), []Record{
   307  		{
   308  			Hosts: "testhostname1",
   309  			IP:    "1.1.1.1",
   310  		},
   311  		{
   312  			Hosts: "testhostname2",
   313  			IP:    "2.2.2.2",
   314  		},
   315  		{
   316  			Hosts: "testhostname3",
   317  			IP:    "3.3.3.3",
   318  		},
   319  	}); err != nil {
   320  		t.Fatal(err)
   321  	}
   322  
   323  	if err := Delete(file.Name(), []Record{
   324  		{
   325  			Hosts: "testhostname1",
   326  			IP:    "1.1.1.1",
   327  		},
   328  		{
   329  			Hosts: "testhostname3",
   330  			IP:    "3.3.3.3",
   331  		},
   332  	}); err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	content, err := os.ReadFile(file.Name())
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	if expected := "2.2.2.2\ttesthostname2\n"; !bytes.Contains(content, []byte(expected)) {
   342  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   343  	}
   344  
   345  	if expected := "1.1.1.1\ttesthostname1\n"; bytes.Contains(content, []byte(expected)) {
   346  		t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
   347  	}
   348  }
   349  
   350  func TestConcurrentWrites(t *testing.T) {
   351  	file, err := os.CreateTemp("", "")
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	defer os.Remove(file.Name())
   356  
   357  	err = Build(file.Name(), nil)
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	if err := Add(file.Name(), []Record{
   363  		{
   364  			Hosts: "inithostname",
   365  			IP:    "172.17.0.1",
   366  		},
   367  	}); err != nil {
   368  		t.Fatal(err)
   369  	}
   370  
   371  	group := new(errgroup.Group)
   372  	for i := 0; i < 10; i++ {
   373  		i := i
   374  		group.Go(func() error {
   375  			rec := []Record{
   376  				{
   377  					IP:    fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
   378  					Hosts: fmt.Sprintf("testhostname%d", i),
   379  				},
   380  			}
   381  
   382  			for j := 0; j < 25; j++ {
   383  				if err := Add(file.Name(), rec); err != nil {
   384  					return err
   385  				}
   386  
   387  				if err := Delete(file.Name(), rec); err != nil {
   388  					return err
   389  				}
   390  			}
   391  			return nil
   392  		})
   393  	}
   394  
   395  	if err := group.Wait(); err != nil {
   396  		t.Fatal(err)
   397  	}
   398  
   399  	content, err := os.ReadFile(file.Name())
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  
   404  	if expected := "172.17.0.1\tinithostname\n"; !bytes.Contains(content, []byte(expected)) {
   405  		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
   406  	}
   407  }
   408  
   409  func benchDelete(b *testing.B) {
   410  	b.StopTimer()
   411  	file, err := os.CreateTemp("", "")
   412  	if err != nil {
   413  		b.Fatal(err)
   414  	}
   415  	defer func() {
   416  		b.StopTimer()
   417  		file.Close()
   418  		os.Remove(file.Name())
   419  		b.StartTimer()
   420  	}()
   421  
   422  	err = Build(file.Name(), nil)
   423  	if err != nil {
   424  		b.Fatal(err)
   425  	}
   426  
   427  	var records []Record
   428  	var toDelete []Record
   429  	for i := 0; i < 255; i++ {
   430  		record := Record{
   431  			Hosts: fmt.Sprintf("testhostname%d", i),
   432  			IP:    fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
   433  		}
   434  		records = append(records, record)
   435  		if i%2 == 0 {
   436  			toDelete = append(records, record)
   437  		}
   438  	}
   439  
   440  	if err := Add(file.Name(), records); err != nil {
   441  		b.Fatal(err)
   442  	}
   443  
   444  	b.StartTimer()
   445  	if err := Delete(file.Name(), toDelete); err != nil {
   446  		b.Fatal(err)
   447  	}
   448  }
   449  
   450  func BenchmarkDelete(b *testing.B) {
   451  	for i := 0; i < b.N; i++ {
   452  		benchDelete(b)
   453  	}
   454  }