github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/driver/docker_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path/filepath"
     7  	"reflect"
     8  	"runtime/debug"
     9  	"testing"
    10  	"time"
    11  
    12  	docker "github.com/fsouza/go-dockerclient"
    13  	"github.com/hashicorp/nomad/client/config"
    14  	"github.com/hashicorp/nomad/client/driver/environment"
    15  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  )
    18  
    19  func testDockerDriverContext(task string) *DriverContext {
    20  	cfg := testConfig()
    21  	cfg.DevMode = true
    22  	return NewDriverContext(task, cfg, cfg.Node, testLogger())
    23  }
    24  
    25  // dockerIsConnected checks to see if a docker daemon is available (local or remote)
    26  func dockerIsConnected(t *testing.T) bool {
    27  	client, err := docker.NewClientFromEnv()
    28  	if err != nil {
    29  		return false
    30  	}
    31  
    32  	// Creating a client doesn't actually connect, so make sure we do something
    33  	// like call Version() on it.
    34  	env, err := client.Version()
    35  	if err != nil {
    36  		t.Logf("Failed to connect to docker daemon: %s", err)
    37  		return false
    38  	}
    39  
    40  	t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version"))
    41  	return true
    42  }
    43  
    44  func dockerIsRemote(t *testing.T) bool {
    45  	client, err := docker.NewClientFromEnv()
    46  	if err != nil {
    47  		return false
    48  	}
    49  
    50  	// Technically this could be a local tcp socket but for testing purposes
    51  	// we'll just assume that tcp is only used for remote connections.
    52  	if client.Endpoint()[0:3] == "tcp" {
    53  		return true
    54  	}
    55  	return false
    56  }
    57  
    58  func dockerTask() *structs.Task {
    59  	return &structs.Task{
    60  		Name: "redis-demo",
    61  		Config: map[string]interface{}{
    62  			"image": "redis",
    63  		},
    64  		Resources: &structs.Resources{
    65  			MemoryMB: 256,
    66  			CPU:      512,
    67  			Networks: []*structs.NetworkResource{
    68  				&structs.NetworkResource{
    69  					IP:            "127.0.0.1",
    70  					ReservedPorts: []structs.Port{{"main", 11110}},
    71  					DynamicPorts:  []structs.Port{{"REDIS", 43330}},
    72  				},
    73  			},
    74  		},
    75  	}
    76  }
    77  
    78  // dockerSetup does all of the basic setup you need to get a running docker
    79  // process up and running for testing. Use like:
    80  //
    81  //	task := taskTemplate()
    82  //	// do custom task configuration
    83  //	client, handle, cleanup := dockerSetup(t, task)
    84  //	defer cleanup()
    85  //	// do test stuff
    86  //
    87  // If there is a problem during setup this function will abort or skip the test
    88  // and indicate the reason.
    89  func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) {
    90  	if !dockerIsConnected(t) {
    91  		t.SkipNow()
    92  	}
    93  
    94  	client, err := docker.NewClientFromEnv()
    95  	if err != nil {
    96  		t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack())
    97  	}
    98  
    99  	driverCtx := testDockerDriverContext(task.Name)
   100  	ctx := testDriverExecContext(task, driverCtx)
   101  	driver := NewDockerDriver(driverCtx)
   102  
   103  	handle, err := driver.Start(ctx, task)
   104  	if err != nil {
   105  		ctx.AllocDir.Destroy()
   106  		t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack())
   107  	}
   108  	if handle == nil {
   109  		ctx.AllocDir.Destroy()
   110  		t.Fatalf("handle is nil\nStack\n%s", debug.Stack())
   111  	}
   112  
   113  	cleanup := func() {
   114  		handle.Kill()
   115  		ctx.AllocDir.Destroy()
   116  	}
   117  
   118  	return client, handle, cleanup
   119  }
   120  
   121  func TestDockerDriver_Handle(t *testing.T) {
   122  	h := &DockerHandle{
   123  		imageID:     "imageid",
   124  		containerID: "containerid",
   125  		doneCh:      make(chan struct{}),
   126  		waitCh:      make(chan *cstructs.WaitResult, 1),
   127  	}
   128  
   129  	actual := h.ID()
   130  	expected := `DOCKER:{"ImageID":"imageid","ContainerID":"containerid"}`
   131  	if actual != expected {
   132  		t.Errorf("Expected `%s`, found `%s`", expected, actual)
   133  	}
   134  }
   135  
   136  // This test should always pass, even if docker daemon is not available
   137  func TestDockerDriver_Fingerprint(t *testing.T) {
   138  	d := NewDockerDriver(testDockerDriverContext(""))
   139  	node := &structs.Node{
   140  		Attributes: make(map[string]string),
   141  	}
   142  	apply, err := d.Fingerprint(&config.Config{}, node)
   143  	if err != nil {
   144  		t.Fatalf("err: %v", err)
   145  	}
   146  	if apply != dockerIsConnected(t) {
   147  		t.Fatalf("Fingerprinter should detect when docker is available")
   148  	}
   149  	if node.Attributes["driver.docker"] != "1" {
   150  		t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.")
   151  	}
   152  	t.Logf("Found docker version %s", node.Attributes["driver.docker.version"])
   153  }
   154  
   155  func TestDockerDriver_StartOpen_Wait(t *testing.T) {
   156  	if !dockerIsConnected(t) {
   157  		t.SkipNow()
   158  	}
   159  
   160  	task := &structs.Task{
   161  		Name: "redis-demo",
   162  		Config: map[string]interface{}{
   163  			"image": "redis",
   164  		},
   165  		Resources: basicResources,
   166  	}
   167  
   168  	driverCtx := testDockerDriverContext(task.Name)
   169  	ctx := testDriverExecContext(task, driverCtx)
   170  	defer ctx.AllocDir.Destroy()
   171  	d := NewDockerDriver(driverCtx)
   172  
   173  	handle, err := d.Start(ctx, task)
   174  	if err != nil {
   175  		t.Fatalf("err: %v", err)
   176  	}
   177  	if handle == nil {
   178  		t.Fatalf("missing handle")
   179  	}
   180  	defer handle.Kill()
   181  
   182  	// Attempt to open
   183  	handle2, err := d.Open(ctx, handle.ID())
   184  	if err != nil {
   185  		t.Fatalf("err: %v", err)
   186  	}
   187  	if handle2 == nil {
   188  		t.Fatalf("missing handle")
   189  	}
   190  }
   191  
   192  func TestDockerDriver_Start_Wait(t *testing.T) {
   193  	task := &structs.Task{
   194  		Name: "redis-demo",
   195  		Config: map[string]interface{}{
   196  			"image":   "redis",
   197  			"command": "redis-server",
   198  			"args":    []string{"-v"},
   199  		},
   200  		Resources: &structs.Resources{
   201  			MemoryMB: 256,
   202  			CPU:      512,
   203  		},
   204  	}
   205  
   206  	_, handle, cleanup := dockerSetup(t, task)
   207  	defer cleanup()
   208  
   209  	// Update should be a no-op
   210  	err := handle.Update(task)
   211  	if err != nil {
   212  		t.Fatalf("err: %v", err)
   213  	}
   214  
   215  	select {
   216  	case res := <-handle.WaitCh():
   217  		if !res.Successful() {
   218  			t.Fatalf("err: %v", res)
   219  		}
   220  	case <-time.After(5 * time.Second):
   221  		t.Fatalf("timeout")
   222  	}
   223  }
   224  
   225  func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
   226  	// This test requires that the alloc dir be mounted into docker as a volume.
   227  	// Because this cannot happen when docker is run remotely, e.g. when running
   228  	// docker in a VM, we skip this when we detect Docker is being run remotely.
   229  	if !dockerIsConnected(t) || dockerIsRemote(t) {
   230  		t.SkipNow()
   231  	}
   232  
   233  	exp := []byte{'w', 'i', 'n'}
   234  	file := "output.txt"
   235  	task := &structs.Task{
   236  		Name: "redis-demo",
   237  		Config: map[string]interface{}{
   238  			"image":   "redis",
   239  			"command": "/bin/bash",
   240  			"args": []string{
   241  				"-c",
   242  				fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
   243  					string(exp), environment.AllocDir, file),
   244  			},
   245  		},
   246  		Resources: &structs.Resources{
   247  			MemoryMB: 256,
   248  			CPU:      512,
   249  		},
   250  	}
   251  
   252  	driverCtx := testDockerDriverContext(task.Name)
   253  	ctx := testDriverExecContext(task, driverCtx)
   254  	defer ctx.AllocDir.Destroy()
   255  	d := NewDockerDriver(driverCtx)
   256  
   257  	handle, err := d.Start(ctx, task)
   258  	if err != nil {
   259  		t.Fatalf("err: %v", err)
   260  	}
   261  	if handle == nil {
   262  		t.Fatalf("missing handle")
   263  	}
   264  	defer handle.Kill()
   265  
   266  	select {
   267  	case res := <-handle.WaitCh():
   268  		if !res.Successful() {
   269  			t.Fatalf("err: %v", res)
   270  		}
   271  	case <-time.After(5 * time.Second):
   272  		t.Fatalf("timeout")
   273  	}
   274  
   275  	// Check that data was written to the shared alloc directory.
   276  	outputFile := filepath.Join(ctx.AllocDir.SharedDir, file)
   277  	act, err := ioutil.ReadFile(outputFile)
   278  	if err != nil {
   279  		t.Fatalf("Couldn't read expected output: %v", err)
   280  	}
   281  
   282  	if !reflect.DeepEqual(act, exp) {
   283  		t.Fatalf("Command outputted %v; want %v", act, exp)
   284  	}
   285  }
   286  
   287  func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
   288  	task := &structs.Task{
   289  		Name: "redis-demo",
   290  		Config: map[string]interface{}{
   291  			"image":   "redis",
   292  			"command": "/bin/sleep",
   293  			"args":    []string{"10"},
   294  		},
   295  		Resources: basicResources,
   296  	}
   297  
   298  	_, handle, cleanup := dockerSetup(t, task)
   299  	defer cleanup()
   300  
   301  	go func() {
   302  		time.Sleep(100 * time.Millisecond)
   303  		err := handle.Kill()
   304  		if err != nil {
   305  			t.Fatalf("err: %v", err)
   306  		}
   307  	}()
   308  
   309  	select {
   310  	case res := <-handle.WaitCh():
   311  		if res.Successful() {
   312  			t.Fatalf("should err: %v", res)
   313  		}
   314  	case <-time.After(10 * time.Second):
   315  		t.Fatalf("timeout")
   316  	}
   317  }
   318  
   319  func TestDocker_StartN(t *testing.T) {
   320  	if !dockerIsConnected(t) {
   321  		t.SkipNow()
   322  	}
   323  
   324  	task1 := dockerTask()
   325  	task1.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 11110}
   326  	task1.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43331}
   327  
   328  	task2 := dockerTask()
   329  	task2.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 22222}
   330  	task2.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43332}
   331  
   332  	task3 := dockerTask()
   333  	task3.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 33333}
   334  	task3.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43333}
   335  
   336  	taskList := []*structs.Task{task1, task2, task3}
   337  
   338  	handles := make([]DriverHandle, len(taskList))
   339  
   340  	t.Logf("==> Starting %d tasks", len(taskList))
   341  
   342  	// Let's spin up a bunch of things
   343  	var err error
   344  	for idx, task := range taskList {
   345  		driverCtx := testDockerDriverContext(task.Name)
   346  		ctx := testDriverExecContext(task, driverCtx)
   347  		defer ctx.AllocDir.Destroy()
   348  		d := NewDockerDriver(driverCtx)
   349  
   350  		handles[idx], err = d.Start(ctx, task)
   351  		if err != nil {
   352  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   353  		}
   354  	}
   355  
   356  	t.Log("==> All tasks are started. Terminating...")
   357  
   358  	for idx, handle := range handles {
   359  		if handle == nil {
   360  			t.Errorf("Bad handle for task #%d", idx+1)
   361  			continue
   362  		}
   363  
   364  		err := handle.Kill()
   365  		if err != nil {
   366  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   367  		}
   368  	}
   369  
   370  	t.Log("==> Test complete!")
   371  }
   372  
   373  func TestDocker_StartNVersions(t *testing.T) {
   374  	if !dockerIsConnected(t) {
   375  		t.SkipNow()
   376  	}
   377  
   378  	task1 := dockerTask()
   379  	task1.Config["image"] = "redis"
   380  	task1.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 11110}
   381  	task1.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43331}
   382  
   383  	task2 := dockerTask()
   384  	task2.Config["image"] = "redis:latest"
   385  	task2.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 22222}
   386  	task2.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43332}
   387  
   388  	task3 := dockerTask()
   389  	task3.Config["image"] = "redis:3.0"
   390  	task3.Resources.Networks[0].ReservedPorts[0] = structs.Port{Label: "main", Value: 33333}
   391  	task3.Resources.Networks[0].DynamicPorts[0] = structs.Port{Label: "REDIS", Value: 43333}
   392  
   393  	taskList := []*structs.Task{task1, task2, task3}
   394  
   395  	handles := make([]DriverHandle, len(taskList))
   396  
   397  	t.Logf("==> Starting %d tasks", len(taskList))
   398  
   399  	// Let's spin up a bunch of things
   400  	var err error
   401  	for idx, task := range taskList {
   402  		driverCtx := testDockerDriverContext(task.Name)
   403  		ctx := testDriverExecContext(task, driverCtx)
   404  		defer ctx.AllocDir.Destroy()
   405  		d := NewDockerDriver(driverCtx)
   406  
   407  		handles[idx], err = d.Start(ctx, task)
   408  		if err != nil {
   409  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   410  		}
   411  	}
   412  
   413  	t.Log("==> All tasks are started. Terminating...")
   414  
   415  	for idx, handle := range handles {
   416  		if handle == nil {
   417  			t.Errorf("Bad handle for task #%d", idx+1)
   418  			continue
   419  		}
   420  
   421  		err := handle.Kill()
   422  		if err != nil {
   423  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   424  		}
   425  	}
   426  
   427  	t.Log("==> Test complete!")
   428  }
   429  
   430  func TestDockerHostNet(t *testing.T) {
   431  	expected := "host"
   432  
   433  	task := &structs.Task{
   434  		Name: "redis-demo",
   435  		Config: map[string]interface{}{
   436  			"image":        "redis",
   437  			"network_mode": expected,
   438  		},
   439  		Resources: &structs.Resources{
   440  			MemoryMB: 256,
   441  			CPU:      512,
   442  		},
   443  	}
   444  
   445  	client, handle, cleanup := dockerSetup(t, task)
   446  	defer cleanup()
   447  
   448  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   449  	if err != nil {
   450  		t.Fatalf("err: %v", err)
   451  	}
   452  
   453  	actual := container.HostConfig.NetworkMode
   454  	if actual != expected {
   455  		t.Errorf("DNS Network mode doesn't match.\nExpected:\n%s\nGot:\n%s\n", expected, actual)
   456  	}
   457  }
   458  
   459  func TestDockerLabels(t *testing.T) {
   460  	task := dockerTask()
   461  	task.Config["labels"] = []map[string]string{
   462  		map[string]string{
   463  			"label1": "value1",
   464  			"label2": "value2",
   465  		},
   466  	}
   467  
   468  	client, handle, cleanup := dockerSetup(t, task)
   469  	defer cleanup()
   470  
   471  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   472  	if err != nil {
   473  		t.Fatalf("err: %v", err)
   474  	}
   475  
   476  	if want, got := 2, len(container.Config.Labels); want != got {
   477  		t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got)
   478  	}
   479  
   480  	if want, got := "value1", container.Config.Labels["label1"]; want != got {
   481  		t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got)
   482  	}
   483  }
   484  
   485  func TestDockerDNS(t *testing.T) {
   486  	task := dockerTask()
   487  	task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"}
   488  	task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"}
   489  
   490  	client, handle, cleanup := dockerSetup(t, task)
   491  	defer cleanup()
   492  
   493  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   494  	if err != nil {
   495  		t.Fatalf("err: %v", err)
   496  	}
   497  
   498  	if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) {
   499  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS)
   500  	}
   501  
   502  	if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) {
   503  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch)
   504  	}
   505  }
   506  
   507  func inSlice(needle string, haystack []string) bool {
   508  	for _, h := range haystack {
   509  		if h == needle {
   510  			return true
   511  		}
   512  	}
   513  	return false
   514  }
   515  
   516  func TestDockerPortsNoMap(t *testing.T) {
   517  	task := dockerTask()
   518  
   519  	client, handle, cleanup := dockerSetup(t, task)
   520  	defer cleanup()
   521  
   522  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   523  	if err != nil {
   524  		t.Fatalf("err: %v", err)
   525  	}
   526  
   527  	// Verify that the correct ports are EXPOSED
   528  	expectedExposedPorts := map[docker.Port]struct{}{
   529  		docker.Port("11110/tcp"): struct{}{},
   530  		docker.Port("11110/udp"): struct{}{},
   531  		docker.Port("43330/tcp"): struct{}{},
   532  		docker.Port("43330/udp"): struct{}{},
   533  		// This one comes from the redis container
   534  		docker.Port("6379/tcp"): struct{}{},
   535  	}
   536  
   537  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   538  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   539  	}
   540  
   541  	// Verify that the correct ports are FORWARDED
   542  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   543  		docker.Port("11110/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "11110"}},
   544  		docker.Port("11110/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "11110"}},
   545  		docker.Port("43330/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "43330"}},
   546  		docker.Port("43330/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "43330"}},
   547  	}
   548  
   549  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   550  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   551  	}
   552  
   553  	expectedEnvironment := map[string]string{
   554  		"NOMAD_PORT_main":  "11110",
   555  		"NOMAD_PORT_REDIS": "43330",
   556  	}
   557  
   558  	for key, val := range expectedEnvironment {
   559  		search := fmt.Sprintf("%s=%s", key, val)
   560  		if !inSlice(search, container.Config.Env) {
   561  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   562  		}
   563  	}
   564  }
   565  
   566  func TestDockerPortsMapping(t *testing.T) {
   567  	task := dockerTask()
   568  	task.Config["port_map"] = []map[string]string{
   569  		map[string]string{
   570  			"main":  "8080",
   571  			"REDIS": "6379",
   572  		},
   573  	}
   574  
   575  	client, handle, cleanup := dockerSetup(t, task)
   576  	defer cleanup()
   577  
   578  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   579  	if err != nil {
   580  		t.Fatalf("err: %v", err)
   581  	}
   582  
   583  	// Verify that the correct ports are EXPOSED
   584  	expectedExposedPorts := map[docker.Port]struct{}{
   585  		docker.Port("8080/tcp"): struct{}{},
   586  		docker.Port("8080/udp"): struct{}{},
   587  		docker.Port("6379/tcp"): struct{}{},
   588  		docker.Port("6379/udp"): struct{}{},
   589  	}
   590  
   591  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   592  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   593  	}
   594  
   595  	// Verify that the correct ports are FORWARDED
   596  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   597  		docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "11110"}},
   598  		docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "11110"}},
   599  		docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "43330"}},
   600  		docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: "43330"}},
   601  	}
   602  
   603  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   604  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   605  	}
   606  
   607  	expectedEnvironment := map[string]string{
   608  		"NOMAD_PORT_main":  "8080",
   609  		"NOMAD_PORT_REDIS": "6379",
   610  	}
   611  
   612  	for key, val := range expectedEnvironment {
   613  		search := fmt.Sprintf("%s=%s", key, val)
   614  		if !inSlice(search, container.Config.Env) {
   615  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   616  		}
   617  	}
   618  }