dded retry logic to setmeta's optimistic concurrency approach.

See https://codereview.appspot.com/7311079/.


git-svn-id: svn://svn.chromium.org/gsutil/trunk/src@332 56d8b958-6b11-9cbd-d51d-67dd3ef4e9fa
diff --git a/gslib/commands/setmeta.py b/gslib/commands/setmeta.py
index 232a682..a1f5932 100755
--- a/gslib/commands/setmeta.py
+++ b/gslib/commands/setmeta.py
@@ -15,8 +15,11 @@
 
 import boto
 import csv
+import random
 import StringIO
+import time
 
+from boto.exception import GSResponseError
 from boto.s3.key import Key
 from gslib.command import COMMAND_NAME
 from gslib.command import COMMAND_NAME_ALIASES
@@ -213,12 +216,36 @@
       self.THREADED_LOGGER.error(str(e))
       self.everything_set_okay = False
 
-    def _SetMetadataFunc(name_expansion_result):
+    def _SetMetadataFunc(name_expansion_result, retry=3):
       exp_src_uri = self.suri_builder.StorageUri(
           name_expansion_result.GetExpandedUriStr())
-      self.THREADED_LOGGER.info('Setting metadata on %s...' % exp_src_uri)
-      exp_src_uri.set_metadata(metadata_plus, metadata_minus, preserve_acl)
-
+      self.THREADED_LOGGER.info('Setting metadata on %s...', exp_src_uri)
+      
+      key = exp_src_uri.get_key()
+      meta_generation = key.meta_generation
+      generation = key.generation
+            
+      headers = {}
+      if generation:
+        headers['x-goog-if-generation-match'] = generation
+      if meta_generation:
+        headers['x-goog-if-metageneration-match'] = meta_generation
+          
+      try:
+        exp_src_uri.set_metadata(metadata_plus, metadata_minus, preserve_acl, 
+                                 headers=headers)
+      except GSResponseError as response_error:
+        # HTTP error 412 is "Precondition Failed."
+        if response_error.status == 412:
+          if retry <= 0:
+            self.THREADED_LOGGER.error('Exhausted retries. Giving up.')
+            raise
+          self.THREADED_LOGGER.warn('Collision - %d tries left.', retry)
+          time.sleep(random.uniform(0.5, 1.0))
+          _SetMetadataFunc(name_expansion_result, retry-1)
+        else:
+          raise
+      
     name_expansion_iterator = NameExpansionIterator(
         self.command_name, self.proj_id_handler, self.headers, self.debug,
         self.bucket_storage_uri_class, uri_args, self.recursion_requested,