]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/util.py
Rewrite parametrize to use new parametrizeWithVars
[wizard.git] / wizard / util.py
index 2d9c72f118467f9cbc14525acd237927b3a87b08..6c9764d317d88d4dd8f6f05b4546d91778665ec4 100644 (file)
@@ -17,6 +17,10 @@ import itertools
 import signal
 import httplib
 import urllib
+import time
+import logging
+import random
+import string
 
 import wizard
 
@@ -57,6 +61,15 @@ class Counter(object):
         return self.dict[key]
     def __iter__(self):
         return self.dict.__iter__()
+    def max(self):
+        """Returns the max counter value seen."""
+        return max(self.dict.values())
+    def sum(self):
+        """Returns the sum of all counter values."""
+        return sum(self.dict.values())
+    def keys(self):
+        """Returns the keys of counters."""
+        return self.dict.keys()
 
 class PipeToLess(object):
     """
@@ -87,17 +100,47 @@ class LockDirectory(object):
     """
     Context for locking a directory.
     """
-    def __init__(self, lockfile):
+    def __init__(self, lockfile, expiry = 3600):
         self.lockfile = lockfile
+        self.expiry = expiry # by default an hour
     def __enter__(self):
-        try:
-            os.open(self.lockfile, os.O_CREAT | os.O_EXCL)
-        except OSError as e:
-            if e.errno == errno.EEXIST:
-                raise DirectoryLockedError(os.getcwd())
-            elif e.errno == errno.EACCES:
-                raise command.PermissionsError(os.getcwd())
-            raise
+        # It's A WAVY
+        for i in range(0, 3):
+            try:
+                os.open(self.lockfile, os.O_CREAT | os.O_EXCL)
+                open(self.lockfile, "w").write("%d" % os.getpid())
+            except OSError as e:
+                if e.errno == errno.EEXIST:
+                    # There is a possibility of infinite recursion, but we
+                    # expect it to be unlikely, and not harmful if it does happen
+                    with LockDirectory(self.lockfile + "_"):
+                        # See if we can break the lock
+                        try:
+                            pid = open(self.lockfile, "r").read().strip()
+                            if not os.path.exists("/proc/%s" % pid):
+                                # break the lock, try again
+                                logging.warning("Breaking orphaned lock at %s", self.lockfile)
+                                os.unlink(self.lockfile)
+                                continue
+                            try:
+                                # check if the file is expiry old, if so, break the lock, try again
+                                if time.time() - os.stat(self.lockfile).st_mtime > self.expiry:
+                                    logging.warning("Breaking stale lock at %s", self.lockfile)
+                                    os.unlink(self.lockfile)
+                                    continue
+                            except OSError as e:
+                                if e.errno == errno.ENOENT:
+                                    continue
+                                raise
+                        except IOError:
+                            # oh hey, it went away; try again
+                            continue
+                    raise DirectoryLockedError(os.getcwd())
+                elif e.errno == errno.EACCES:
+                    raise PermissionsError(os.getcwd())
+                raise
+            return
+        raise DirectoryLockedError(os.getcwd())
     def __exit__(self, *args):
         try:
             os.unlink(self.lockfile)
@@ -152,10 +195,7 @@ def get_exception_name(output):
             continue
         if cue:
             cue = False
-            if line[-1] == ":":
-                result = line[:-1]
-            else:
-                result = line
+            return line.partition(':')[0]
     return result
 
 def get_dir_uid(dir):
@@ -172,9 +212,13 @@ def get_dir_owner(dir = "."):
         only works on scripts servers when querying directories
         that live on AFS.
     """
-    pwentry = pwd.getpwuid(get_dir_uid(dir))
-    # XXX: Error handling!
-    return pwentry.pw_name
+    uid = get_dir_uid(dir)
+    try:
+        pwentry = pwd.getpwuid(uid)
+        return pwentry.pw_name
+    except KeyError:
+        # do an pts query to get the name
+        return subprocess.Popen(['pts', 'examine', str(uid)], stdout=subprocess.PIPE).communicate()[0].partition(",")[0].partition(": ")[2]
 
 def get_revision():
     """Returns the commit ID of the current Wizard install."""
@@ -283,6 +327,8 @@ def get_git_footer():
 
 def safe_unlink(file):
     """Moves a file/dir to a backup location."""
+    if not os.path.exists(file):
+        return None
     prefix = "%s.bak" % file
     name = None
     for i in itertools.count():
@@ -292,18 +338,45 @@ def safe_unlink(file):
     os.rename(file, name)
     return name
 
+def soft_unlink(file):
+    """Unlink a file, but don't complain if it doesn't exist."""
+    try:
+        os.unlink(file)
+    except OSError:
+        pass
+
 def fetch(host, path, subpath, post=None):
-    h = httplib.HTTPConnection(host)
-    fullpath = path + "/" + subpath
-    if post:
-        headers = {"Content-type": "application/x-www-form-urlencoded"}
-        h.request("POST", fullpath, urllib.urlencode(post), headers)
-    else:
-        h.request("GET", fullpath)
-    r = h.getresponse()
-    data = r.read()
-    h.close()
-    return data
+    try:
+        # XXX: Special case if it's https; not sure why this data isn't
+        # passed
+        h = httplib.HTTPConnection(host)
+        fullpath = path.rstrip("/") + "/" + subpath.lstrip("/") # to be lenient about input we accept
+        if post:
+            headers = {"Content-type": "application/x-www-form-urlencoded"}
+            h.request("POST", fullpath, urllib.urlencode(post), headers)
+        else:
+            h.request("GET", fullpath)
+        r = h.getresponse()
+        data = r.read()
+        h.close()
+        return data
+    except socket.gaierror as e:
+        if e.errno == socket.EAI_NONAME:
+            raise DNSError(host)
+        else:
+            raise
+
+def mixed_newlines(filename):
+    """Returns ``True`` if ``filename`` has mixed newlines."""
+    f = open(filename, "U") # requires universal newline support
+    f.read()
+    ret = isinstance(f.newlines, tuple)
+    f.close() # just to be safe
+    return ret
+
+def random_key(length=30):
+    """Generates a random alphanumeric key of ``length`` size."""
+    return ''.join(random.choice(string.letters + string.digits) for i in xrange(length))
 
 class NoOperatorInfo(wizard.Error):
     """No information could be found about the operator from Kerberos."""
@@ -325,3 +398,14 @@ ERROR: Could not acquire lock on directory.  Maybe there is
 another migration process running?
 """
 
+class DNSError(socket.gaierror):
+    errno = socket.EAI_NONAME
+    #: Hostname that could not resolve name
+    host = None
+    def __init__(self, host):
+        self.host = host
+    def __str__(self):
+        return """
+
+ERROR: Could not resolve hostname %s.
+""" % self.host