From ca53d5fe323900561b24f77ab72c76322a8150fc Mon Sep 17 00:00:00 2001 From: Ryan Wallner Date: Mon, 1 Aug 2016 17:47:08 +0000 Subject: [PATCH 1/3] first pass at adding ID to Mount Unmount for docker 1.12 --- flocker/dockerplugin/_api.py | 10 ++++++++-- flocker/dockerplugin/test/test_api.py | 22 +++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/flocker/dockerplugin/_api.py b/flocker/dockerplugin/_api.py index 11aab5adb9..e2bda611ff 100644 --- a/flocker/dockerplugin/_api.py +++ b/flocker/dockerplugin/_api.py @@ -197,11 +197,12 @@ def volumedriver_remove(self, Name): @app.route("/VolumeDriver.Unmount", methods=["POST"]) @_endpoint(u"Unmount") - def volumedriver_unmount(self, Name): + def volumedriver_unmount(self, Name, ID): """ The Docker container is no longer using the given volume. :param unicode Name: The name of the volume. + :param string ID: A unique ID for caller that requested the mount For now this does nothing. In FLOC-2755 this will release the lease acquired for the dataset by the ``VolumeDriver.Mount`` @@ -313,7 +314,7 @@ def got_state(datasets): @app.route("/VolumeDriver.Mount", methods=["POST"]) @_endpoint(u"Mount") - def volumedriver_mount(self, Name): + def volumedriver_mount(self, Name, ID): """ Move a volume with the given name to the current node and mount it. @@ -321,6 +322,11 @@ def volumedriver_mount(self, Name): dataset is mounted locally. :param unicode Name: The name of the volume. + :param string ID: A unique ID for caller that requested the mount + + Right now we do nothing with ID, but it can be used to track + the mount calls when multiple containers are trying to mount + the same volume. :return: Result that includes the mountpoint. """ diff --git a/flocker/dockerplugin/test/test_api.py b/flocker/dockerplugin/test/test_api.py index 587cf37edf..17b8e8649f 100644 --- a/flocker/dockerplugin/test/test_api.py +++ b/flocker/dockerplugin/test/test_api.py @@ -4,6 +4,7 @@ Tests for the Volumes Plugin API provided by the plugin. """ +import random from uuid import uuid4 from bitmath import TiB, GiB, MiB, KiB, Byte @@ -115,8 +116,11 @@ def test_unmount(self): """ ``/VolumeDriver.Unmount`` returns a successful result. """ + unmount_id = ''.join(random.choice( + '0123456789abcdef') for n in xrange(64)) return self.assertResult(b"POST", b"/VolumeDriver.Unmount", - {u"Name": u"vol"}, OK, {u"Err": u""}) + {u"Name": u"vol", unicode(unmount_id): u""}, + OK, {u"Err": u""}) def test_create_with_profile(self): """ @@ -320,6 +324,8 @@ def test_mount(self): """ name = u"myvol" dataset_id = uuid4() + mount_id = ''.join(random.choice( + '0123456789abcdef') for n in xrange(64)) # Create dataset on a different node: d = self.flocker_client.create_dataset( @@ -337,7 +343,7 @@ def test_mount(self): d.addCallback(lambda _: self.assertResult( b"POST", b"/VolumeDriver.Mount", - {u"Name": name}, OK, + {u"Name": name, u"ID": unicode(mount_id)}, OK, {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(dataset_id)})) d.addCallback(lambda _: self.flocker_client.list_datasets_state()) @@ -363,6 +369,8 @@ def test_mount_timeout(self): """ name = u"myvol" dataset_id = uuid4() + mount_id = ''.join(random.choice( + '0123456789abcdef') for n in xrange(64)) # Create dataset on a different node: d = self.flocker_client.create_dataset( self.NODE_B, int(DEFAULT_SIZE.to_Byte()), @@ -379,7 +387,7 @@ def test_mount_timeout(self): d.addCallback(lambda _: self.assertResult( b"POST", b"/VolumeDriver.Mount", - {u"Name": name}, OK, + {u"Name": name, u"ID": unicode(mount_id)}, OK, {u"Err": u"Timed out waiting for dataset to mount.", u"Mountpoint": u""})) return d @@ -392,6 +400,8 @@ def test_mount_already_exists(self): don't have a special dataset ID. """ name = u"myvol" + mount_id = ''.join(random.choice( + '0123456789abcdef') for n in xrange(64)) d = self.flocker_client.create_dataset( self.NODE_A, int(DEFAULT_SIZE.to_Byte()), @@ -401,7 +411,7 @@ def created(dataset): self.flocker_client.synchronize_state() result = self.assertResult( b"POST", b"/VolumeDriver.Mount", - {u"Name": name}, OK, + {u"Name": name, u"ID": unicode(mount_id)}, OK, {u"Err": u"", u"Mountpoint": u"/flocker/{}".format( dataset.dataset_id)}) @@ -420,9 +430,11 @@ def test_unknown_mount(self): non-existent volume. """ name = u"myvol" + mount_id = ''.join(random.choice( + '0123456789abcdef') for n in xrange(64)) return self.assertResult( b"POST", b"/VolumeDriver.Mount", - {u"Name": name}, OK, + {u"Name": name, u"ID": unicode(mount_id)}, OK, {u"Err": u"Could not find volume with given name."}) def test_path(self): From 2040fe7b32de7516a21eb199ccf7816bb8010dea Mon Sep 17 00:00:00 2001 From: Ryan Wallner Date: Mon, 1 Aug 2016 14:31:24 -0400 Subject: [PATCH 2/3] fix test where ID value used as key Signed-off-by: Ryan Wallner --- flocker/dockerplugin/test/test_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flocker/dockerplugin/test/test_api.py b/flocker/dockerplugin/test/test_api.py index 17b8e8649f..ca28f9ebc3 100644 --- a/flocker/dockerplugin/test/test_api.py +++ b/flocker/dockerplugin/test/test_api.py @@ -119,7 +119,8 @@ def test_unmount(self): unmount_id = ''.join(random.choice( '0123456789abcdef') for n in xrange(64)) return self.assertResult(b"POST", b"/VolumeDriver.Unmount", - {u"Name": u"vol", unicode(unmount_id): u""}, + {u"Name": u"vol", + u"ID": unicode(unmount_id)}, OK, {u"Err": u""}) def test_create_with_profile(self): From 24bab0d4bf6b0232d4236436034f1cf137df2e78 Mon Sep 17 00:00:00 2001 From: Ryan Wallner Date: Tue, 2 Aug 2016 11:03:52 -0400 Subject: [PATCH 3/3] add default value for backward compat with docker < 1.12, add no_id tests Signed-off-by: Ryan Wallner --- flocker/dockerplugin/_api.py | 4 +- flocker/dockerplugin/test/test_api.py | 54 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/flocker/dockerplugin/_api.py b/flocker/dockerplugin/_api.py index e2bda611ff..16e6668016 100644 --- a/flocker/dockerplugin/_api.py +++ b/flocker/dockerplugin/_api.py @@ -197,7 +197,7 @@ def volumedriver_remove(self, Name): @app.route("/VolumeDriver.Unmount", methods=["POST"]) @_endpoint(u"Unmount") - def volumedriver_unmount(self, Name, ID): + def volumedriver_unmount(self, Name, ID=None): """ The Docker container is no longer using the given volume. @@ -314,7 +314,7 @@ def got_state(datasets): @app.route("/VolumeDriver.Mount", methods=["POST"]) @_endpoint(u"Mount") - def volumedriver_mount(self, Name, ID): + def volumedriver_mount(self, Name, ID=None): """ Move a volume with the given name to the current node and mount it. diff --git a/flocker/dockerplugin/test/test_api.py b/flocker/dockerplugin/test/test_api.py index ca28f9ebc3..67c7423da4 100644 --- a/flocker/dockerplugin/test/test_api.py +++ b/flocker/dockerplugin/test/test_api.py @@ -123,6 +123,16 @@ def test_unmount(self): u"ID": unicode(unmount_id)}, OK, {u"Err": u""}) + def test_unmount_no_id(self): + """ + ``/VolumeDriver.Unmount`` returns a successful result. + + No ID for backward compatability with Docker < 1.12 + """ + return self.assertResult(b"POST", b"/VolumeDriver.Unmount", + {u"Name": u"vol"}, + OK, {u"Err": u""}) + def test_create_with_profile(self): """ Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value @@ -361,6 +371,50 @@ def final_assertions(datasets): return d + def test_mount_no_id(self): + """ + ``/VolumeDriver.Mount`` sets the primary of the dataset with matching + name to the current node and then waits for the dataset to + actually arrive. + + No ID for backward compatability with Docker < 1.12 + """ + name = u"myvol" + dataset_id = uuid4() + + # Create dataset on a different node: + d = self.flocker_client.create_dataset( + self.NODE_B, int(DEFAULT_SIZE.to_Byte()), + metadata={NAME_FIELD: name}, + dataset_id=dataset_id) + + self._flush_volume_plugin_reactor_on_endpoint_render() + + # Pretend that it takes 5 seconds for the dataset to get established on + # Node A. + self.volume_plugin_reactor.callLater( + 5.0, self.flocker_client.synchronize_state) + + d.addCallback(lambda _: + self.assertResult( + b"POST", b"/VolumeDriver.Mount", + {u"Name": name}, OK, + {u"Err": u"", + u"Mountpoint": u"/flocker/{}".format(dataset_id)})) + d.addCallback(lambda _: self.flocker_client.list_datasets_state()) + + def final_assertions(datasets): + self.assertEqual([self.NODE_A], + [d.primary for d in datasets + if d.dataset_id == dataset_id]) + # There should be less than 20 calls to list_datasets_state over + # the course of 5 seconds. + self.assertLess( + self.flocker_client.num_calls('list_datasets_state'), 20) + d.addCallback(final_assertions) + + return d + def test_mount_timeout(self): """ ``/VolumeDriver.Mount`` sets the primary of the dataset with matching