github.com/ilhicas/nomad@v0.11.8/drivers/docker/config_test.go (about)

     1  package docker
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
     7  	"github.com/hashicorp/nomad/plugins/drivers"
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  func TestConfig_ParseHCL(t *testing.T) {
    12  	cases := []struct {
    13  		name string
    14  
    15  		input    string
    16  		expected *TaskConfig
    17  	}{
    18  		{
    19  			"basic image",
    20  			`config {
    21  				image = "redis:3.2"
    22  			}`,
    23  			&TaskConfig{
    24  				Image:        "redis:3.2",
    25  				Devices:      []DockerDevice{},
    26  				Mounts:       []DockerMount{},
    27  				CPUCFSPeriod: 100000,
    28  			},
    29  		},
    30  	}
    31  
    32  	parser := hclutils.NewConfigParser(taskConfigSpec)
    33  	for _, c := range cases {
    34  		c := c
    35  		t.Run(c.name, func(t *testing.T) {
    36  			var tc *TaskConfig
    37  
    38  			parser.ParseHCL(t, c.input, &tc)
    39  
    40  			require.EqualValues(t, c.expected, tc)
    41  
    42  		})
    43  	}
    44  }
    45  
    46  func TestConfig_ParseJSON(t *testing.T) {
    47  	cases := []struct {
    48  		name     string
    49  		input    string
    50  		expected TaskConfig
    51  	}{
    52  		{
    53  			name:  "nil values for blocks are safe",
    54  			input: `{"Config": {"image": "bash:3", "mounts": null}}`,
    55  			expected: TaskConfig{
    56  				Image:        "bash:3",
    57  				Mounts:       []DockerMount{},
    58  				Devices:      []DockerDevice{},
    59  				CPUCFSPeriod: 100000,
    60  			},
    61  		},
    62  		{
    63  			name:  "nil values for 'volumes' field are safe",
    64  			input: `{"Config": {"image": "bash:3", "volumes": null}}`,
    65  			expected: TaskConfig{
    66  				Image:        "bash:3",
    67  				Mounts:       []DockerMount{},
    68  				Devices:      []DockerDevice{},
    69  				CPUCFSPeriod: 100000,
    70  			},
    71  		},
    72  		{
    73  			name:  "nil values for 'args' field are safe",
    74  			input: `{"Config": {"image": "bash:3", "args": null}}`,
    75  			expected: TaskConfig{
    76  				Image:        "bash:3",
    77  				Mounts:       []DockerMount{},
    78  				Devices:      []DockerDevice{},
    79  				CPUCFSPeriod: 100000,
    80  			},
    81  		},
    82  		{
    83  			name:  "nil values for string fields are safe",
    84  			input: `{"Config": {"image": "bash:3", "command": null}}`,
    85  			expected: TaskConfig{
    86  				Image:        "bash:3",
    87  				Mounts:       []DockerMount{},
    88  				Devices:      []DockerDevice{},
    89  				CPUCFSPeriod: 100000,
    90  			},
    91  		},
    92  	}
    93  
    94  	for _, c := range cases {
    95  		c := c
    96  		t.Run(c.name, func(t *testing.T) {
    97  			var tc TaskConfig
    98  			hclutils.NewConfigParser(taskConfigSpec).ParseJson(t, c.input, &tc)
    99  
   100  			require.Equal(t, c.expected, tc)
   101  		})
   102  	}
   103  }
   104  
   105  func TestConfig_PortMap_Deserialization(t *testing.T) {
   106  	parser := hclutils.NewConfigParser(taskConfigSpec)
   107  
   108  	expectedMap := map[string]int{
   109  		"ssh":   25,
   110  		"http":  80,
   111  		"https": 443,
   112  	}
   113  
   114  	t.Run("parsing hcl block case", func(t *testing.T) {
   115  		validHCL := `
   116  config {
   117    image = "redis"
   118    port_map {
   119      ssh   = 25
   120      http  = 80
   121      https = 443
   122    }
   123  }`
   124  
   125  		var tc *TaskConfig
   126  		parser.ParseHCL(t, validHCL, &tc)
   127  
   128  		require.EqualValues(t, expectedMap, tc.PortMap)
   129  	})
   130  
   131  	t.Run("parsing hcl assignment case", func(t *testing.T) {
   132  		validHCL := `
   133  config {
   134    image = "redis"
   135    port_map = {
   136      ssh   = 25
   137      http  = 80
   138      https = 443
   139    }
   140  }`
   141  
   142  		var tc *TaskConfig
   143  		parser.ParseHCL(t, validHCL, &tc)
   144  
   145  		require.EqualValues(t, expectedMap, tc.PortMap)
   146  	})
   147  
   148  	validJsons := []struct {
   149  		name string
   150  		json string
   151  	}{
   152  		{
   153  			"single map in an array",
   154  			`{"Config": {"image": "redis", "port_map": [{"ssh": 25, "http": 80, "https": 443}]}}`,
   155  		},
   156  		{
   157  			"array of single map entries",
   158  			`{"Config": {"image": "redis", "port_map": [{"ssh": 25}, {"http": 80}, {"https": 443}]}}`,
   159  		},
   160  		{
   161  			"array of maps",
   162  			`{"Config": {"image": "redis", "port_map": [{"ssh": 25, "http": 80}, {"https": 443}]}}`,
   163  		},
   164  	}
   165  
   166  	for _, c := range validJsons {
   167  		t.Run("json:"+c.name, func(t *testing.T) {
   168  			var tc *TaskConfig
   169  			parser.ParseJson(t, c.json, &tc)
   170  
   171  			require.EqualValues(t, expectedMap, tc.PortMap)
   172  		})
   173  	}
   174  
   175  }
   176  
   177  func TestConfig_ParseAllHCL(t *testing.T) {
   178  	cfgStr := `
   179  config {
   180    image = "redis:3.2"
   181    advertise_ipv6_address = true
   182    args = ["command_arg1", "command_arg2"]
   183    auth {
   184      username = "myusername"
   185      password = "mypassword"
   186      email = "myemail@example.com"
   187      server_address = "https://example.com"
   188    }
   189  
   190    auth_soft_fail = true
   191    cap_add = ["CAP_SYS_NICE"]
   192    cap_drop = ["CAP_SYS_ADMIN", "CAP_SYS_TIME"]
   193    command = "/bin/bash"
   194    cpu_hard_limit = true
   195    cpu_cfs_period = 20
   196    devices = [
   197      {"host_path"="/dev/null", "container_path"="/tmp/container-null", cgroup_permissions="rwm"},
   198      {"host_path"="/dev/random", "container_path"="/tmp/container-random"},
   199    ]
   200    dns_search_domains = ["sub.example.com", "sub2.example.com"]
   201    dns_options = ["debug", "attempts:10"]
   202    dns_servers = ["8.8.8.8", "1.1.1.1"]
   203    entrypoint = ["/bin/bash", "-c"]
   204    extra_hosts = ["127.0.0.1  localhost.example.com"]
   205    force_pull = true
   206    hostname = "self.example.com"
   207    interactive = true
   208    ipc_mode = "host"
   209    ipv4_address = "10.0.2.1"
   210    ipv6_address = "2601:184:407f:b37c:d834:412e:1f86:7699"
   211    labels {
   212      owner = "hashicorp-nomad"
   213      key = "val"
   214    }
   215    load = "/tmp/image.tar.gz"
   216    logging {
   217      driver = "json-file-driver"
   218      type   = "json-file"
   219      config {
   220        "max-file" = "3"
   221        "max-size" = "10m"
   222      }
   223    }
   224    mac_address = "02:42:ac:11:00:02"
   225    memory_hard_limit = 512
   226    mounts = [
   227      {
   228        type = "bind"
   229        target = "/bind-target",
   230        source = "/bind-source"
   231        readonly = true
   232        bind_options {
   233          propagation = "rshared"
   234        }
   235      },
   236      {
   237        type = "tmpfs"
   238        target = "/tmpfs-target",
   239        readonly = true
   240        tmpfs_options {
   241          size = 30000
   242          mode = 0777
   243        }
   244      },
   245      {
   246        type = "volume"
   247        target = "/volume-target"
   248        source = "/volume-source"
   249        readonly = true
   250        volume_options {
   251          no_copy = true
   252          labels {
   253            label_key = "label_value"
   254  	}
   255          driver_config {
   256            name = "nfs"
   257            options {
   258              option_key = "option_value"
   259            }
   260          }
   261        }
   262      },
   263    ]
   264    network_aliases = ["redis"]
   265    network_mode = "host"
   266    pids_limit = 2000
   267    pid_mode = "host"
   268    port_map {
   269      http = 80
   270      redis = 6379
   271    }
   272    privileged = true
   273    readonly_rootfs = true
   274    runtime = "runc"
   275    security_opt = [
   276      "credentialspec=file://gmsaUser.json"
   277    ],
   278    shm_size = 30000
   279    storage_opt {
   280      dm.thinpooldev = "dev/mapper/thin-pool"
   281      dm.use_deferred_deletion = "true"
   282      dm.use_deferred_removal = "true"
   283  
   284    }
   285    sysctl {
   286      net.core.somaxconn = "16384"
   287    }
   288    tty = true
   289    ulimit {
   290      nproc = "4242"
   291      nofile = "2048:4096"
   292    }
   293    uts_mode = "host"
   294    userns_mode = "host"
   295    volumes = [
   296      "/host-path:/container-path:rw",
   297    ]
   298    volume_driver = "host"
   299    work_dir = "/tmp/workdir"
   300  }`
   301  
   302  	expected := &TaskConfig{
   303  		Image:             "redis:3.2",
   304  		AdvertiseIPv6Addr: true,
   305  		Args:              []string{"command_arg1", "command_arg2"},
   306  		Auth: DockerAuth{
   307  			Username:   "myusername",
   308  			Password:   "mypassword",
   309  			Email:      "myemail@example.com",
   310  			ServerAddr: "https://example.com",
   311  		},
   312  		AuthSoftFail: true,
   313  		CapAdd:       []string{"CAP_SYS_NICE"},
   314  		CapDrop:      []string{"CAP_SYS_ADMIN", "CAP_SYS_TIME"},
   315  		Command:      "/bin/bash",
   316  		CPUHardLimit: true,
   317  		CPUCFSPeriod: 20,
   318  		Devices: []DockerDevice{
   319  			{
   320  				HostPath:          "/dev/null",
   321  				ContainerPath:     "/tmp/container-null",
   322  				CgroupPermissions: "rwm",
   323  			},
   324  			{
   325  				HostPath:          "/dev/random",
   326  				ContainerPath:     "/tmp/container-random",
   327  				CgroupPermissions: "",
   328  			},
   329  		},
   330  		DNSSearchDomains: []string{"sub.example.com", "sub2.example.com"},
   331  		DNSOptions:       []string{"debug", "attempts:10"},
   332  		DNSServers:       []string{"8.8.8.8", "1.1.1.1"},
   333  		Entrypoint:       []string{"/bin/bash", "-c"},
   334  		ExtraHosts:       []string{"127.0.0.1  localhost.example.com"},
   335  		ForcePull:        true,
   336  		Hostname:         "self.example.com",
   337  		Interactive:      true,
   338  		IPCMode:          "host",
   339  		IPv4Address:      "10.0.2.1",
   340  		IPv6Address:      "2601:184:407f:b37c:d834:412e:1f86:7699",
   341  		Labels: map[string]string{
   342  			"owner": "hashicorp-nomad",
   343  			"key":   "val",
   344  		},
   345  		LoadImage: "/tmp/image.tar.gz",
   346  		Logging: DockerLogging{
   347  			Driver: "json-file-driver",
   348  			Type:   "json-file",
   349  			Config: map[string]string{
   350  				"max-file": "3",
   351  				"max-size": "10m",
   352  			}},
   353  		MacAddress:      "02:42:ac:11:00:02",
   354  		MemoryHardLimit: 512,
   355  		Mounts: []DockerMount{
   356  			{
   357  				Type:     "bind",
   358  				Target:   "/bind-target",
   359  				Source:   "/bind-source",
   360  				ReadOnly: true,
   361  				BindOptions: DockerBindOptions{
   362  					Propagation: "rshared",
   363  				},
   364  			},
   365  			{
   366  				Type:     "tmpfs",
   367  				Target:   "/tmpfs-target",
   368  				Source:   "",
   369  				ReadOnly: true,
   370  				TmpfsOptions: DockerTmpfsOptions{
   371  					SizeBytes: 30000,
   372  					Mode:      511,
   373  				},
   374  			},
   375  			{
   376  				Type:     "volume",
   377  				Target:   "/volume-target",
   378  				Source:   "/volume-source",
   379  				ReadOnly: true,
   380  				VolumeOptions: DockerVolumeOptions{
   381  					NoCopy: true,
   382  					Labels: map[string]string{
   383  						"label_key": "label_value",
   384  					},
   385  					DriverConfig: DockerVolumeDriverConfig{
   386  						Name: "nfs",
   387  						Options: map[string]string{
   388  							"option_key": "option_value",
   389  						},
   390  					},
   391  				},
   392  			},
   393  		},
   394  		NetworkAliases: []string{"redis"},
   395  		NetworkMode:    "host",
   396  		PidsLimit:      2000,
   397  		PidMode:        "host",
   398  		PortMap: map[string]int{
   399  			"http":  80,
   400  			"redis": 6379,
   401  		},
   402  		Privileged:     true,
   403  		ReadonlyRootfs: true,
   404  		Runtime:        "runc",
   405  		SecurityOpt: []string{
   406  			"credentialspec=file://gmsaUser.json",
   407  		},
   408  		ShmSize: 30000,
   409  		StorageOpt: map[string]string{
   410  			"dm.thinpooldev":           "dev/mapper/thin-pool",
   411  			"dm.use_deferred_deletion": "true",
   412  			"dm.use_deferred_removal":  "true",
   413  		},
   414  		Sysctl: map[string]string{
   415  			"net.core.somaxconn": "16384",
   416  		},
   417  		TTY: true,
   418  		Ulimit: map[string]string{
   419  			"nofile": "2048:4096",
   420  			"nproc":  "4242",
   421  		},
   422  		UTSMode:    "host",
   423  		UsernsMode: "host",
   424  		Volumes: []string{
   425  			"/host-path:/container-path:rw",
   426  		},
   427  		VolumeDriver: "host",
   428  		WorkDir:      "/tmp/workdir",
   429  	}
   430  
   431  	var tc *TaskConfig
   432  	hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc)
   433  
   434  	require.EqualValues(t, expected, tc)
   435  }
   436  
   437  // TestConfig_DriverConfig_DanglingContainers asserts that dangling_containers is parsed
   438  // and populated with defaults as expected
   439  func TestConfig_DriverConfig_DanglingContainers(t *testing.T) {
   440  	cases := []struct {
   441  		name     string
   442  		config   string
   443  		expected ContainerGCConfig
   444  	}{
   445  		{
   446  			name:     "pure default",
   447  			config:   `{}`,
   448  			expected: ContainerGCConfig{Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"},
   449  		},
   450  		{
   451  			name:     "partial gc",
   452  			config:   `{ gc { } }`,
   453  			expected: ContainerGCConfig{Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"},
   454  		},
   455  		{
   456  			name:     "partial gc",
   457  			config:   `{ gc { dangling_containers { } } }`,
   458  			expected: ContainerGCConfig{Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"},
   459  		},
   460  		{
   461  			name:     "partial dangling_containers",
   462  			config:   `{ gc { dangling_containers { enabled = false } } }`,
   463  			expected: ContainerGCConfig{Enabled: false, PeriodStr: "5m", CreationGraceStr: "5m"},
   464  		},
   465  		{
   466  			name:     "incomplete dangling_containers 2",
   467  			config:   `{ gc { dangling_containers { period = "10m" } } }`,
   468  			expected: ContainerGCConfig{Enabled: true, PeriodStr: "10m", CreationGraceStr: "5m"},
   469  		},
   470  		{
   471  			name: "full default",
   472  			config: `{ gc { dangling_containers {
   473  			     enabled = false
   474  			     dry_run = true
   475  			     period = "10m"
   476  			     creation_grace = "20m"
   477  			}}}`,
   478  			expected: ContainerGCConfig{
   479  				Enabled:          false,
   480  				DryRun:           true,
   481  				PeriodStr:        "10m",
   482  				CreationGraceStr: "20m",
   483  			},
   484  		},
   485  	}
   486  
   487  	for _, c := range cases {
   488  		t.Run(c.name, func(t *testing.T) {
   489  			var tc DriverConfig
   490  			hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc)
   491  			require.EqualValues(t, c.expected, tc.GC.DanglingContainers)
   492  
   493  		})
   494  	}
   495  }
   496  
   497  func TestConfig_InternalCapabilities(t *testing.T) {
   498  	cases := []struct {
   499  		name     string
   500  		config   string
   501  		expected drivers.InternalCapabilities
   502  	}{
   503  		{
   504  			name:     "pure default",
   505  			config:   `{}`,
   506  			expected: drivers.InternalCapabilities{},
   507  		},
   508  		{
   509  			name:     "disabled",
   510  			config:   `{ disable_log_collection = true }`,
   511  			expected: drivers.InternalCapabilities{DisableLogCollection: true},
   512  		},
   513  		{
   514  			name:     "enabled explicitly",
   515  			config:   `{ disable_log_collection = false }`,
   516  			expected: drivers.InternalCapabilities{},
   517  		},
   518  	}
   519  
   520  	for _, c := range cases {
   521  		t.Run(c.name, func(t *testing.T) {
   522  			var tc DriverConfig
   523  			hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc)
   524  
   525  			d := &Driver{config: &tc}
   526  			require.Equal(t, c.expected, d.InternalCapabilities())
   527  		})
   528  	}
   529  }
   530  
   531  func TestConfig_DriverConfig_PullActivityTimeout(t *testing.T) {
   532  	cases := []struct {
   533  		name     string
   534  		config   string
   535  		expected string
   536  	}{
   537  		{
   538  			name:     "default",
   539  			config:   `{}`,
   540  			expected: "2m",
   541  		},
   542  		{
   543  			name:     "set explicitly",
   544  			config:   `{ pull_activity_timeout = "5m" }`,
   545  			expected: "5m",
   546  		},
   547  	}
   548  
   549  	for _, c := range cases {
   550  		t.Run(c.name, func(t *testing.T) {
   551  			var tc DriverConfig
   552  			hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc)
   553  			require.Equal(t, c.expected, tc.PullActivityTimeout)
   554  		})
   555  	}
   556  }
   557  
   558  func TestConfig_DriverConfig_AllowRuntimes(t *testing.T) {
   559  	cases := []struct {
   560  		name     string
   561  		config   string
   562  		expected map[string]struct{}
   563  	}{
   564  		{
   565  			name:     "pure default",
   566  			config:   `{}`,
   567  			expected: map[string]struct{}{"runc": struct{}{}, "nvidia": struct{}{}},
   568  		},
   569  		{
   570  			name:     "custom",
   571  			config:   `{ allow_runtimes = ["runc", "firecracker"]}`,
   572  			expected: map[string]struct{}{"runc": struct{}{}, "firecracker": struct{}{}},
   573  		},
   574  	}
   575  
   576  	for _, c := range cases {
   577  		t.Run(c.name, func(t *testing.T) {
   578  			var tc map[string]interface{}
   579  			hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc)
   580  
   581  			dh := dockerDriverHarness(t, tc)
   582  			d := dh.Impl().(*Driver)
   583  			require.Equal(t, c.expected, d.config.allowRuntimes)
   584  		})
   585  	}
   586  }