##########
tags:
- find . '(' -name '*.py' -o -name '*.c' -o -name '*.spec' ')' | xargs etags
+ (find . '(' -name '*.py' -o -name '*.c' -o -name '*.spec' ')' ; ls initscripts/*) | xargs etags
.PHONY: tags
# Signed tickets
Requires: gnupg
-# Contact API server
-Requires: curl
-
# Uses function decorators
Requires: python >= 2.4
rsync -av initscripts/ $RPM_BUILD_ROOT/%{_initrddir}/
chmod 755 $RPM_BUILD_ROOT/%{_initrddir}/*
-install -D -m 644 logrotate/nm $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/nm
+install -d -m 755 $RPM_BUILD_ROOT/var/lib/nodemanager
+
+install -D -m 644 logrotate/nodemanager $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/nodemanager
+##########
%post
+# tmp - handle file renamings; old names are from 2.0-8
+renamings="
+/var/lib/misc/bwmon.dat@/var/lib/nodemanager/bwmon.dat
+/root/sliver_mgr_db.pickle@/var/lib/nodemanager/nodemanager.pickle
+/var/log/getslivers.txt@/var/lib/nodemanager/getslivers.txt
+/var/log/nm@/var/log/nodemanager
+/var/log/nm.daemon@/var/log/nodemanager.daemon
+/var/run/nm.pid@/var/run/nodemanager.pid
+/tmp/sliver_mgr.api@/tmp/nodemanager.api
+/etc/logrotate.d/nm@/etc/logrotate.d/nodemanager
+"
+for renaming in $renamings; do
+ old=$(echo $renaming | cut -d@ -f1)
+ new=$(echo $renaming | cut -d@ -f2)
+ newdir=$(dirname $new)
+ if [ -e "$old" -a ! -e "$new" ] ; then
+ mkdir -p $newdir
+ mv -f $old $new
+ fi
+done
+#
chkconfig --add conf_files
chkconfig conf_files on
chkconfig --add nm
service fuse-pl restart
fi
-
+##########
%preun
# 0 = erase, 1 = upgrade
if [ $1 -eq 0 ] ; then
%{_datadir}/NodeManager/
%{_bindir}/forward_api_calls
%{_initrddir}/
-%{_sysconfdir}/logrotate.d/nm
+%{_sysconfdir}/logrotate.d/nodemanager
+/var/lib/
%changelog
* Fri May 14 2010 Talip Baris Metin <Talip-Baris.Metin@sophia.inria.fr> - NodeManager-2.0-8
logger.log("api: Warning: admin slice prefix set to %s" %(PLC_SLICE_PREFIX), 2)
API_SERVER_PORT = 812
-UNIX_ADDR = '/tmp/sliver_mgr.api'
+UNIX_ADDR = '/tmp/nodemanager.api'
class APIRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
# overriding _dispatch to achieve this effect is officially deprecated,
ucred = self.request.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, sizeof_struct_ucred)
xid = struct.unpack('3i', ucred)[1]
caller_name = pwd.getpwuid(xid)[0]
- # Special case the genicw
+ # Special case : the sfa component manager
if caller_name == PLC_SLICE_PREFIX+"_sfacm":
try: result = method(*args)
except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
# Anyone can call these functions
- elif method_name not in ('Help', 'Ticket', 'GetXIDs', 'GetSSHKeys'):
+ elif method_name in ('Help', 'Ticket', 'GetXIDs', 'GetSSHKeys'):
+ try: result = method(*args)
+ except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
+ else: # Execute anonymous call.
# Authenticate the caller if not in the above fncts.
if method_name == "GetRecord":
target_name = caller_name
# Gather target slice's object.
target_rec = database.db.get(target_name)
- # only work on slivers or self. Sannity check.
+ # only work on slivers or self. Sanity check.
if not (target_rec and target_rec['type'].startswith('sliver.')):
raise xmlrpclib.Fault(102, \
'Invalid argument: the first argument must be a sliver name.')
except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
else:
raise xmlrpclib.Fault(108, '%s: Permission denied.' % caller_name)
- else: # Execute anonymous call.
- try: result = method(*args)
- except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
if result == None: result = 1
return result
# TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
# import this file in order to extract the documentation from each
-# exported function. A better approach will involve more extensive code
-# splitting, I think.
+# exported function.
+# A better approach will involve more extensive code splitting, I think.
try: import database
except: import logger as database
try: import sliver_vs
@export_to_api(0)
def Help():
"""Get a list of functions currently supported by the Node Manager API"""
- return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
+ names=api_method_dict.keys()
+ names.sort()
+ return ''.join(['**** ' + api_method_dict[name].__name__ + '\n' + api_method_dict[name].__doc__ + '\n'
+ for name in names])
@export_to_docbook(roles=['self'],
accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
actions are performed on a delegated slice (such as creation),
a controller slice must deliver a valid slice ticket to NM.
- This ticket is the value retured by PLC's GetSliceTicket() API call,
- """
+ This ticket is the value retured by PLC's GetSliceTicket() API call."""
try:
data = ticket_module.verify(ticket)
name = data['slivers'][0]['name']
returns=Parameter(int, '1 if successful'))
@export_to_api(1)
def AdminTicket(ticket):
- """Admin interface to create slivers based on ticket returned by GetSlivers().
- """
+ """Admin interface to create slivers based on ticket returned by GetSlivers()."""
try:
data, = xmlrpclib.loads(ticket)[0]
name = data['slivers'][0]['name']
rec = sliver_name
return rec.get('_loans', [])[:]
-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
- return type(obj)==list and False not in map(validate_loan, obj)
+def validate_loans(loans):
+ """Check that <obj> is a list of valid loan specifications."""
+ def validate_loan(loan):
+ return (type(loan)==list or type(loan)==tuple) and len(loan)==3 \
+ and type(loan[0])==str and type(loan[1])==str and loan[1] in database.LOANABLE_RESOURCES and type(loan[2])==int and loan[2]>=0
+ return type(loans)==list and False not in [validate_loan(load) for loan in loans]
+
@export_to_docbook(roles=['nm-controller', 'self'],
- accepts=[ Parameter(str, 'A sliver/slice name.'),
- [Mixed(Parameter(str, 'recipient slice name'),
- Parameter(str, 'resource name'),
- Parameter(int, 'resource amount'))] ],
- returns=Parameter(int, '1 if successful'))
+ accepts=[ Parameter(str, 'A sliver/slice name.'),
+ [Mixed(Parameter(str, 'recipient slice name'),
+ Parameter(str, 'resource name'),
+ Parameter(int, 'resource amount'))], ],
+ returns=Parameter(int, '1 if successful'))
@export_to_api(2)
def SetLoans(sliver_name, loans):
"""Overwrite the list of loans made by the specified sliver.
RSpec is handed out, but it will silently discard those loans that would
put it over capacity. This behavior may be replaced with error semantics
in the future. As well, there is currently no asynchronous notification
- of loss of resources.
- """
+ of loss of resources."""
rec = sliver_name
- if not validate_loans(loans): raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
+ if not validate_loans(loans):
+ raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
rec['_loans'] = loans
database.db.sync()
@export_to_docbook(roles=['nm-controller', 'self'],
- returns=Parameter(dict, 'Record dictionary'))
+ returns=Parameter(dict, 'Record dictionary'))
@export_to_api(0)
def GetRecord(sliver_name):
"""Return sliver record"""
# Set ENABLE to False to setup buckets, but not limit.
ENABLE = True
-datafile = "/var/lib/misc/bwmon.dat"
+datafile = "/var/lib/nodemanager/bwmon.dat"
try:
sys.path.append("/etc/planetlab")
-# $Id$
-# $URL$
-
-from subprocess import PIPE, Popen
-from select import select
-# raise xmplrpclib.ProtocolError
-import xmlrpclib
-import signal
import os
+import xmlrpclib
+import urllib
+import pycurl
+from cStringIO import StringIO
+
import logger
-class Sopen(Popen):
- def kill(self, signal = signal.SIGTERM):
- os.kill(self.pid, signal)
+# a pycurl-based replacement for the previous version that relied on forking curl
def retrieve(url, cacert=None, postdata=None, timeout=90):
-# options = ('/usr/bin/curl', '--fail', '--silent')
- options = ('/usr/bin/curl', '--fail', )
- if cacert: options += ('--cacert', cacert)
- if postdata: options += ('--data', '@-')
+ curl= pycurl.Curl()
+ curl.setopt(pycurl.URL,url)
+
+ # reproduce --fail from the previous version
+ curl.setopt(pycurl.FAILONERROR,1)
+ # don't want curl sending any signals
+ curl.setopt(pycurl.NOSIGNAL, 1)
+
+ # do not follow location when attempting to download a file
+ # curl.setopt(pycurl.FOLLOWLOCATION, 0)
+
+ # store result on the fly
+ buffer=StringIO()
+ curl.setopt(pycurl.WRITEFUNCTION,buffer.write)
+
+ # set timeout
if timeout:
- options += ('--max-time', str(timeout))
- options += ('--connect-timeout', str(timeout))
- p = Sopen(options + (url,), stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
- if postdata: p.stdin.write(postdata)
- p.stdin.close()
- sout, sin, serr = select([p.stdout,p.stderr],[],[], timeout)
- if len(sout) == 0 and len(sin) == 0 and len(serr) == 0:
- logger.verbose("curlwrapper: timed out after %s" % timeout)
- p.kill(signal.SIGKILL)
- data = p.stdout.read()
- err = p.stderr.read()
- rc = p.wait()
- if rc != 0:
- # when this triggers, the error sometimes doesn't get printed
- logger.log ("curlwrapper: retrieve, got stderr <%s>"%err)
- raise xmlrpclib.ProtocolError(url, rc, err, postdata)
- else:
- return data
+ curl.setopt(pycurl.CONNECTTIMEOUT, timeout)
+ curl.setopt(pycurl.TIMEOUT, timeout)
+
+ # set cacert
+ if cacert:
+ curl.setopt(pycurl.CAINFO, cacert)
+ curl.setopt(pycurl.SSL_VERIFYPEER, 2)
+ else:
+ curl.setopt(pycurl.SSL_VERIFYPEER, 0)
+
+ # set postdata
+ if postdata:
+ if isinstance(postdata,dict):
+ postfields = urllib.urlencode(postdata)
+ else:
+ postfields=postdata
+ curl.setopt(pycurl.POSTFIELDS, postfields)
+
+ # go
+ try:
+ curl.perform()
+
+ errcode = curl.getinfo(pycurl.HTTP_CODE)
+ curl.close()
+
+ # check the code, return 1 if successfull
+ if errcode == 60:
+ raise xmlrpclib.ProtocolError (url,errcode, "SSL certificate validation failed", postdata)
+ elif errcode != 200:
+ raise xmlrpclib.ProtocolError (url,errcode, "http error %d"%errcode, postdata)
+
+ except pycurl.error, err:
+ errno, errstr = err
+ raise xmlrpclib.ProtocolError(url, errno, "curl error %d: '%s'\n" %(errno,errstr),postdata )
+
+ return buffer.getvalue()
# 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_pct': 0, 'cpu_share': 1, 'net_min_rate': 0, 'net_max_rate': 8, 'net_i2_min_rate': 0, 'net_i2_max_rate': 8, 'net_share': 1}
+MINIMUM_ALLOCATION = {'cpu_pct': 0,
+ 'cpu_share': 1,
+ 'net_min_rate': 0,
+ 'net_max_rate': 8,
+ 'net_i2_min_rate': 0,
+ 'net_i2_max_rate': 8,
+ 'net_share': 1,
+ }
LOANABLE_RESOURCES = MINIMUM_ALLOCATION.keys()
-DB_FILE = '/root/sliver_mgr_db.pickle'
+DB_FILE = '/var/lib/nodemanager/nodemanager.pickle'
# database object and associated lock
self._min_timestamp = 0
def _compute_effective_rspecs(self):
- """Calculate the effects of loans and store the result in field _rspec. At the moment, we allow slivers to loan only those resources that they have received directly from PLC. In order to do the accounting, we store three different rspecs: field 'rspec', which is the resources given by PLC; field '_rspec', which is the actual amount of resources the sliver has after all loans; and variable resid_rspec, which is the amount of resources the sliver has after giving out loans but not receiving any."""
+ """Calculate the effects of loans and store the result in field _rspec.
+At the moment, we allow slivers to loan only those resources that they have received directly from PLC.
+In order to do the accounting, we store three different rspecs:
+ * field 'rspec', which is the resources given by PLC;
+ * field '_rspec', which is the actual amount of resources the sliver has after all loans;
+ * and variable resid_rspec, which is the amount of resources the sliver
+ has after giving out loans but not receiving any."""
slivers = {}
for name, rec in self.iteritems():
if 'rspec' in rec:
for rec in slivers.itervalues():
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] - MINIMUM_ALLOCATION[resname]:
- eff_rspec[resname] -= amt
- resid_rspec[resname] -= amt
- slivers[target]['_rspec'][resname] += amt
+ for target, resource_name, amount in rec.get('_loans', []):
+ if target in slivers and amount <= resid_rspec[resource_name] - MINIMUM_ALLOCATION[resource_name]:
+ eff_rspec[resource_name] -= amount
+ resid_rspec[resource_name] -= amount
+ slivers[target]['_rspec'][resource_name] += amount
def deliver_record(self, rec):
- """A record is simply a dictionary with 'name' and 'timestamp' keys. We keep some persistent private data in the records under keys that start with '_'; thus record updates should not displace such keys."""
+ """A record is simply a dictionary with 'name' and 'timestamp'
+keys. We keep some persistent private data in the records under keys
+that start with '_'; thus record updates should not displace such
+keys."""
if rec['timestamp'] < self._min_timestamp: return
name = rec['name']
old_rec = self.get(name)
old_rec.update(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."""
+ """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):
- """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."""
+ """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()
def start():
- """The database dumper daemon. When it starts up, it populates the database with the last dumped database. It proceeds to handle dump requests forever."""
+ """The database dumper daemon.
+When it starts up, it populates the database with the last dumped database.
+It proceeds to handle dump requests forever."""
def run():
global dump_requested
while True:
*/
static const int TIMEOUT_SECS = 120;
-const char *API_addr = "/tmp/sliver_mgr.api";
+const char *API_addr = "/tmp/nodemanager.api";
static const char *Header =
"POST / HTTP/1.0\r\n"
import subprocess
import select
-LOG_FILE = '/var/log/nm'
-LOG_SLIVERS = '/var/log/getslivers.txt'
+LOG_FILE = '/var/log/nodemanager'
+LOG_SLIVERS = '/var/lib/nodemanager/getslivers.txt'
# Thierry - trying to debug this for 4.2
# basically define 3 levels
-/var/log/nm {
+/var/log/nodemanager {
copytruncate
compress
daily
logger.log("nm: Checking Auth.")
while plc.check_authentication() != True:
try:
+# import pdb
+# pdb.set_trace()
plc.update_session()
logger.log("nm: Authentication Failure. Retrying")
- except:
- logger.log("nm: Retry Failed. Waiting")
+ except Exception,e:
+ logger.log("nm: Retry Failed. (%r); Waiting.."%e)
time.sleep(iperiod)
logger.log("nm: Authentication Succeeded!")
session => SessionAuth
To authenticate using the Boot Manager authentication method, or
- the new session-based method.
+ the new session-based method, respectively.
"""
def __init__(self, uri, cacert, auth, timeout = 90, **kwds):
logger.verbose ('sliver_vs: %s init'%name)
try:
logger.log("sliver_vs: %s: first chance..."%name)
- vserver.VServer.__init__(self, name,logfile='/var/log/nm')
+ vserver.VServer.__init__(self, name,logfile='/var/log/nodemanager')
except Exception, err:
if not isinstance(err, vserver.NoSuchVServer):
# Probably a bad vserver or vserver configuration file
self.destroy(name)
self.create(name, rec['vref'])
logger.log("sliver_vs: %s: second chance..."%name)
- vserver.VServer.__init__(self, name,logfile='/var/log/nm')
+ vserver.VServer.__init__(self, name,logfile='/var/log/nodemanager')
self.keys = ''
self.rspec = {}
GPG = '/usr/bin/gpg'
+def _popen_gpg(*args):
+ """Return a Popen object to GPG."""
+ return Popen((GPG, '--batch', '--no-tty') + args,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
def sign(data):
"""Return <data> signed with the default GPG key."""
msg = p.stdout.read()
p.stdout.close()
p.stderr.close()
- if p.wait(): return None # verification failed
+ if p.wait():
+ return None # verification failed
else:
data, = loads(msg)[0]
return data
-def _popen_gpg(*args):
- """Return a Popen object to GPG."""
- return Popen((GPG, '--batch', '--no-tty') + args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
import logger
-PID_FILE = '/var/run/nm.pid'
+PID_FILE = '/var/run/nodemanager.pid'
####################
def get_default_if():
devnull = os.open(os.devnull, os.O_RDWR)
os.dup2(devnull, 0)
# xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
- crashlog = os.open('/var/log/nm.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
+ crashlog = os.open('/var/log/nodemanager.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
os.dup2(crashlog, 1)
os.dup2(crashlog, 2)