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>
| ... | ... |
@@ -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" |
| ... | ... |
@@ -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 |
} |