reguire gnupg1 on f>=31; sense the system to use gpg1 when installed
[nodemanager.git] / slivermanager.py
index f69fa74..6db569a 100644 (file)
@@ -1,6 +1,3 @@
-# $Id$
-# $URL$
-
 """Sliver manager.
 
 The sliver manager has several functions.  It is responsible for
@@ -10,19 +7,40 @@ also to make inter-sliver resource loans.  The sliver manager is also
 responsible for handling delegation accounts.
 """
 
-import string,re
+import string
+import re
+import time
 
 import logger
 import api, api_calls
 import database
-import accounts
+import account
 import controller
-import sliver_vs
 
-try: from bwlimit import bwmin, bwmax
-except ImportError: bwmin, bwmax = 8, 1000*1000*1000
+try:
+    import sliver_lxc
+    implementation='lxc'
+    sliver_default_type='sliver.LXC'
+    sliver_class_to_register = sliver_lxc.Sliver_LXC
+    sliver_password_shell = sliver_lxc.Sliver_LXC.SHELL
+except:
+    try:
+        import sliver_vs
+        implementation='vs'
+        sliver_default_type='sliver.VServer'
+        sliver_class_to_register = sliver_vs.Sliver_VS
+        sliver_password_shell = sliver_vs.Sliver_VS.SHELL
+    except:
+        logger.log("Could not import either sliver_lxc or sliver_vs - bailing out")
+        exit(1)
+
+# just being safe
+try:
+    from plnode.bwlimit import bwmin, bwmax
+except:
+    bwmin, bwmax = 8, 1000*1000*1000
 
-priority=10
+priority = 10
 
 
 DEFAULT_ALLOCATION = {
@@ -30,6 +48,8 @@ DEFAULT_ALLOCATION = {
     # CPU parameters
     'cpu_pct': 0, # percent CPU reserved
     'cpu_share': 1, # proportional share
+    'cpu_cores': "0b", # reserved cpu cores <num_cores>[b]
+    'cpu_freezable': 0, # freeze processes if cpu_cores is 0
     # bandwidth parameters
     'net_min_rate': bwmin / 1000, # kbps
     'net_max_rate': bwmax / 1000, # kbps
@@ -57,6 +77,53 @@ DEFAULT_ALLOCATION = {
 
 start_requested = False  # set to True in order to request that all slivers be started
 
+# check leases and adjust the 'reservation_alive' field in slivers
+# this is not expected to be saved as it will change for the next round
+def adjustReservedSlivers (data):
+    """
+    On reservable nodes, tweak the 'reservation_alive' field to instruct cyclic loop
+    about what to do with slivers.
+    """
+    # only impacts reservable nodes
+    if 'reservation_policy' not in data: return
+    policy=data['reservation_policy'] 
+    if policy not in ['lease_or_idle', 'lease_or_shared']:
+        if policy is not None:
+            logger.log ("unexpected reservation_policy %(policy)s"%locals())
+        return
+
+    logger.log("slivermanager.adjustReservedSlivers")
+    now=int(time.time())
+    # scan leases that are expected to show in ascending order
+    active_lease=None
+    for lease in data['leases']:
+        if lease['t_from'] <= now and now <= lease['t_until']:
+            active_lease=lease
+            break
+
+    def is_system_sliver (sliver):
+        for d in sliver['attributes']:
+            if d['tagname']=='system' and d['value']:
+                return True
+        return False
+
+    # mark slivers as appropriate
+    for sliver in data['slivers']:
+        # system slivers must be kept alive
+        if is_system_sliver(sliver):
+            sliver['reservation_alive']=True
+            continue
+
+        # regular slivers
+        if not active_lease:
+            # with 'idle_or_shared', just let the field out, behave like a shared node
+            # otherwise, mark all slivers as being turned down
+            if policy == 'lease_or_idle':
+                sliver['reservation_alive']=False
+        else:
+            # there is an active lease, mark it alive and the other not
+            sliver['reservation_alive'] = sliver['name']==active_lease['name']
+
 @database.synchronized
 def GetSlivers(data, config = None, plc=None, fullupdate=True):
     """This function has two purposes.  One, convert GetSlivers() data
@@ -65,32 +132,36 @@ def GetSlivers(data, config = None, plc=None, fullupdate=True):
     slivers."""
 
     logger.verbose("slivermanager: Entering GetSlivers with fullupdate=%r"%fullupdate)
-    for key in data.keys():
+    for key in list(data.keys()):
         logger.verbose('slivermanager: GetSlivers key : ' + key)
 
     node_id = None
     try:
         f = open('/etc/planetlab/node_id')
-        try: node_id = int(f.read())
-        finally: f.close()
-    except: logger.log_exc("slivermanager: GetSlivers failed to read /etc/planetlab/node_id")
+        try:
+            node_id = int(f.read())
+        finally:
+            f.close()
+    except:
+        logger.log_exc("slivermanager: GetSlivers failed to read /etc/planetlab/node_id")
 
-    if data.has_key('node_id') and data['node_id'] != node_id: return
+    if 'node_id' in data and data['node_id'] != node_id: return
 
-    if data.has_key('networks'):
+    if 'networks' in data:
         for network in data['networks']:
             if network['is_primary'] and network['bwlimit'] is not None:
                 DEFAULT_ALLOCATION['net_max_rate'] = network['bwlimit'] / 1000
 
-    # Take initscripts (global) returned by API, make dict
+    # Take initscripts (global) returned by API, build a hash scriptname->code
+    iscripts_hash = {}
     if 'initscripts' not in data:
-        logger.log_missing_data("slivermanager.GetSlivers",'initscripts')
+        logger.log_missing_data("slivermanager.GetSlivers", 'initscripts')
         return
-    initscripts = {}
-    for is_rec in data['initscripts']:
-        logger.verbose("slivermanager: initscript: %s" % is_rec['name'])
-        initscripts[str(is_rec['name'])] = is_rec['script']
+    for initscript_rec in data['initscripts']:
+        logger.verbose("slivermanager: initscript: %s" % initscript_rec['name'])
+        iscripts_hash[str(initscript_rec['name'])] = initscript_rec['script']
 
+    adjustReservedSlivers (data)
     for sliver in data['slivers']:
         logger.verbose("slivermanager: %s: slivermanager.GetSlivers in slivers loop"%sliver['name'])
         rec = sliver.copy()
@@ -111,17 +182,22 @@ def GetSlivers(data, config = None, plc=None, fullupdate=True):
         if rec['instantiation'].lower() == 'nm-controller':
             rec.setdefault('type', attributes.get('type', 'controller.Controller'))
         else:
-            rec.setdefault('type', attributes.get('type', 'sliver.VServer'))
+            rec.setdefault('type', attributes.get('type', sliver_default_type))
 
         # set the vserver reference.  If none, set to default.
         rec.setdefault('vref', attributes.get('vref', 'default'))
 
-        # set initscripts.  first check if exists, if not, leave empty.
-        is_name = attributes.get('initscript')
-        if is_name is not None and is_name in initscripts:
-            rec['initscript'] = initscripts[is_name]
+        ### set initscripts; set empty rec['initscript'] if not
+        # if tag 'initscript_code' is set, that's what we use
+        iscode = attributes.get('initscript_code', '')
+        if iscode:
+            rec['initscript'] = iscode
         else:
-            rec['initscript'] = ''
+            isname = attributes.get('initscript')
+            if isname is not None and isname in iscripts_hash:
+                rec['initscript'] = iscripts_hash[isname]
+            else:
+                rec['initscript'] = ''
 
         # set delegations, if none, set empty
         rec.setdefault('delegations', attributes.get("delegations", []))
@@ -129,32 +205,56 @@ def GetSlivers(data, config = None, plc=None, fullupdate=True):
         # extract the implied rspec
         rspec = {}
         rec['rspec'] = rspec
-        for resname, default_amount in DEFAULT_ALLOCATION.iteritems():
+        for resname, default_amount in DEFAULT_ALLOCATION.items():
             try:
                 t = type(default_amount)
-                amt = t.__new__(t, attributes[resname])
-            except (KeyError, ValueError): amt = default_amount
-            rspec[resname] = amt
+                amount = t.__new__(t, attributes[resname])
+            except (KeyError, ValueError): amount = default_amount
+            rspec[resname] = amount
 
         # add in sysctl attributes into the rspec
-        for key in attributes.keys():
+        for key in list(attributes.keys()):
             if key.find("sysctl.") == 0:
                 rspec[key] = attributes[key]
 
+        # also export tags in rspec so they make it to the sliver_vs.start call
+        rspec['tags'] = attributes
+
         database.db.deliver_record(rec)
-    if fullupdate: database.db.set_min_timestamp(data['timestamp'])
+    if fullupdate:
+        database.db.set_min_timestamp(data['timestamp'])
     # slivers are created here.
     database.db.sync()
 
 def deliver_ticket(data):
     return GetSlivers(data, fullupdate=False)
 
-def start(options, config):
-    for resname, default_amount in sliver_vs.DEFAULT_ALLOCATION.iteritems():
-        DEFAULT_ALLOCATION[resname]=default_amount
+def start():
+    # No default allocation values for LXC yet, think if its necessary given
+    # that they are also default allocation values in this module
+    if implementation == 'vs':
+        for resname, default_amount in sliver_vs.DEFAULT_ALLOCATION.items():
+            DEFAULT_ALLOCATION[resname]=default_amount
 
-    accounts.register_class(sliver_vs.Sliver_VS)
-    accounts.register_class(controller.Controller)
+    account.register_class(sliver_class_to_register)
+    account.register_class(controller.Controller)
     database.start()
     api_calls.deliver_ticket = deliver_ticket
     api.start()
+
+### check if a sliver is running
+### a first step to a unified code for codemux
+def is_running (name):
+    if implementation=='vs':
+        import vserver
+        return vserver.VServer(name).is_running()
+    else:
+        import libvirt
+        running = False
+        try:
+            conn = libvirt.open('lxc://')
+            dom  = conn.lookupByName(name)
+            running = dom.info()[0] == libvirt.VIR_DOMAIN_RUNNING
+        finally:
+            conn.close()
+        return running