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