Browse code

Add CreatedAt filed to volume. Display when volume is inspected.

Closes #32663 by adding CreatedAt field when volume is created.
Displaying CreatedAt value when volume is inspected
Adding tests to verfiy the new field is correctly populated

Signed-off-by: Marianna <mtesselh@gmail.com>

Moving CreatedAt tests from the CLI

Moving the tests added for the newly added CreatedAt field for Volume, from CLI to API tests

Signed-off-by: Marianna <mtesselh@gmail.com>

Marianna authored on 2017/05/18 06:19:13
Showing 9 changed files
... ...
@@ -1047,6 +1047,10 @@ definitions:
1047 1047
         type: "string"
1048 1048
         description: "Mount path of the volume on the host."
1049 1049
         x-nullable: false
1050
+      CreatedAt:
1051
+        type: "string"
1052
+        format: "dateTime"
1053
+        description: "Time volume was created."
1050 1054
       Status:
1051 1055
         type: "object"
1052 1056
         description: |
... ...
@@ -1100,6 +1104,7 @@ definitions:
1100 1100
         com.example.some-label: "some-value"
1101 1101
         com.example.some-other-label: "some-other-value"
1102 1102
       Scope: "local"
1103
+      CreatedAt: "2016-06-07T20:31:11.853781916Z"
1103 1104
 
1104 1105
   Network:
1105 1106
     type: "object"
... ...
@@ -7,6 +7,9 @@ package types
7 7
 // swagger:model Volume
8 8
 type Volume struct {
9 9
 
10
+	// Time volume was created.
11
+	CreatedAt string `json:"CreatedAt,omitempty"`
12
+
10 13
 	// Name of the volume driver used by the volume.
11 14
 	// Required: true
12 15
 	Driver string `json:"Driver"`
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"os"
7 7
 	"path/filepath"
8 8
 	"strings"
9
+	"time"
9 10
 
10 11
 	"github.com/Sirupsen/logrus"
11 12
 	dockererrors "github.com/docker/docker/api/errors"
... ...
@@ -27,9 +28,11 @@ type mounts []container.Mount
27 27
 
28 28
 // volumeToAPIType converts a volume.Volume to the type used by the Engine API
29 29
 func volumeToAPIType(v volume.Volume) *types.Volume {
30
+	createdAt, _ := v.CreatedAt()
30 31
 	tv := &types.Volume{
31
-		Name:   v.Name(),
32
-		Driver: v.DriverName(),
32
+		Name:      v.Name(),
33
+		Driver:    v.DriverName(),
34
+		CreatedAt: createdAt.Format(time.RFC3339),
33 35
 	}
34 36
 	if v, ok := v.(volume.DetailedVolume); ok {
35 37
 		tv.Labels = v.Labels()
... ...
@@ -2,8 +2,11 @@ package main
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 	"net/http"
6 7
 	"path/filepath"
8
+	"strings"
9
+	"time"
7 10
 
8 11
 	"github.com/docker/docker/api/types"
9 12
 	volumetypes "github.com/docker/docker/api/types/volume"
... ...
@@ -69,6 +72,8 @@ func (s *DockerSuite) TestVolumesAPIInspect(c *check.C) {
69 69
 	config := volumetypes.VolumesCreateBody{
70 70
 		Name: "test",
71 71
 	}
72
+	// sampling current time minus a minute so to now have false positive in case of delays
73
+	now := time.Now().Truncate(time.Minute)
72 74
 	status, b, err := request.SockRequest("POST", "/volumes/create", config, daemonHost())
73 75
 	c.Assert(err, check.IsNil)
74 76
 	c.Assert(status, check.Equals, http.StatusCreated, check.Commentf(string(b)))
... ...
@@ -87,4 +92,12 @@ func (s *DockerSuite) TestVolumesAPIInspect(c *check.C) {
87 87
 	c.Assert(status, checker.Equals, http.StatusOK, check.Commentf(string(b)))
88 88
 	c.Assert(json.Unmarshal(b, &vol), checker.IsNil)
89 89
 	c.Assert(vol.Name, checker.Equals, config.Name)
90
+
91
+	// comparing CreatedAt field time for the new volume to now. Removing a minute from both to avoid false positive
92
+	testCreatedAt, err := time.Parse(time.RFC3339, strings.TrimSpace(vol.CreatedAt))
93
+	c.Assert(err, check.IsNil)
94
+	testCreatedAt = testCreatedAt.Truncate(time.Minute)
95
+	if !testCreatedAt.Equal(now) {
96
+		c.Assert(fmt.Errorf("Time Volume is CreatedAt not equal to current time"), check.NotNil)
97
+	}
90 98
 }
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"path/filepath"
6 6
 	"strings"
7
+	"time"
7 8
 
8 9
 	"github.com/Sirupsen/logrus"
9 10
 	"github.com/docker/docker/volume"
... ...
@@ -82,6 +83,7 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
82 82
 		name:         v.Name,
83 83
 		driverName:   a.Name(),
84 84
 		eMount:       v.Mountpoint,
85
+		createdAt:    v.CreatedAt,
85 86
 		status:       v.Status,
86 87
 		baseHostPath: a.baseHostPath,
87 88
 	}, nil
... ...
@@ -124,13 +126,15 @@ type volumeAdapter struct {
124 124
 	name         string
125 125
 	baseHostPath string
126 126
 	driverName   string
127
-	eMount       string // ephemeral host volume path
127
+	eMount       string    // ephemeral host volume path
128
+	createdAt    time.Time // time the directory was created
128 129
 	status       map[string]interface{}
129 130
 }
130 131
 
131 132
 type proxyVolume struct {
132 133
 	Name       string
133 134
 	Mountpoint string
135
+	CreatedAt  time.Time
134 136
 	Status     map[string]interface{}
135 137
 }
136 138
 
... ...
@@ -168,6 +172,9 @@ func (a *volumeAdapter) Unmount(id string) error {
168 168
 	return err
169 169
 }
170 170
 
171
+func (a *volumeAdapter) CreatedAt() (time.Time, error) {
172
+	return a.createdAt, nil
173
+}
171 174
 func (a *volumeAdapter) Status() map[string]interface{} {
172 175
 	out := make(map[string]interface{}, len(a.status))
173 176
 	for k, v := range a.status {
... ...
@@ -8,8 +8,11 @@ package local
8 8
 import (
9 9
 	"fmt"
10 10
 	"net"
11
+	"os"
11 12
 	"path/filepath"
12 13
 	"strings"
14
+	"syscall"
15
+	"time"
13 16
 
14 17
 	"github.com/pkg/errors"
15 18
 
... ...
@@ -85,3 +88,12 @@ func (v *localVolume) mount() error {
85 85
 	err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts)
86 86
 	return errors.Wrapf(err, "error while mounting volume with options: %s", v.opts)
87 87
 }
88
+
89
+func (v *localVolume) CreatedAt() (time.Time, error) {
90
+	fileInfo, err := os.Stat(v.path)
91
+	if err != nil {
92
+		return time.Time{}, err
93
+	}
94
+	sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix()
95
+	return time.Unix(sec, nsec), nil
96
+}
... ...
@@ -5,8 +5,11 @@ package local
5 5
 
6 6
 import (
7 7
 	"fmt"
8
+	"os"
8 9
 	"path/filepath"
9 10
 	"strings"
11
+	"syscall"
12
+	"time"
10 13
 )
11 14
 
12 15
 type optsConfig struct{}
... ...
@@ -32,3 +35,12 @@ func setOpts(v *localVolume, opts map[string]string) error {
32 32
 func (v *localVolume) mount() error {
33 33
 	return nil
34 34
 }
35
+
36
+func (v *localVolume) CreatedAt() (time.Time, error) {
37
+	fileInfo, err := os.Stat(v.path)
38
+	if err != nil {
39
+		return time.Time{}, err
40
+	}
41
+	ft := fileInfo.Sys().(*syscall.Win32FileAttributeData).CreationTime
42
+	return time.Unix(0, ft.Nanoseconds()), nil
43
+}
... ...
@@ -2,6 +2,7 @@ package testutils
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"time"
5 6
 
6 7
 	"github.com/docker/docker/volume"
7 8
 )
... ...
@@ -27,6 +28,9 @@ func (NoopVolume) Unmount(_ string) error { return nil }
27 27
 // Status proivdes low-level details about the volume
28 28
 func (NoopVolume) Status() map[string]interface{} { return nil }
29 29
 
30
+// CreatedAt provides the time the volume (directory) was created at
31
+func (NoopVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
32
+
30 33
 // FakeVolume is a fake volume with a random name
31 34
 type FakeVolume struct {
32 35
 	name       string
... ...
@@ -56,6 +60,9 @@ func (FakeVolume) Unmount(_ string) error { return nil }
56 56
 // Status proivdes low-level details about the volume
57 57
 func (FakeVolume) Status() map[string]interface{} { return nil }
58 58
 
59
+// CreatedAt provides the time the volume (directory) was created at
60
+func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
61
+
59 62
 // FakeDriver is a driver that generates fake volumes
60 63
 type FakeDriver struct {
61 64
 	name string
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"path/filepath"
7 7
 	"strings"
8 8
 	"syscall"
9
+	"time"
9 10
 
10 11
 	mounttypes "github.com/docker/docker/api/types/mount"
11 12
 	"github.com/docker/docker/pkg/idtools"
... ...
@@ -64,6 +65,8 @@ type Volume interface {
64 64
 	Mount(id string) (string, error)
65 65
 	// Unmount unmounts the volume when it is no longer in use.
66 66
 	Unmount(id string) error
67
+	// CreatedAt returns Volume Creation time
68
+	CreatedAt() (time.Time, error)
67 69
 	// Status returns low-level status information about a volume
68 70
 	Status() map[string]interface{}
69 71
 }