Support for -s. More comments. Removed out of date documentation and bwcap.
authorDavid E. Eisenstat <deisenst@cs.princeton.edu>
Mon, 30 Oct 2006 16:13:54 +0000 (16:13 +0000)
committerDavid E. Eisenstat <deisenst@cs.princeton.edu>
Mon, 30 Oct 2006 16:13:54 +0000 (16:13 +0000)
README.txt [deleted file]
accounts.py
api.py
bwcap.py [deleted file]
database.py
forward_api_calls.c
nm.py
sliver_vs.py
sm.py [new file with mode: 0644]
tools.py

diff --git a/README.txt b/README.txt
deleted file mode 100644 (file)
index f532a85..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-THE NEW NODE MANAGER
-====================
-
-This is a very preliminary version of the new node manager.  Currently
-it is set up to download slices.xml; however, not all of the
-implemented functionality is accessible via slices.xml.
-
-FILES
-=====
-
-accounts.py - Account management functionality generic between
-delegate accounts and VServers.
-
-api.py - XMLRPC interface to Node Manager functionality.  Runs on port
-812, supports a Help() call with more information.
-
-bwcap.py - Sets the bandwidth cap via the bwlimit module.  The bwlimit
-calls are commented out because they've been giving me a bunch of
-errors.
-
-config.py - Configuration parameters.  You'll probably want to change
-SA_HOSTNAME to the PLC address.
-
-database.py - The dreaded NM database.  The main class defined is a
-dict subclass, which both indexes and stores various records.  These
-records include the sliver/delegate records, as well as the timestamp,
-node bw cap, and any other crap PLC wants to put there.
-
-delegate.py - Create and delete delegate accounts.  These accounts
-have low space overhead (unlike a VServer) and serve to authenticate
-remote NM users.
-
-forward_api_calls.c - The forward_api_calls program proxies stdin to
-the Unix domain socket /tmp/node_mgr_api, letting Node Manager take
-advantage of ssh authentication.  It is intended for use as a shell on
-a special delegate account.
-
-logger.py - This is a very basic logger.
-
-Makefile - For compiling forward_api_calls.
-
-nm.py - The main program.
-
-plc.py - Downloads and parses slices.xml, reads the node id file.
-
-README.txt - Duh.
-
-sliver.py - Handles all VServer functionality.
-
-ticket.py - Not used at the moment; contains a demonstration of
-xmlsec1.
-
-tools.py - Various convenience functions for functionality provided by
-Linux.
-
-RUNNING
-=======
-
-Change SA_HOSTNAME in config.py and run nm.py.  No bootstrapping
-required.
-
-INTERNALS
-=========
-
-At the moment, the main thread loops forever, fetching slices.xml and
-updating the database.  Other threads handle incoming API connections
-(each connection is handled by a separate thread) and the database
-dumper.  There is also one thread per account, which supervises
-creation/deletion/resource initialization for that account.  The other
-threads request operations by means of a queue.
-
-Other than the queues, the threads synchronize by acquiring a global
-database lock before reading/writing the database.  The database
-itself is a collection of records, which are just Python dicts with
-certain required fields.  The most important of these fields are
-'timestamp', 'expiry', and 'record_key'.  'record_key' serves to
-uniquely identify a particular record; the only naming conventions
-followed are that account records have record_key <account
-type>_<account name>; thus sliver princeton_sirius has record_key
-'sliver_princeton_sirius'.
-
-The two main features that will not be familiar from the old node
-manager are delegates and loans.  Delegates, as described above, are
-lightweight accounts whose sole purpose is to proxy NM API calls from
-outside.  The current code makes a delegate account 'del_snoop' that's
-allowed to spy on everyone's RSpec; you'll need to change the key in
-plc.py order to use it.  Loans are resource transfers from one sliver
-to another; the format for loans is a list of triples: recipient
-sliver, resource type, amount.  Thus for princeton_sirius to give 20%
-guaranteed CPU to princeton_eisentest, it would call
-
-api.SetLoans(['princeton_eisentest', 'nm_cpu_guaranteed_share', 200])
-
-provided, of course, that it has 200 guaranteed shares :)
-
-POSTSCRIPT
-==========
-
-The log file will come in a great deal of use when attempting to
-use/debug node manager; it lives at /var/log/pl_node_mgr.log.  If you
-break the DB, you should kill the pickled copy, which lives at
-<config.py:DB_FILE>.
-
-I have been refactoring the code constantly in an attempt to keep the
-amount of glue to a minimum; unfortunately comments quickly grow stale
-in such an environment, and I have not yet made any attempt to comment
-reasonably.  Until such time as I do, I'm on the hook for limited
-support of this thing.  Please feel free to contact me at
-deisenst@cs.princeton.edu.
index 8363e7f..da88049 100644 (file)
@@ -1,12 +1,14 @@
 """Functionality common to all account classes.
 
 """Functionality common to all account classes.
 
-Each subclass of Account must provide five methods: create(),
-destroy(), configure(), start(), and stop().  In addition, it must
+Each subclass of Account must provide five methods: create() and
+destroy(), which are static; configure(), start(), and stop(), which
+are not.  configure(), which takes a record as its only argument, does
+things like set up ssh keys.  In addition, an Account subclass must
 provide static member variables SHELL, which contains the unique shell
 provide static member variables SHELL, which contains the unique shell
-that it uses; and TYPE, which contains a description of the type that
-it uses.  TYPE is divided hierarchically by periods; at the moment the
-only convention is that all sliver accounts have type that begins with
-sliver.
+that it uses; and TYPE, a string that is used by the account creation
+code.  For no particular reason, TYPE is divided hierarchically by
+periods; at the moment the only convention is that all sliver accounts
+have type that begins with sliver.
 
 There are any number of race conditions that may result from the fact
 that account names are not unique over time.  Moreover, it's a bad
 
 There are any number of race conditions that may result from the fact
 that account names are not unique over time.  Moreover, it's a bad
@@ -78,7 +80,7 @@ class Account:
             logger.log('%s: installing ssh keys' % self.name)
             tools.fork_as(self.name, do_installation)
 
             logger.log('%s: installing ssh keys' % self.name)
             tools.fork_as(self.name, do_installation)
 
-    def start(self): pass
+    def start(self, delay=0): pass
     def stop(self): pass
 
 
     def stop(self): pass
 
 
@@ -115,8 +117,8 @@ class Worker:
     def ensure_destroyed(self): self._q.put((self._ensure_destroyed,))
     def _ensure_destroyed(self): self._destroy(self._get_class())
 
     def ensure_destroyed(self): self._q.put((self._ensure_destroyed,))
     def _ensure_destroyed(self): self._destroy(self._get_class())
 
-    def start(self): self._q.put((self._start,))
-    def _start(self): self._acct.start()
+    def start(self, delay=0): self._q.put((self._start, delay))
+    def _start(self, d): self._acct.start(delay=d)
 
     def stop(self): self._q.put((self._stop,))
     def _stop(self): self._acct.stop()
 
     def stop(self): self._q.put((self._stop,))
     def _stop(self): self._acct.stop()
@@ -134,6 +136,7 @@ class Worker:
         return shell_acct_class[shell]
 
     def _run(self):
         return shell_acct_class[shell]
 
     def _run(self):
+        """Repeatedly pull commands off the queue and execute.  If memory usage becomes an issue, it might be wise to terminate after a while."""
         while True:
             try:
                 cmd = self._q.get()
         while True:
             try:
                 cmd = self._q.get()
diff --git a/api.py b/api.py
index c4dec57..0dd7115 100644 (file)
--- a/api.py
+++ b/api.py
@@ -1,6 +1,15 @@
+"""Sliver manager API.
+
+This module exposes an XMLRPC interface that allows PlanetLab users to
+create/destroy slivers with delegated instantiation, start and stop
+slivers, make resource loans, and examine resource allocations.  The
+XMLRPC is provided on a localhost-only TCP port as well as via a Unix
+domain socket that is accessible by ssh-ing into a delegate account
+with the forward_api_calls shell.
+"""
+
 import SimpleXMLRPCServer
 import SocketServer
 import SimpleXMLRPCServer
 import SocketServer
-import cPickle
 import errno
 import os
 import pwd
 import errno
 import os
 import pwd
@@ -16,7 +25,7 @@ import tools
 
 
 API_SERVER_PORT = 812
 
 
 API_SERVER_PORT = 812
-UNIX_ADDR = '/tmp/node_mgr.api'
+UNIX_ADDR = '/tmp/sliver_mgr.api'
 
 
 api_method_dict = {}
 
 
 api_method_dict = {}
@@ -36,13 +45,13 @@ def Help():
     return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
 
 @export_to_api(1)
     return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
 
 @export_to_api(1)
-def CreateSliver(rec):
-    """CreateSliver(sliver_name): create a non-PLC-instantiated sliver"""
+def Create(rec):
+    """Create(sliver_name): create a non-PLC-instantiated sliver"""
     if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
 
 @export_to_api(1)
     if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
 
 @export_to_api(1)
-def DestroySliver(rec):
-    """DestroySliver(sliver_name): destroy a non-PLC-instantiated sliver"""
+def Destroy(rec):
+    """Destroy(sliver_name): destroy a non-PLC-instantiated sliver"""
     if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
 
 @export_to_api(1)
     if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
 
 @export_to_api(1)
@@ -72,7 +81,7 @@ def GetLoans(rec):
 
 def validate_loans(obj):
     """Check that <obj> is a valid loan specification."""
 
 def validate_loans(obj):
     """Check that <obj> is a valid loan specification."""
-    def validate_loan(obj): return (type(obj)==list or type(obj)==tuple) and len(obj)==3 and type(obj[0])==str and type(obj[1])==str and obj[1] in database.LOANABLE_RESOURCES and type(obj[2])==int and obj[2]>0
+    def validate_loan(obj): return (type(obj)==list or type(obj)==tuple) and len(obj)==3 and type(obj[0])==str and type(obj[1])==str and obj[1] in database.LOANABLE_RESOURCES and type(obj[2])==int and obj[2]>=0
     return type(obj)==list and False not in map(validate_loan, obj)
 
 @export_to_api(2)
     return type(obj)==list and False not in map(validate_loan, obj)
 
 @export_to_api(2)
diff --git a/bwcap.py b/bwcap.py
deleted file mode 100644 (file)
index 3d95db0..0000000
--- a/bwcap.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import bwlimit
-
-import logger
-import tools
-
-
-_old_rec = {}
-
-def update(rec):
-    global _old_rec
-    if rec != _old_rec:
-        if rec['cap'] != _old_rec.get('cap'):
-            logger.log('setting node bw cap to %d' % rec['cap'])
-#             bwlimit.init('eth0', rec['cap'])
-        if rec['exempt_ips'] != _old_rec.get('exempt_ips'):
-            logger.log('initializing exempt ips to %s' % rec['exempt_ips'])
-#             bwlimit.exempt_init('Internet2', rec['exempt_ips'])
-        _old_rec = tools.deepcopy(rec)
index c762638..d7ddf04 100644 (file)
@@ -1,19 +1,32 @@
+"""The database houses information on slivers.  This information
+reaches the sliver manager in two different ways: one, through the
+GetSlivers() call made periodically; two, by users delivering tickets.
+The sync() method of the Database class turns this data into reality.
+
+The database format is a dictionary that maps account names to records
+(also known as dictionaries).  Inside each record, data supplied or
+computed locally is stored under keys that begin with an underscore,
+and data from PLC is stored under keys that don't.
+
+In order to maintain service when the node reboots during a network
+partition, the database is constantly being dumped to disk.
+"""
+
 import cPickle
 import threading
 import time
 
 import cPickle
 import threading
 import time
 
-try: from bwlimit import bwmin, bwmax
-except ImportError: bwmin, bwmax = 8, 1000000000
 import accounts
 import logger
 import tools
 
 
 import accounts
 import logger
 import tools
 
 
-DB_FILE = '/root/node_mgr_db.pickle'
+# We enforce minimum allocations to keep the clueless from hosing their slivers.
+# Disallow disk loans because there's currently no way to punish slivers over quota.
+MINIMUM_ALLOCATION = {'cpu_min': 0, 'cpu_share': 16, 'net_min': 0, 'net_max': 8, 'net2_min': 0, 'net2_max': 8, 'net_share': 1}
+LOANABLE_RESOURCES = MINIMUM_ALLOCATION.keys()
 
 
-LOANABLE_RESOURCES = ['cpu_min', 'cpu_share', 'net_min', 'net_max', 'net2_min', 'net2_max', 'net_share', 'disk_max']
-
-DEFAULT_ALLOCATIONS = {'enabled': 1, 'cpu_min': 0, 'cpu_share': 32, 'net_min': bwmin, 'net_max': bwmax, 'net2_min': bwmin, 'net2_max': bwmax, 'net_share': 1, 'disk_max': 5000000}
+DB_FILE = '/root/sliver_mgr_db.pickle'
 
 
 # database object and associated lock
 
 
 # database object and associated lock
@@ -25,6 +38,7 @@ db_cond = threading.Condition(db_lock)
 dump_requested = False
 
 # decorator that acquires and releases the database lock before and after the decorated operation
 dump_requested = False
 
 # decorator that acquires and releases the database lock before and after the decorated operation
+# XXX - replace with "with" statements once we switch to 2.5
 def synchronized(fn):
     def sync_fn(*args, **kw_args):
         db_lock.acquire()
 def synchronized(fn):
     def sync_fn(*args, **kw_args):
         db_lock.acquire()
@@ -50,7 +64,7 @@ class Database(dict):
             eff_rspec = rec['_rspec']
             resid_rspec = rec['rspec'].copy()
             for target, resname, amt in rec.get('_loans', []):
             eff_rspec = rec['_rspec']
             resid_rspec = rec['rspec'].copy()
             for target, resname, amt in rec.get('_loans', []):
-                if target in slivers and amt < resid_rspec[resname]:
+                if target in slivers and amt <= resid_rspec[resname] - MINIMUM_ALLOCATION[resname]:
                     eff_rspec[resname] -= amt
                     resid_rspec[resname] -= amt
                     slivers[target]['_rspec'][resname] += amt
                     eff_rspec[resname] -= amt
                     resid_rspec[resname] -= amt
                     slivers[target]['_rspec'][resname] += amt
@@ -66,11 +80,14 @@ class Database(dict):
         elif rec['timestamp'] >= self._min_timestamp: self[name] = rec
 
     def set_min_timestamp(self, ts):
         elif rec['timestamp'] >= self._min_timestamp: self[name] = rec
 
     def set_min_timestamp(self, ts):
+        """The ._min_timestamp member is the timestamp on the last comprehensive update.  We use it to determine if a record is stale.  This method should be called whenever new GetSlivers() data comes in."""
         self._min_timestamp = ts
         for name, rec in self.items():
             if rec['timestamp'] < ts: del self[name]
 
     def sync(self):
         self._min_timestamp = ts
         for name, rec in self.items():
             if rec['timestamp'] < ts: del self[name]
 
     def sync(self):
+        """Synchronize reality with the database contents.  This method does a lot of things, and it's currently called after every single batch of database changes (a GetSlivers(), a loan, a record).  It may be necessary in the future to do something smarter."""
+
         # delete expired records
         now = time.time()
         for name, rec in self.items():
         # delete expired records
         now = time.time()
         for name, rec in self.items():
@@ -112,33 +129,3 @@ def start():
         logger.log_exc()
         db = Database()
     tools.as_daemon_thread(run)
         logger.log_exc()
         db = Database()
     tools.as_daemon_thread(run)
-
-@synchronized
-def GetSlivers_callback(data):
-    for d in data:
-        for sliver in d['slivers']:
-            rec = sliver.copy()
-            rec.setdefault('timestamp', d['timestamp'])
-            rec.setdefault('type', 'sliver.VServer')
-
-            # convert attributes field to a proper dict
-            attr_dict = {}
-            for attr in rec.pop('attributes'): attr_dict[attr['name']] = attr['value']
-
-            # squash keys
-            keys = rec.pop('keys')
-            rec.setdefault('keys', '\n'.join([key_struct['key'] for key_struct in keys]))
-
-            rec.setdefault('initscript', attr_dict.get('initscript'))
-            rec.setdefault('delegations', [])  # XXX - delegation not yet supported
-
-            # extract the implied rspec
-            rspec = {}
-            rec['rspec'] = rspec
-            for resname, default_amt in DEFAULT_ALLOCATIONS.iteritems():
-                try: amt = int(attr_dict[resname])
-                except (KeyError, ValueError): amt = default_amt
-                rspec[resname] = amt
-            db.deliver_record(rec)
-        db.set_min_timestamp(d['timestamp'])
-    db.sync()
index 52d7024..fa7741f 100644 (file)
@@ -6,12 +6,13 @@
  * Doesn't handle Unicode properly.  UTF-8 is probably OK.
  *
  * Change History:
  * Doesn't handle Unicode properly.  UTF-8 is probably OK.
  *
  * Change History:
+ * 2006/10/30: [deisenst] Changed location of Unix socket.
  * 2006/09/14: [deisenst] Switched to PF_UNIX sockets so that SO_PEERCRED works
  * 2006/09/08: [deisenst] First version.
  */
 
 static const int TIMEOUT_SECS = 30;
  * 2006/09/14: [deisenst] Switched to PF_UNIX sockets so that SO_PEERCRED works
  * 2006/09/08: [deisenst] First version.
  */
 
 static const int TIMEOUT_SECS = 30;
-const char *API_addr = "/tmp/node_mgr.api";
+const char *API_addr = "/tmp/sliver_mgr.api";
 
 static const char *Header =
   "POST / HTTP/1.0\r\n"
 
 static const char *Header =
   "POST / HTTP/1.0\r\n"
diff --git a/nm.py b/nm.py
index 1e5559d..3f52609 100644 (file)
--- a/nm.py
+++ b/nm.py
@@ -4,12 +4,8 @@ import optparse
 import time
 import xmlrpclib
 
 import time
 import xmlrpclib
 
-import accounts
-import api
-import database
-import delegate
 import logger
 import logger
-import sliver_vs
+import sm
 import tools
 
 
 import tools
 
 
@@ -26,7 +22,7 @@ def GetSlivers():
     for mod in modules: mod.GetSlivers_callback(data)
 
 def start_and_register_callback(mod):
     for mod in modules: mod.GetSlivers_callback(data)
 
 def start_and_register_callback(mod):
-    mod.start()
+    mod.start(options)
     modules.append(mod)
 
 
     modules.append(mod)
 
 
@@ -34,16 +30,13 @@ def run():
     try:
         if options.daemon: tools.daemon()
 
     try:
         if options.daemon: tools.daemon()
 
-        accounts.register_class(sliver_vs.Sliver_VS)
-        accounts.register_class(delegate.Delegate)
 
         other_pid = tools.pid_file()
         if other_pid != None:
             print """There might be another instance of the node manager running as pid %d.  If this is not the case, please remove the pid file %s""" % (other_pid, tools.PID_FILE)
             return
 
 
         other_pid = tools.pid_file()
         if other_pid != None:
             print """There might be another instance of the node manager running as pid %d.  If this is not the case, please remove the pid file %s""" % (other_pid, tools.PID_FILE)
             return
 
-        start_and_register_callback(database)
-        api.start()
+        start_and_register_callback(sm)
         while True:
             try: GetSlivers()
             except: logger.log_exc()
         while True:
             try: GetSlivers()
             except: logger.log_exc()
index 1ada697..58798c5 100644 (file)
@@ -18,6 +18,7 @@ don't have to guess if there is a running process or not.
 
 import errno
 import os
 
 import errno
 import os
+import time
 import vserver
 
 import accounts
 import vserver
 
 import accounts
@@ -26,7 +27,7 @@ import tools
 
 
 class Sliver_VS(accounts.Account, vserver.VServer):
 
 
 class Sliver_VS(accounts.Account, vserver.VServer):
-    """This class wraps vserver.VServer to make its interface closer to what we need for the Node Manager."""
+    """This class wraps vserver.VServer to make its interface closer to what we need."""
 
     SHELL = '/bin/vsh'
     TYPE = 'sliver.VServer'
 
     SHELL = '/bin/vsh'
     TYPE = 'sliver.VServer'
@@ -65,13 +66,14 @@ class Sliver_VS(accounts.Account, vserver.VServer):
 
         accounts.Account.configure(self, rec)  # install ssh keys
 
 
         accounts.Account.configure(self, rec)  # install ssh keys
 
-    def start(self):
+    def start(self, delay=0):
         if self.rspec['enabled']:
         if self.rspec['enabled']:
-            logger.log('%s: starting' % self.name)
+            logger.log('%s: starting in %d seconds' % (self.name, delay))
             child_pid = os.fork()
             if child_pid == 0:
                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
                 tools.close_nonstandard_fds()
             child_pid = os.fork()
             if child_pid == 0:
                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
                 tools.close_nonstandard_fds()
+                time.sleep(delay)
                 vserver.VServer.start(self, True)
                 os._exit(0)
             else: os.waitpid(child_pid, 0)
                 vserver.VServer.start(self, True)
                 os._exit(0)
             else: os.waitpid(child_pid, 0)
diff --git a/sm.py b/sm.py
new file mode 100644 (file)
index 0000000..79403ca
--- /dev/null
+++ b/sm.py
@@ -0,0 +1,71 @@
+"""Sliver manager.
+
+The sliver manager has several functions.  It is responsible for
+creating, resource limiting, starting, stopping, and destroying
+slivers.  It provides an API for users to access these functions and
+also to make inter-sliver resource loans.  The sliver manager is also
+responsible for handling delegation accounts.
+"""
+
+try: from bwlimit import bwmin, bwmax
+except ImportError: bwmin, bwmax = 8, 1000*1000*1000
+import accounts
+import api
+import database
+import delegate
+import sliver_vs
+
+
+DEFAULT_ALLOCATION = {'enabled': 1, 'cpu_min': 0, 'cpu_share': 32, 'net_min': bwmin, 'net_max': bwmax, 'net2_min': bwmin, 'net2_max': bwmax, 'net_share': 1, 'disk_max': 5000000}
+
+start_requested = False  # set to True in order to request that all slivers be started
+
+
+@database.synchronized
+def GetSlivers_callback(data):
+    """This function has two purposes.  One, convert GetSlivers() data into a more convenient format.  Two, even if no updates are coming in, use the GetSlivers() heartbeat as a cue to scan for expired slivers."""
+    for d in data:
+        for sliver in d['slivers']:
+            rec = sliver.copy()
+            rec.setdefault('timestamp', d['timestamp'])
+            rec.setdefault('type', 'sliver.VServer')
+
+            # convert attributes field to a proper dict
+            attr_dict = {}
+            for attr in rec.pop('attributes'): attr_dict[attr['name']] = attr['value']
+
+            # squash keys
+            keys = rec.pop('keys')
+            rec.setdefault('keys', '\n'.join([key_struct['key'] for key_struct in keys]))
+
+            rec.setdefault('initscript', attr_dict.get('initscript', ''))
+            rec.setdefault('delegations', [])  # XXX - delegation not yet supported
+
+            # extract the implied rspec
+            rspec = {}
+            rec['rspec'] = rspec
+            for resname, default_amt in DEFAULT_ALLOCATION.iteritems():
+                try: amt = int(attr_dict[resname])
+                except (KeyError, ValueError): amt = default_amt
+                rspec[resname] = amt
+            database.db.deliver_record(rec)
+        database.db.set_min_timestamp(d['timestamp'])
+    database.db.sync()
+
+    # handle requested startup
+    global start_requested
+    if start_requested:
+        start_requested = False
+        cumulative_delay = 0
+        for name in database.db.iterkeys():
+            accounts.get(name).start(delay=cumulative_delay)
+            cumulative_delay += 3
+
+
+def start(options):
+    accounts.register_class(sliver_vs.Sliver_VS)
+    accounts.register_class(delegate.Delegate)
+    global start_requested
+    start_requested = options.startup
+    database.start()
+    api.start()
index 51527cc..83f0327 100644 (file)
--- a/tools.py
+++ b/tools.py
@@ -1,3 +1,5 @@
+"""A few things that didn't seem to fit anywhere else."""
+
 import cPickle
 import errno
 import os
 import cPickle
 import errno
 import os