From: Sandrine Avakian Date: Thu, 21 Jun 2012 15:26:06 +0000 (+0200) Subject: Merge branch 'senslab2' of ssh://git.f-lab.fr/git/sfa into senslab2 X-Git-Tag: sfa-2.1-24~3^2~152 X-Git-Url: http://git.onelab.eu/?p=sfa.git;a=commitdiff_plain;h=1db1a879ffde8991aa95dd80142d555551655e88;hp=fa77e36e57e78fb3f589818782cbdf327414fee5 Merge branch 'senslab2' of ssh://git.f-lab.fr/git/sfa into senslab2 --- diff --git a/Makefile b/Makefile index 8446d4ab..6989044a 100644 --- a/Makefile +++ b/Makefile @@ -144,11 +144,11 @@ RSYNC_EXCLUDES := --exclude .svn --exclude .git --exclude '*~' --exclude TAGS $ RSYNC_COND_DRY_RUN := $(if $(findstring n,$(MAKEFLAGS)),--dry-run,) RSYNC := rsync -a -v $(RSYNC_COND_DRY_RUN) --no-owner $(RSYNC_EXCLUDES) -CLIENTS = $(shell ls sfa/clientbin/*.py) +CLIENTS = $(shell ls clientbin/*.py) BINS = ./config/sfa-config-tty ./config/gen-sfa-cm-config.py \ ./sfa/server/sfa-start.py \ - ./sfa/clientbin/sfaadmin.py \ + ./clientbin/sfaadmin.py \ $(CLIENTS) synclib: synccheck diff --git a/sfa/clientbin/Makefile b/clientbin/Makefile similarity index 100% rename from sfa/clientbin/Makefile rename to clientbin/Makefile diff --git a/sfa/clientbin/getNodes.py b/clientbin/getNodes.py similarity index 100% rename from sfa/clientbin/getNodes.py rename to clientbin/getNodes.py diff --git a/sfa/clientbin/getRecord.py b/clientbin/getRecord.py similarity index 100% rename from sfa/clientbin/getRecord.py rename to clientbin/getRecord.py diff --git a/sfa/clientbin/setRecord.py b/clientbin/setRecord.py similarity index 100% rename from sfa/clientbin/setRecord.py rename to clientbin/setRecord.py diff --git a/sfa/clientbin/sfaadmin.py b/clientbin/sfaadmin.py similarity index 100% rename from sfa/clientbin/sfaadmin.py rename to clientbin/sfaadmin.py diff --git a/sfa/clientbin/sfadump.py b/clientbin/sfadump.py similarity index 100% rename from sfa/clientbin/sfadump.py rename to clientbin/sfadump.py diff --git a/sfa/clientbin/sfascan.py b/clientbin/sfascan.py similarity index 100% rename from sfa/clientbin/sfascan.py rename to clientbin/sfascan.py diff --git a/sfa/clientbin/sfi.py b/clientbin/sfi.py similarity index 100% rename from sfa/clientbin/sfi.py rename to clientbin/sfi.py diff --git a/sfa/clientbin/sfiAddAttribute.py b/clientbin/sfiAddAttribute.py similarity index 100% rename from sfa/clientbin/sfiAddAttribute.py rename to clientbin/sfiAddAttribute.py diff --git a/sfa/clientbin/sfiAddLinks.py b/clientbin/sfiAddLinks.py similarity index 100% rename from sfa/clientbin/sfiAddLinks.py rename to clientbin/sfiAddLinks.py diff --git a/sfa/clientbin/sfiAddSliver.py b/clientbin/sfiAddSliver.py similarity index 100% rename from sfa/clientbin/sfiAddSliver.py rename to clientbin/sfiAddSliver.py diff --git a/sfa/clientbin/sfiDeleteAttribute.py b/clientbin/sfiDeleteAttribute.py similarity index 100% rename from sfa/clientbin/sfiDeleteAttribute.py rename to clientbin/sfiDeleteAttribute.py diff --git a/sfa/clientbin/sfiDeleteSliver.py b/clientbin/sfiDeleteSliver.py similarity index 100% rename from sfa/clientbin/sfiDeleteSliver.py rename to clientbin/sfiDeleteSliver.py diff --git a/sfa/clientbin/sfiListLinks.py b/clientbin/sfiListLinks.py similarity index 100% rename from sfa/clientbin/sfiListLinks.py rename to clientbin/sfiListLinks.py diff --git a/sfa/clientbin/sfiListNodes.py b/clientbin/sfiListNodes.py similarity index 100% rename from sfa/clientbin/sfiListNodes.py rename to clientbin/sfiListNodes.py diff --git a/sfa/clientbin/sfiListSlivers.py b/clientbin/sfiListSlivers.py similarity index 100% rename from sfa/clientbin/sfiListSlivers.py rename to clientbin/sfiListSlivers.py diff --git a/docs/README b/docs/README index 3dad1aaf..9e846de7 100644 --- a/docs/README +++ b/docs/README @@ -3,3 +3,6 @@ general design overview. API documentation is generated by using the pytondoc tool. See http://effbot.org/zone/pythondoc.htm to obtain the tool. + +==== +WARNING: as of June 2012, most of the documents here are likely mostly outdated.. diff --git a/setup.py b/setup.py index eafced43..96cb8cb1 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from glob import glob import shutil from distutils.core import setup -scripts = glob("sfa/clientbin/*.py") + \ +scripts = glob("clientbin/*.py") + \ [ 'config/sfa-config-tty', 'config/gen-sfa-cm-config.py', @@ -22,11 +22,9 @@ scripts = glob("sfa/clientbin/*.py") + \ packages = [ 'sfa', - 'sfa/openstack', 'sfa/trust', 'sfa/storage', 'sfa/util', - 'sfa/client', 'sfa/server', 'sfa/methods', 'sfa/generic', @@ -34,14 +32,21 @@ packages = [ 'sfa/importer', + 'sfa/senslab', - 'sfa/planetlab', + + + 'sfa/rspecs', 'sfa/rspecs/elements', 'sfa/rspecs/elements/versions', 'sfa/rspecs/versions', + 'sfa/client', + 'sfa/planetlab', + 'sfa/openstack', + 'sfa/federica', 'sfatables', 'sfatables/commands', 'sfatables/processors', diff --git a/sfa.spec b/sfa.spec index 4d77b2cc..4943fce3 100644 --- a/sfa.spec +++ b/sfa.spec @@ -1,6 +1,6 @@ %define name sfa %define version 2.1 -%define taglevel 7 +%define taglevel 11 %define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}} %global python_sitearch %( python -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)" ) @@ -62,11 +62,10 @@ Requires: python-xmlbuilder #Requires: python-uuid #%endif -%package plc -Summary: the SFA layer around MyPLC +%package flashpolicy +Summary: SFA support for flash clients Group: Applications/System Requires: sfa -Requires: python-psycopg2 %package client Summary: the SFA experimenter-side CLI @@ -74,8 +73,8 @@ Group: Applications/System Requires: sfa Requires: pyOpenSSL >= 0.7 -%package sfatables -Summary: sfatables policy tool for SFA +%package plc +Summary: the SFA layer around MyPLC Group: Applications/System Requires: sfa @@ -85,8 +84,13 @@ Group: Applications/System Requires: sfa Requires: pyOpenSSL >= 0.6 -%package flashpolicy -Summary: SFA support for flash clients +%package federica +Summary: the SFA layer around Federica +Group: Applications/System +Requires: sfa + +%package sfatables +Summary: sfatables policy tool for SFA Group: Applications/System Requires: sfa @@ -103,25 +107,28 @@ Requires: sfa %description This package provides the python libraries for the PlanetLab implementation of SFA -%description plc -This package implements the SFA interface which serves as a layer -between the existing PlanetLab interfaces and the SFA API. +%description flashpolicy +This package provides support for adobe flash client applications. %description client This package provides the client side of the SFA API, in particular sfi.py, together with other utilities. -%description sfatables -sfatables is a tool for defining access and admission control policies -in an SFA network, in much the same way as iptables is for ip -networks. This is the command line interface to manage sfatables +%description plc +This package implements the SFA interface which serves as a layer +between the existing PlanetLab interfaces and the SFA API. %description cm This package implements the SFA interface which serves as a layer between the existing PlanetLab NodeManager interfaces and the SFA API. -%description flashpolicy -This package provides support for adobe flash client applications. +%description federica +The SFA driver for FEDERICA. + +%description sfatables +sfatables is a tool for defining access and admission control policies +in an SFA network, in much the same way as iptables is for ip +networks. This is the command line interface to manage sfatables %description xmlbuilder This package contains the xmlbuilder python library, packaged for @@ -144,8 +151,17 @@ make VERSIONTAG="%{version}-%{taglevel}" SCMURL="%{SCMURL}" install DESTDIR="$RP rm -rf $RPM_BUILD_ROOT %files -# sfa and sfatables depend on each other. -%{python_sitelib}/sfa +%{python_sitelib}/sfa/__init__.py* +%{python_sitelib}/sfa/trust +%{python_sitelib}/sfa/storage +%{python_sitelib}/sfa/util +%{python_sitelib}/sfa/server +%{python_sitelib}/sfa/methods +%{python_sitelib}/sfa/generic +%{python_sitelib}/sfa/managers +%{python_sitelib}/sfa/importer +%{python_sitelib}/sfa/rspecs +%{python_sitelib}/sfa/client /etc/init.d/sfa %{_bindir}/sfa-start.py* %{_bindir}/sfaadmin.py* @@ -159,16 +175,9 @@ rm -rf $RPM_BUILD_ROOT /usr/share/sfa/examples /var/www/html/wsdl/*.wsdl -%files plc -%defattr(-,root,root) -/etc/sfa/pl.rng -/etc/sfa/credential.xsd -/etc/sfa/top.xsd -/etc/sfa/sig.xsd -/etc/sfa/xml.xsd -/etc/sfa/protogeni-rspec-common.xsd -/etc/sfa/topology -%{_bindir}/gen-sfa-cm-config.py* +%files flashpolicy +%{_bindir}/sfa_flashpolicy.py* +/etc/sfa/sfa_flashpolicy_config.xml %files client %config (noreplace) /etc/sfa/sfi_config @@ -180,19 +189,31 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/sfascan %{_bindir}/sfadump.py* -%files sfatables -/etc/sfatables/* -%{_bindir}/sfatables -%{python_sitelib}/sfatables +%files plc +%defattr(-,root,root) +%{python_sitelib}/sfa/planetlab +%{python_sitelib}/sfa/openstack +/etc/sfa/pl.rng +/etc/sfa/credential.xsd +/etc/sfa/top.xsd +/etc/sfa/sig.xsd +/etc/sfa/xml.xsd +/etc/sfa/protogeni-rspec-common.xsd +/etc/sfa/topology +%{_bindir}/gen-sfa-cm-config.py* %files cm /etc/init.d/sfa-cm %{_bindir}/sfa_component_setup.py* # cron jobs here -%files flashpolicy -%{_bindir}/sfa_flashpolicy.py* -/etc/sfa/sfa_flashpolicy_config.xml +%files federica +%{python_sitelib}/sfa/federica + +%files sfatables +/etc/sfatables/* +%{_bindir}/sfatables +%{python_sitelib}/sfatables %files xmlbuilder %{python_sitelib}/xmlbuilder @@ -227,6 +248,25 @@ fi [ "$1" -ge "1" ] && service sfa-cm restart || : %changelog +* Thu Jun 07 2012 Thierry Parmentelat - sfa-2.1-11 +- review packaging - site-packages/planetlab now come with sfa-plc +- new package sfa-federica +- clientbin moved one step upwards + +* Wed Jun 6 2012 Tony Mack - sfa-2.1-10 +- fix bug in sfi update() + +* Sun Jun 03 2012 Thierry Parmentelat - sfa-2.1-9 +- fix broken sfa.util.xrn class for lowercase + +* Sat Jun 02 2012 Thierry Parmentelat - sfa-2.1-8 +- new 'void' generic_flavour for running in registry-only mode +- first shot at refactoring importers - probably needs more work +- openstack: various enhancements +- sfi interface to registry not based on xml files anymore +- sfi show sorts result on record key +- bugfix in sfa update on users with a pl-backed registry + * Mon May 14 2012 Thierry Parmentelat - sfa-2.1-7 - renamed sfa/plc into sfa/planetlab - plxrn moved in sfa/planetlab as well diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py index 1b0f9b75..43e5b985 100644 --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -1,7 +1,6 @@ # # sfi.py - basic SFA command-line client -# the actual binary in sfa/clientbin essentially runs main() -# this module is used in sfascan +# this module is also used in sfascan # import sys @@ -9,6 +8,7 @@ sys.path.append('.') import os, os.path import socket +import re import datetime import codecs import pickle @@ -23,8 +23,9 @@ from sfa.trust.gid import GID from sfa.trust.credential import Credential from sfa.trust.sfaticket import SfaTicket +from sfa.util.faults import SfaInvalidArgument from sfa.util.sfalogging import sfi_logger -from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn +from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn from sfa.util.config import Config from sfa.util.version import version_core from sfa.util.cache import Cache @@ -43,6 +44,30 @@ from sfa.client.return_value import ReturnValue CM_PORT=12346 # utility methods here +def optparse_listvalue_callback(option, option_string, value, parser): + setattr(parser.values, option.dest, value.split(',')) + +# a code fragment that could be helpful for argparse which unfortunately is +# available with 2.7 only, so this feels like too strong a requirement for the client side +#class ExtraArgAction (argparse.Action): +# def __call__ (self, parser, namespace, values, option_string=None): +# would need a try/except of course +# (k,v)=values.split('=') +# d=getattr(namespace,self.dest) +# d[k]=v +##### +#parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction, +# help="set extra flags, testbed dependent, e.g. --extra enabled=true") + +def optparse_dictvalue_callback (option, option_string, value, parser): + try: + (k,v)=value.split('=',1) + d=getattr(parser.values, option.dest) + d[k]=v + except: + parser.print_help() + sys.exit(1) + # display methods def display_rspec(rspec, format='rspec'): if format in ['dns']: @@ -72,7 +97,7 @@ def display_records(recordList, dump=False): def display_record(record, dump=False): if dump: - record.dump() + record.dump(sort=True) else: info = record.getdict() print "%s (%s)" % (info['hrn'], info['type']) @@ -154,8 +179,44 @@ def save_record_to_file(filename, record_dict): f.close() return +# minimally check a key argument +def check_ssh_key (key): + good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$' + return re.match(good_ssh_key, key, re.IGNORECASE) # load methods +def load_record_from_opts(options): + record_dict = {} + if hasattr(options, 'xrn') and options.xrn: + if hasattr(options, 'type') and options.type: + xrn = Xrn(options.xrn, options.type) + else: + xrn = Xrn(options.xrn) + record_dict['urn'] = xrn.get_urn() + record_dict['hrn'] = xrn.get_hrn() + record_dict['type'] = xrn.get_type() + if hasattr(options, 'key') and options.key: + try: + pubkey = open(options.key, 'r').read() + except IOError: + pubkey = options.key + if not check_ssh_key (pubkey): + raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format") + record_dict['keys'] = [pubkey] + if hasattr(options, 'slices') and options.slices: + record_dict['slices'] = options.slices + if hasattr(options, 'researchers') and options.researchers: + record_dict['researcher'] = options.researchers + if hasattr(options, 'email') and options.email: + record_dict['email'] = options.email + if hasattr(options, 'pis') and options.pis: + record_dict['pi'] = options.pis + + # handle extra settings + record_dict.update(options.extras) + + return Record(dict=record_dict) + def load_record_from_file(filename): f=codecs.open(filename, encoding="utf-8", mode="r") xml_string = f.read() @@ -257,6 +318,30 @@ class Sfi: parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \ % (command, self.available_dict[command])) + if command in ("add", "update"): + parser.add_option('-x', '--xrn', dest='xrn', metavar='', help='object hrn/urn (mandatory)') + parser.add_option('-t', '--type', dest='type', metavar='', help='object type', default=None) + parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)") +# use --extra instead +# parser.add_option('-u', '--url', dest='url', metavar='', default=None, help="URL, useful for slices") +# parser.add_option('-d', '--description', dest='description', metavar='', +# help='Description, useful for slices', default=None) + parser.add_option('-k', '--key', dest='key', metavar='', help='public key string or file', + default=None) + parser.add_option('-s', '--slices', dest='slices', metavar='', help='slice xrns', + default='', type="str", action='callback', callback=optparse_listvalue_callback) + parser.add_option('-r', '--researchers', dest='researchers', metavar='', + help='slice researchers', default='', type="str", action='callback', + callback=optparse_listvalue_callback) + parser.add_option('-p', '--pis', dest='pis', metavar='', help='Principal Investigators/Project Managers', + default='', type="str", action='callback', callback=optparse_listvalue_callback) +# use --extra instead +# parser.add_option('-f', '--firstname', dest='firstname', metavar='', help='user first name') +# parser.add_option('-l', '--lastname', dest='lastname', metavar='', help='user last name') + parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="", + action="callback", callback=optparse_dictvalue_callback, nargs=1, + help="set extra/testbed-dependent flags, e.g. --extra enabled=true") + # user specifies remote aggregate/sm/component if command in ("resources", "slices", "create", "delete", "start", "stop", "restart", "shutdown", "get_ticket", "renew", "status"): @@ -284,8 +369,12 @@ class Sfi: help="display format ([xml]|dns|ip)", default="xml", choices=("xml", "dns", "ip")) #panos: a new option to define the type of information about resources a user is interested in - parser.add_option("-i", "--info", dest="info", + parser.add_option("-i", "--info", dest="info", help="optional component information", default=None) + # a new option to retreive or not reservation-oriented RSpecs (leases) + parser.add_option("-l", "--list_leases", dest="list_leases", type="choice", + help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )", + choices=("all", "resources", "leases"), default="resources") # 'create' does return the new rspec, makes sense to save that too @@ -737,7 +826,7 @@ or version information about sfi itself self.logger.error("No record of type %s"% options.type) records = [ Record(dict=record_dict) for record_dict in record_dicts ] for record in records: - if (options.format == "text"): record.dump() + if (options.format == "text"): record.dump(sort=True) else: print record.save_as_xml() if options.file: save_records_to_file(options.file, record_dicts, options.fileformat) @@ -746,29 +835,50 @@ or version information about sfi itself def add(self, options, args): "add record into registry from xml file (Register)" auth_cred = self.my_authority_credential_string() - if len(args)!=1: + record_dict = {} + if len(args) > 0: + record_filepath = args[0] + rec_file = self.get_record_file(record_filepath) + record_dict.update(load_record_from_file(rec_file).todict()) + if options: + record_dict.update(load_record_from_opts(options).todict()) + # we should have a type by now + if 'type' not in record_dict : self.print_help() sys.exit(1) - record_filepath = args[0] - rec_file = self.get_record_file(record_filepath) - record = load_record_from_file(rec_file).todict() - return self.registry().Register(record, auth_cred) + # this is still planetlab dependent.. as plc will whine without that + # also, it's only for adding + if record_dict['type'] == 'user': + if not 'first_name' in record_dict: + record_dict['first_name'] = record_dict['hrn'] + if 'last_name' not in record_dict: + record_dict['last_name'] = record_dict['hrn'] + return self.registry().Register(record_dict, auth_cred) def update(self, options, args): "update record into registry from xml file (Update)" - if len(args)!=1: + record_dict = {} + if len(args) > 0: + record_filepath = args[0] + rec_file = self.get_record_file(record_filepath) + record_dict.update(load_record_from_file(rec_file).todict()) + if options: + record_dict.update(load_record_from_opts(options).todict()) + # at the very least we need 'type' here + if 'type' not in record_dict: self.print_help() sys.exit(1) - rec_file = self.get_record_file(args[0]) - record = load_record_from_file(rec_file) - if record.type == "user": - if record.hrn == self.user: + + # don't translate into an object, as this would possibly distort + # user-provided data; e.g. add an 'email' field to Users + if record_dict['type'] == "user": + if record_dict['hrn'] == self.user: cred = self.my_credential_string else: cred = self.my_authority_credential_string() - elif record.type in ["slice"]: + elif record_dict['type'] in ["slice"]: try: - cred = self.slice_credential_string(record.hrn) + cred = self.slice_credential_string(record_dict['hrn']) except ServerException, e: # XXX smbaker -- once we have better error return codes, update this # to do something better than a string compare @@ -776,13 +886,12 @@ or version information about sfi itself cred = self.my_authority_credential_string() else: raise - elif record.type in ["authority"]: + elif record_dict['type'] in ["authority"]: cred = self.my_authority_credential_string() - elif record.type == 'node': + elif record_dict['type'] == 'node': cred = self.my_authority_credential_string() else: - raise "unknown record type" + record.type - record_dict = record.todict() + raise "unknown record type" + record_dict['type'] return self.registry().Update(record_dict, cred) def remove(self, options, args): @@ -849,6 +958,8 @@ or with an slice hrn, shows currently provisioned resources api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice') if options.info: api_options['info'] = options.info + if options.list_leases: + api_options['list_leases'] = options.list_leases if options.current: if options.current == True: api_options['cached'] = False diff --git a/sfa/importer/plimporter.py b/sfa/importer/plimporter.py index ab37ac9f..1e04e54c 100644 --- a/sfa/importer/plimporter.py +++ b/sfa/importer/plimporter.py @@ -82,17 +82,20 @@ class PlImporter: def locate_by_type_pointer (self, type, pointer): return self.records_by_type_pointer.get ( (type, pointer), None) - # convenience : try to locate first based on type+pointer - # if so, the record was created already even if e.g. its hrn has changed meanwhile - # otherwise we try by type+hrn (is this truly useful ?) - def locate (self, type, hrn=None, pointer=-1): - if pointer!=-1: - attempt = self.locate_by_type_pointer (type, pointer) - if attempt : return attempt - if hrn is not None: - attempt = self.locate_by_type_hrn (type, hrn,) - if attempt : return attempt - return None + # a convenience/helper function to see if a record is already known + # a former, broken, attempt (in 2.1-9) had been made + # to try and use 'pointer' as a first, most significant attempt + # the idea being to preserve stuff as much as possible, and thus + # to avoid creating a new gid in the case of a simple hrn rename + # however this of course doesn't work as the gid depends on the hrn... + #def locate (self, type, hrn=None, pointer=-1): + # if pointer!=-1: + # attempt = self.locate_by_type_pointer (type, pointer) + # if attempt : return attempt + # if hrn is not None: + # attempt = self.locate_by_type_hrn (type, hrn,) + # if attempt : return attempt + # return None # this makes the run method a bit abtruse - out of the way def create_special_vini_record (self, interface_hrn): @@ -185,7 +188,7 @@ class PlImporter: site_hrn = _get_site_hrn(interface_hrn, site) # import if hrn is not in list of existing hrns or if the hrn exists # but its not a site record - site_record=self.locate ('authority', site_hrn, site['site_id']) + site_record=self.locate_by_type_hrn ('authority', site_hrn) if not site_record: try: urn = hrn_to_urn(site_hrn, 'authority') @@ -219,18 +222,18 @@ class PlImporter: continue site_auth = get_authority(site_hrn) site_name = site['login_base'] - hrn = hostname_to_hrn(site_auth, site_name, node['hostname']) + node_hrn = hostname_to_hrn(site_auth, site_name, node['hostname']) # xxx this sounds suspicious - if len(hrn) > 64: hrn = hrn[:64] - node_record = self.locate ( 'node', hrn , node['node_id'] ) + if len(node_hrn) > 64: node_hrn = node_hrn[:64] + node_record = self.locate_by_type_hrn ( 'node', node_hrn ) if not node_record: try: pkey = Keypair(create=True) - urn = hrn_to_urn(hrn, 'node') + urn = hrn_to_urn(node_hrn, 'node') node_gid = self.auth_hierarchy.create_gid(urn, create_uuid(), pkey) - node_record = RegNode (hrn=hrn, gid=node_gid, + node_record = RegNode (hrn=node_hrn, gid=node_gid, pointer =node['node_id'], - authority=get_authority(hrn)) + authority=get_authority(node_hrn)) node_record.just_created() dbsession.add(node_record) dbsession.commit() @@ -255,7 +258,7 @@ class PlImporter: if len(person_hrn) > 64: person_hrn = person_hrn[:64] person_urn = hrn_to_urn(person_hrn, 'user') - user_record = self.locate ( 'user', person_hrn, person['person_id']) + user_record = self.locate_by_type_hrn ( 'user', person_hrn) # return a tuple pubkey (a plc key object) and pkey (a Keypair object) def init_person_key (person, plc_keys): @@ -337,7 +340,7 @@ class PlImporter: except: self.logger.warning ("PlImporter: cannot locate slice_id %s - ignored"%slice_id) slice_hrn = slicename_to_hrn(interface_hrn, slice['name']) - slice_record = self.locate ('slice', slice_hrn, slice['slice_id']) + slice_record = self.locate_by_type_hrn ('slice', slice_hrn) if not slice_record: try: pkey = Keypair(create=True) diff --git a/sfa/managers/registry_manager.py b/sfa/managers/registry_manager.py index 03a90439..a94a039c 100644 --- a/sfa/managers/registry_manager.py +++ b/sfa/managers/registry_manager.py @@ -390,11 +390,11 @@ class RegistryManager: dbsession.commit() # update the PLC information that was specified with the record - # xxx oddly enough, without this statement, record.__dict__ as received by - # the driver seems to be off + # xxx oddly enough, without this useless statement, + # record.__dict__ as received by the driver seems to be off # anyway the driver should receive an object # (and then extract __dict__ itself if needed) - print "before driver.update, record=%s"%record + print "DO NOT REMOVE ME before driver.update, record=%s"%record if not self.driver.update (record.__dict__, new_record.__dict__, hrn, new_key): logger.warning("driver.update failed") diff --git a/sfa/openstack/nova_driver.py b/sfa/openstack/nova_driver.py index e4b95b07..d42c7143 100644 --- a/sfa/openstack/nova_driver.py +++ b/sfa/openstack/nova_driver.py @@ -9,6 +9,7 @@ from sfa.util.sfalogging import logger from sfa.util.defaultdict import defaultdict from sfa.util.sfatime import utcparse, datetime_to_string, datetime_to_epoch from sfa.util.xrn import Xrn, hrn_to_urn, get_leaf, urn_to_sliver_id +from sfa.util.plxrn import PlXrn, hrn_to_pl_slicename from sfa.util.cache import Cache from sfa.trust.credential import Credential # used to be used in get_ticket @@ -73,7 +74,7 @@ class NovaDriver (Driver): if type == 'slice': # add slice description, name, researchers, PI - name = Xrn(hrn).get_leaf() + name = hrn_to_pl_slicename(hrn) researchers = sfa_record.get('researchers', []) pis = sfa_record.get('pis', []) project_manager = None @@ -122,7 +123,7 @@ class NovaDriver (Driver): elif type == "slice": # can update project manager and description - name = Xrn(hrn).get_leaf() + name = hrn_to_pl_slicename(hrn) researchers = sfa_record.get('researchers', []) pis = sfa_record.get('pis', []) project_manager = None @@ -143,11 +144,12 @@ class NovaDriver (Driver): ########## def remove (self, sfa_record): type=sfa_record['type'] - name = Xrn(sfa_record['hrn']).get_leaf() if type == 'user': + name = Xrn(sfa_record['hrn']).get_leaf() if self.shell.auth_manager.get_user(name): self.shell.auth_manager.delete_user(name) elif type == 'slice': + name = hrn_to_pl_slicename(sfa_record['hrn']) if self.shell.auth_manager.get_project(name): self.shell.auth_manager.delete_project(name) return True @@ -163,9 +165,9 @@ class NovaDriver (Driver): records = [records] for record in records: - name = Xrn(record['hrn']).get_leaf() os_record = None if record['type'] == 'user': + name = Xrn(record['hrn']).get_leaf() os_record = self.shell.auth_manager.get_user(name) projects = self.shell.db.project_get_by_user(name) record['slices'] = [self.hrn + "." + proj.name for \ @@ -173,7 +175,8 @@ class NovaDriver (Driver): record['roles'] = self.shell.db.user_get_roles(name) keys = self.shell.db.key_pair_get_all_by_user(name) record['keys'] = [key.public_key for key in keys] - elif record['type'] == 'slice': + elif record['type'] == 'slice': + name = hrn_to_pl_slicename(record['hrn']) os_record = self.shell.auth_manager.get_project(name) record['description'] = os_record.description record['PI'] = [self.hrn + "." + os_record.project_manager.name] @@ -289,7 +292,7 @@ class NovaDriver (Driver): def sliver_status (self, slice_urn, slice_hrn): # find out where this slice is currently running - project_name = Xrn(slice_urn).get_leaf() + project_name = hrn_to_pl_slicename(slice_hrn) project = self.shell.auth_manager.get_project(project_name) instances = self.shell.db.instance_get_all_by_project(project_name) if len(instances) == 0: @@ -328,7 +331,7 @@ class NovaDriver (Driver): def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, users, options): - project_name = get_leaf(slice_hrn) + project_name = hrn_to_pl_slicename(slice_hrn) aggregate = OSAggregate(self) # parse rspec rspec = RSpec(rspec_string) @@ -358,14 +361,13 @@ class NovaDriver (Driver): def delete_sliver (self, slice_urn, slice_hrn, creds, options): # we need to do this using the context of one of the slice users - project_name = Xrn(slice_urn).get_leaf() + project_name = hrn_to_pl_slicename(slice_hrn) self.euca_shell.init_context(project_name) - name = OSXrn(xrn=slice_urn).name aggregate = OSAggregate(self) - return aggregate.delete_instances(name) + return aggregate.delete_instances(project_name) def update_sliver(self, slice_urn, slice_hrn, rspec, creds, options): - name = OSXrn(xrn=slice_urn).name + name = hrn_to_pl_slicename(slice_hrn) aggregate = OSAggregate(self) return aggregate.update_instances(name) diff --git a/sfa/openstack/osaggregate.py b/sfa/openstack/osaggregate.py index 3de56271..def366ff 100644 --- a/sfa/openstack/osaggregate.py +++ b/sfa/openstack/osaggregate.py @@ -1,4 +1,6 @@ + import os +import socket import base64 import string import random @@ -13,7 +15,9 @@ from sfa.rspecs.elements.sliver import Sliver from sfa.rspecs.elements.login import Login from sfa.rspecs.elements.disk_image import DiskImage from sfa.rspecs.elements.services import Services +from sfa.rspecs.elements.interface import Interface from sfa.util.xrn import Xrn +from sfa.util.plxrn import PlXrn, hrn_to_pl_slicename from sfa.util.osxrn import OSXrn from sfa.rspecs.version_manager import VersionManager from sfa.openstack.image import ImageManager @@ -78,32 +82,62 @@ class OSAggregate: rspec.version.add_nodes(nodes) return rspec.toxml() + def get_availability_zones(self): + zones = self.driver.shell.db.zone_get_all() + if not zones: + zones = ['cloud'] + else: + zones = [zone.name for zone in zones] + def get_slice_nodes(self, slice_xrn): image_manager = ImageManager(self.driver) - name = OSXrn(xrn = slice_xrn).name + + zones = self.get_availability_zones() + name = hrn_to_pl_slicename(slice_xrn) instances = self.driver.shell.db.instance_get_all_by_project(name) rspec_nodes = [] for instance in instances: rspec_node = Node() - xrn = OSXrn(instance.hostname, 'node') - rspec_node['component_id'] = xrn.urn - rspec_node['component_name'] = xrn.name + interfaces = [] + for fixed_ip in instance.fixed_ips: + if_xrn = PlXrn(auth=self.driver.hrn, + interface='node%s:eth0' % (instance.hostname)) + interface = Interface({'component_id': if_xrn.urn}) + interface['ips'] = [{'address': fixed_ip['address'], + 'netmask': fixed_ip['network'].netmask, + 'type': 'ipv4'}] + interfaces.append(interface) + if instance.availability_zone: + node_xrn = OSXrn(instance.availability_zone, 'node') + else: + node_xrn = OSXrn('cloud', 'node') + + rspec_node['component_id'] = node_xrn.urn + rspec_node['component_name'] = node_xrn.name rspec_node['component_manager_id'] = Xrn(self.driver.hrn, 'authority+cm').get_urn() sliver = instance_to_sliver(instance) disk_image = image_manager.get_disk_image(instance.image_ref) - sliver['disk_images'] = [disk_image.to_rspec_object()] + sliver['disk_image'] = [disk_image.to_rspec_object()] rspec_node['slivers'] = [sliver] + rspec_node['interfaces'] = interfaces + # slivers always provide the ssh service + hostname = None + for interface in interfaces: + if 'ips' in interface and interface['ips'] and \ + isinstance(interface['ips'], list): + if interface['ips'][0].get('address'): + hostname = interface['ips'][0].get('address') + break + login = Login({'authentication': 'ssh-keys', + 'hostname': hostname, + 'port':'22', 'username': 'root'}) + service = Services({'login': login}) + rspec_node['services'] = [service] rspec_nodes.append(rspec_node) return rspec_nodes def get_aggregate_nodes(self): - - zones = self.driver.shell.db.zone_get_all() - if not zones: - zones = ['cloud'] - else: - zones = [zone.name for zone in zones] - + zones = self.get_availability_zones() # available sliver/instance/vm types instances = self.driver.shell.db.instance_type_get_all().values() # available images @@ -124,7 +158,7 @@ class OSAggregate: slivers = [] for instance in instances: sliver = instance_to_sliver(instance) - sliver['disk_images'] = disk_image_objects + sliver['disk_image'] = disk_image_objects slivers.append(sliver) rspec_node['slivers'] = slivers @@ -169,7 +203,7 @@ class OSAggregate: key = {} key['user_id'] = username key['name'] = username - key['public'] = public_key + key['public_key'] = public_key self.driver.shell.db.key_pair_create(key) # remove old keys @@ -179,15 +213,25 @@ class OSAggregate: self.driver.shell.db.key_pair_destroy(username, key.name) - def create_security_group(self, group_name, fw_rules=[]): - security_group = SecurityGroup(self.driver) - security_group.create_security_group(group_name) - for rule in fw_rules: - security_group.add_rule_to_group(group_name, + def create_security_group(self, slicename, fw_rules=[]): + # use default group by default + group_name = 'default' + if isinstance(fw_rules, list) and fw_rules: + # Each sliver get's its own security group. + # Keep security group names unique by appending some random + # characters on end. + random_name = "".join([random.choice(string.letters+string.digits) + for i in xrange(6)]) + group_name = slicename + random_name + security_group = SecurityGroup(self.driver) + security_group.create_security_group(group_name) + for rule in fw_rules: + security_group.add_rule_to_group(group_name, protocol = rule.get('protocol'), cidr_ip = rule.get('cidr_ip'), port_range = rule.get('port_range'), icmp_type_code = rule.get('icmp_type_code')) + return group_name def add_rule_to_security_group(self, group_name, **kwds): security_group = SecurityGroup(self.driver) @@ -248,17 +292,11 @@ class OSAggregate: # iterate over sliver/instance types for instance_type in instance_types: fw_rules = instance_type.get('fw_rules', []) - # Each sliver get's its own security group. - # Keep security group names unique by appending some random - # characters on end. - random_name = "".join([random.choice(string.letters+string.digits) - for i in xrange(6)]) - group_name = slicename + random_name - self.create_security_group(group_name, fw_rules) + group_name = self.create_security_group(slicename, fw_rules) ami_id = default_image_id aki_id = default_aki_id ari_id = default_ari_id - req_image = instance_type.get('disk_images') + req_image = instance_type.get('disk_image') if req_image and isinstance(req_image, list): req_image_name = req_image[0]['name'] disk_image = image_manager.get_disk_image(name=req_image_name) diff --git a/sfa/openstack/security_group.py b/sfa/openstack/security_group.py index 33c62743..4af0e581 100644 --- a/sfa/openstack/security_group.py +++ b/sfa/openstack/security_group.py @@ -24,14 +24,12 @@ class SecurityGroup: def _validate_port_range(self, port_range): from_port = to_port = None if isinstance(port_range, str): - ports = port_range.split('-') + ports = port_range.split(':') if len(ports) > 1: from_port = int(ports[0]) to_port = int(ports[1]) else: from_port = to_port = int(ports[0]) - else: - from_port = to_port = None return (from_port, to_port) def _validate_icmp_type_code(self, icmp_type_code): @@ -53,7 +51,7 @@ class SecurityGroup: from_port, to_port = self._validate_port_range(port_range) icmp_type = self._validate_icmp_type_code(icmp_type_code) - if icmp_type: + if icmp_type and icmp_type[0] and icmp_type[1]: from_port, to_port = icmp_type[0], icmp_type[1] if group_name: diff --git a/sfa/planetlab/plaggregate.py b/sfa/planetlab/plaggregate.py index ccf68527..e2ff3f9e 100644 --- a/sfa/planetlab/plaggregate.py +++ b/sfa/planetlab/plaggregate.py @@ -13,12 +13,15 @@ from sfa.rspecs.elements.location import Location from sfa.rspecs.elements.interface import Interface from sfa.rspecs.elements.services import Services from sfa.rspecs.elements.pltag import PLTag +from sfa.rspecs.elements.lease import Lease +from sfa.rspecs.elements.granularity import Granularity from sfa.rspecs.version_manager import VersionManager -from sfa.planetlab.plxrn import PlXrn, hostname_to_urn, hrn_to_pl_slicename +from sfa.planetlab.plxrn import PlXrn, hostname_to_urn, hrn_to_pl_slicename, slicename_to_hrn from sfa.planetlab.vlink import get_tc_rate from sfa.planetlab.topology import Topology +import time class PlAggregate: @@ -154,6 +157,9 @@ class PlAggregate: filter.update({'peer_id': None}) nodes = self.driver.shell.GetNodes(filter) + + # get the granularity in second for the reservation system + grain = self.driver.shell.GetLeaseGranularity() site_ids = [] interface_ids = [] @@ -205,6 +211,10 @@ class PlAggregate: if site['longitude'] and site['latitude']: location = Location({'longitude': site['longitude'], 'latitude': site['latitude'], 'country': 'unknown'}) rspec_node['location'] = location + # Granularity + granularity = Granularity({'grain': grain}) + rspec_node['granularity'] = granularity + rspec_node['interfaces'] = [] if_count=0 for if_id in node['interface_ids']: @@ -234,7 +244,44 @@ class PlAggregate: rspec_nodes.append(rspec_node) return (rspec_nodes, links) + + def get_leases(self, slice=None, options={}): + now = int(time.time()) + filter={} + filter.update({'clip':now}) + if slice: + filter.update({'name':slice['name']}) + return_fields = ['lease_id', 'hostname', 'site_id', 'name', 't_from', 't_until'] + leases = self.driver.shell.GetLeases(filter) + + site_ids = [] + for lease in leases: + site_ids.append(lease['site_id']) + + # get sites + sites_dict = self.get_sites({'site_id': site_ids}) + + rspec_leases = [] + for lease in leases: + + rspec_lease = Lease() + + # xxx how to retrieve site['login_base'] + site_id=lease['site_id'] + site=sites_dict[site_id] + + rspec_lease['lease_id'] = lease['lease_id'] + rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn, site['login_base'], lease['hostname']) + slice_hrn = slicename_to_hrn(self.driver.hrn, lease['name']) + slice_urn = hrn_to_urn(slice_hrn, 'slice') + rspec_lease['slice_id'] = slice_urn + rspec_lease['t_from'] = lease['t_from'] + rspec_lease['t_until'] = lease['t_until'] + rspec_leases.append(rspec_lease) + return rspec_leases + + def get_rspec(self, slice_xrn=None, version = None, options={}): version_manager = VersionManager() @@ -249,17 +296,22 @@ class PlAggregate: if slice and 'expires' in slice: rspec.xml.set('expires', datetime_to_string(utcparse(slice['expires']))) - nodes, links = self.get_nodes_and_links(slice_xrn, slice, slivers) - rspec.version.add_nodes(nodes) - rspec.version.add_links(links) + if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'leases': + nodes, links = self.get_nodes_and_links(slice_xrn, slice, slivers) + rspec.version.add_nodes(nodes) + rspec.version.add_links(links) + # add sliver defaults + default_sliver = slivers.get(None, []) + if default_sliver: + default_sliver_attribs = default_sliver.get('tags', []) + for attrib in default_sliver_attribs: + logger.info(attrib) + rspec.version.add_default_sliver_attribute(attrib['tagname'], attrib['value']) - # add sliver defaults - default_sliver = slivers.get(None, []) - if default_sliver: - default_sliver_attribs = default_sliver.get('tags', []) - for attrib in default_sliver_attribs: - logger.info(attrib) - rspec.version.add_default_sliver_attribute(attrib['tagname'], attrib['value']) + if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'resources': + leases = self.get_leases(slice) + rspec.version.add_leases(leases) + return rspec.toxml() diff --git a/sfa/planetlab/pldriver.py b/sfa/planetlab/pldriver.py index 76f54da9..7835a1dd 100644 --- a/sfa/planetlab/pldriver.py +++ b/sfa/planetlab/pldriver.py @@ -27,7 +27,7 @@ from sfa.planetlab.plshell import PlShell import sfa.planetlab.peers as peers from sfa.planetlab.plaggregate import PlAggregate from sfa.planetlab.plslices import PlSlices -from sfa.planetlab.plxrn import PlXrn, slicename_to_hrn, hostname_to_hrn, hrn_to_pl_slicename +from sfa.planetlab.plxrn import PlXrn, slicename_to_hrn, hostname_to_hrn, hrn_to_pl_slicename, xrn_to_hostname def list_to_dict(recs, key): @@ -160,6 +160,10 @@ class PlDriver (Driver): 'password', 'phone', 'url', 'bio', 'accepted_aup', 'enabled']: update_fields[key] = all_fields[key] + # when updating a user, we always get a 'email' field at this point + # this is because 'email' is a native field in the RegUser object... + if 'email' in update_fields and not update_fields['email']: + del update_fields['email'] self.shell.UpdatePerson(pointer, update_fields) if new_key: @@ -607,6 +611,10 @@ class PlDriver (Driver): #panos adding the info option to the caching key (can be improved) if options.get('info'): version_string = version_string + "_"+options.get('info', 'default') + + # Adding the list_leases option to the caching key + if options.get('list_leases'): + version_string = version_string + "_"+options.get('list_leases', 'default') # look in cache first if cached_requested and self.cache and not slice_hrn: @@ -724,15 +732,31 @@ class PlDriver (Driver): for node in rspec.version.get_nodes_with_slivers(): hostname = None if node.get('component_name'): - hostname = node.get('component_name') + hostname = node.get('component_name').strip() elif node.get('component_id'): - hostname = xrn_to_hostname(node.get('component_id')) + hostname = xrn_to_hostname(node.get('component_id').strip()) if hostname: requested_slivers.append(hostname) nodes = slices.verify_slice_nodes(slice, requested_slivers, peer) # add/remove links links slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes) + + # add/remove leases + requested_leases = [] + kept_leases = [] + for lease in rspec.version.get_leases(): + requested_lease = {} + if not lease.get('lease_id'): + requested_lease['hostname'] = xrn_to_hostname(lease.get('component_id').strip()) + requested_lease['t_from'] = lease.get('t_from') + requested_lease['t_until'] = lease.get('t_until') + else: + kept_leases.append(int(lease['lease_id'])) + if requested_lease.get('hostname'): + requested_leases.append(requested_lease) + + leases = slices.verify_slice_leases(slice, requested_leases, kept_leases, peer) # handle MyPLC peer association. # only used by plc and ple. diff --git a/sfa/planetlab/plshell.py b/sfa/planetlab/plshell.py index d2cd9cd3..f42af880 100644 --- a/sfa/planetlab/plshell.py +++ b/sfa/planetlab/plshell.py @@ -23,6 +23,9 @@ class PlShell: 'UpdateSlice', 'UpdateSliceTag', # also used as-is in importer 'GetSites','GetNodes', + # Lease management methods + 'GetLeases', 'GetLeaseGranularity', 'DeleteLeases','UpdateLeases', + 'AddLeases' ] # support for other names - this is experimental alias_calls = { 'get_authorities':'GetSites', @@ -84,3 +87,4 @@ class PlShell: logger.debug('PlShell %s (%s) returned ... '%(name,actual_name)) return result return func + diff --git a/sfa/planetlab/plslices.py b/sfa/planetlab/plslices.py index 90da4046..49f2fbb3 100644 --- a/sfa/planetlab/plslices.py +++ b/sfa/planetlab/plslices.py @@ -158,6 +158,25 @@ class PlSlices: return sfa_peer + def verify_slice_leases(self, slice, requested_leases, kept_leases, peer): + + leases = self.driver.shell.GetLeases({'name':slice['name']}, ['lease_id']) + current_leases = [lease['lease_id'] for lease in leases] + deleted_leases = list(set(current_leases).difference(kept_leases)) + + try: + if peer: + self.driver.shell.UnBindObjectFromPeer('slice', slice['slice_id'], peer['shortname']) + deleted=self.driver.shell.DeleteLeases(deleted_leases) + for lease in requested_leases: + added=self.driver.shell.AddLeases(lease['hostname'], slice['name'], int(lease['t_from']), int(lease['t_until'])) + + except: + logger.log_exc('Failed to add/remove slice leases') + + return leases + + def verify_slice_nodes(self, slice, requested_slivers, peer): nodes = self.driver.shell.GetNodes(slice['node_ids'], ['node_id', 'hostname', 'interface_ids']) @@ -354,6 +373,7 @@ class PlSlices: users_by_site = defaultdict(list) users_dict = {} for user in users: + user['urn'] = user['urn'].lower() hrn, type = urn_to_hrn(user['urn']) username = get_leaf(hrn) login_base = PlXrn(xrn=user['urn']).pl_login_base() @@ -361,6 +381,7 @@ class PlSlices: user['site'] = login_base if 'email' in user: + user['email'] = user['email'].lower() users_by_email[user['email']] = user users_dict[user['email']] = user else: diff --git a/sfa/rspecs/elements/granularity.py b/sfa/rspecs/elements/granularity.py new file mode 100644 index 00000000..16d30a01 --- /dev/null +++ b/sfa/rspecs/elements/granularity.py @@ -0,0 +1,7 @@ +from sfa.rspecs.elements.element import Element + +class Granularity(Element): + + fields = [ + 'grain', + ] diff --git a/sfa/rspecs/elements/lease.py b/sfa/rspecs/elements/lease.py new file mode 100644 index 00000000..d329a8cb --- /dev/null +++ b/sfa/rspecs/elements/lease.py @@ -0,0 +1,11 @@ +from sfa.rspecs.elements.element import Element + +class Lease(Element): + + fields = [ + 'lease_id', + 'component_id', + 'slice_id' + 't_from', + 't_until', + ] diff --git a/sfa/rspecs/elements/sliver.py b/sfa/rspecs/elements/sliver.py index 204b9296..5186d1f8 100644 --- a/sfa/rspecs/elements/sliver.py +++ b/sfa/rspecs/elements/sliver.py @@ -8,6 +8,6 @@ class Sliver(Element): 'name', 'type', 'tags', - 'disk_images', + 'disk_image', 'fw_rules', ] diff --git a/sfa/rspecs/elements/versions/pgv2Interface.py b/sfa/rspecs/elements/versions/pgv2Interface.py new file mode 100644 index 00000000..1c16e976 --- /dev/null +++ b/sfa/rspecs/elements/versions/pgv2Interface.py @@ -0,0 +1,19 @@ +from sfa.util.xrn import Xrn +from sfa.util.xml import XpathFilter +from sfa.rspecs.elements.interface import Interface + +class PGv2Interface: + + @staticmethod + def add_interfaces(xml, interfaces): + for interface in interfaces: + if_elem = xml.add_instance('interface', interface, ['component_id', 'client_id']) + ips = interface.get('ips', []) + for ip in ips: + if_elem.add_instance('ip', {'address': ip.get('address'), + 'netmask': ip.get('netmask'), + 'type': ip.get('type')}) + + @staticmethod + def get_interfaces(xml): + pass diff --git a/sfa/rspecs/elements/versions/pgv2Node.py b/sfa/rspecs/elements/versions/pgv2Node.py index 9892d8ca..88787b52 100644 --- a/sfa/rspecs/elements/versions/pgv2Node.py +++ b/sfa/rspecs/elements/versions/pgv2Node.py @@ -11,6 +11,7 @@ from sfa.rspecs.elements.bwlimit import BWlimit from sfa.rspecs.elements.pltag import PLTag from sfa.rspecs.elements.versions.pgv2Services import PGv2Services from sfa.rspecs.elements.versions.pgv2SliverType import PGv2SliverType +from sfa.rspecs.elements.versions.pgv2Interface import PGv2Interface from sfa.planetlab.plxrn import xrn_to_hostname @@ -34,9 +35,10 @@ class PGv2Node: if node.get('location'): node_elem.add_instance('location', node['location'], Location.fields) # set interfaces - if node.get('interfaces'): - for interface in node.get('interfaces', []): - node_elem.add_instance('interface', interface, ['component_id', 'client_id']) + PGv2Interface.add_interfaces(node_elem, node.get('interfaces')) + #if node.get('interfaces'): + # for interface in node.get('interfaces', []): + # node_elem.add_instance('interface', interface, ['component_id', 'client_id']) # set available element if node.get('boot_state'): if node.get('boot_state').lower() == 'boot': diff --git a/sfa/rspecs/elements/versions/pgv2SliverType.py b/sfa/rspecs/elements/versions/pgv2SliverType.py index b5d6da02..3c0e2584 100644 --- a/sfa/rspecs/elements/versions/pgv2SliverType.py +++ b/sfa/rspecs/elements/versions/pgv2SliverType.py @@ -17,7 +17,7 @@ class PGv2SliverType: sliver_elem.set('name', sliver['type']) if sliver.get('client_id'): sliver_elem.set('client_id', sliver['client_id']) - images = sliver.get('disk_images') + images = sliver.get('disk_image') if images and isinstance(images, list): PGv2DiskImage.add_images(sliver_elem, images) fw_rules = sliver.get('fw_rules') @@ -47,7 +47,7 @@ class PGv2SliverType: sliver['component_id'] = xml.attrib['component_id'] if 'name' in sliver_elem.attrib: sliver['type'] = sliver_elem.attrib['name'] - sliver['images'] = PGv2DiskImage.get_images(sliver_elem) + sliver['disk_image'] = PGv2DiskImage.get_images(sliver_elem) sliver['fw_rules'] = PLOSv1FWRule.get_rules(sliver_elem) slivers.append(sliver) return slivers diff --git a/sfa/rspecs/elements/versions/sfav1Lease.py b/sfa/rspecs/elements/versions/sfav1Lease.py new file mode 100644 index 00000000..7a2320e3 --- /dev/null +++ b/sfa/rspecs/elements/versions/sfav1Lease.py @@ -0,0 +1,62 @@ +from sfa.util.sfalogging import logger +from sfa.util.xml import XpathFilter +from sfa.util.xrn import Xrn + +from sfa.rspecs.elements.element import Element +from sfa.rspecs.elements.node import Node +from sfa.rspecs.elements.sliver import Sliver +from sfa.rspecs.elements.location import Location +from sfa.rspecs.elements.hardware_type import HardwareType +from sfa.rspecs.elements.disk_image import DiskImage +from sfa.rspecs.elements.interface import Interface +from sfa.rspecs.elements.bwlimit import BWlimit +from sfa.rspecs.elements.pltag import PLTag +from sfa.rspecs.elements.versions.sfav1Sliver import SFAv1Sliver +from sfa.rspecs.elements.versions.sfav1PLTag import SFAv1PLTag +from sfa.rspecs.elements.versions.pgv2Services import PGv2Services +from sfa.rspecs.elements.lease import Lease + +from sfa.planetlab.plxrn import xrn_to_hostname + +class SFAv1Lease: + + @staticmethod + def add_leases(xml, leases): + + network_elems = xml.xpath('//network') + if len(network_elems) > 0: + network_elem = network_elems[0] + elif len(leases) > 0: + network_urn = Xrn(leases[0]['component_id']).get_authority_urn().split(':')[0] + network_elem = xml.add_element('network', name = network_urn) + else: + network_elem = xml + + lease_elems = [] + for lease in leases: + lease_fields = ['lease_id', 'component_id', 'slice_id', 't_from', 't_until'] + lease_elem = network_elem.add_instance('lease', lease, lease_fields) + lease_elems.append(lease_elem) + + + @staticmethod + def get_leases(xml, filter={}): + xpath = '//lease%s | //default:lease%s' % (XpathFilter.xpath(filter), XpathFilter.xpath(filter)) + lease_elems = xml.xpath(xpath) + return SFAv1Lease.get_lease_objs(lease_elems) + + @staticmethod + def get_lease_objs(lease_elems): + leases = [] + for lease_elem in lease_elems: + lease = Lease(lease_elem.attrib, lease_elem) + if lease.get('lease_id'): + lease['lease_id'] = lease_elem.attrib['lease_id'] + lease['component_id'] = lease_elem.attrib['component_id'] + lease['slice_id'] = lease_elem.attrib['slice_id'] + lease['t_from'] = lease_elem.attrib['t_from'] + lease['t_until'] = lease_elem.attrib['t_until'] + + leases.append(lease) + return leases + diff --git a/sfa/rspecs/elements/versions/sfav1Node.py b/sfa/rspecs/elements/versions/sfav1Node.py index d8db263a..024a52e6 100644 --- a/sfa/rspecs/elements/versions/sfav1Node.py +++ b/sfa/rspecs/elements/versions/sfav1Node.py @@ -57,6 +57,12 @@ class SFAv1Node: if location: node_elem.add_instance('location', location, Location.fields) + # add granularity of the reservation system + granularity = node.get('granularity') + if granularity: + node_elem.add_instance('granularity', granularity, granularity.fields) + + if isinstance(node.get('interfaces'), list): for interface in node.get('interfaces', []): node_elem.add_instance('interface', interface, ['component_id', 'client_id', 'ipv4']) diff --git a/sfa/rspecs/rspec_elements.py b/sfa/rspecs/rspec_elements.py index ce3cac76..90f36a3d 100644 --- a/sfa/rspecs/rspec_elements.py +++ b/sfa/rspecs/rspec_elements.py @@ -20,6 +20,8 @@ RSpecElements = Enum( SERVICES='SERVICES', SLIVER='SLIVER', SLIVER_TYPE='SLIVER_TYPE', + LEASE='LEASE', + GRANULARITY='GRANULARITY', ) class RSpecElement: diff --git a/sfa/rspecs/versions/sfav1.py b/sfa/rspecs/versions/sfav1.py index 964da059..fd2e0313 100644 --- a/sfa/rspecs/versions/sfav1.py +++ b/sfa/rspecs/versions/sfav1.py @@ -8,6 +8,7 @@ from sfa.rspecs.elements.element import Element from sfa.rspecs.elements.versions.pgv2Link import PGv2Link from sfa.rspecs.elements.versions.sfav1Node import SFAv1Node from sfa.rspecs.elements.versions.sfav1Sliver import SFAv1Sliver +from sfa.rspecs.elements.versions.sfav1Lease import SFAv1Lease class SFAv1(RSpecVersion): enabled = True @@ -216,6 +217,14 @@ class SFAv1(RSpecVersion): self.xml.append(network.element) current_networks.append(current_network) + # Leases + + def get_leases(self, filter=None): + return SFAv1Lease.get_leases(self.xml, filter) + + def add_leases(self, leases, network = None, no_dupes=False): + SFAv1Lease.add_leases(self.xml, leases) + if __name__ == '__main__': from sfa.rspecs.rspec import RSpec from sfa.rspecs.rspec_elements import * diff --git a/sfa/storage/record.py b/sfa/storage/record.py index ad1c8c76..f3aa8ee9 100644 --- a/sfa/storage/record.py +++ b/sfa/storage/record.py @@ -62,13 +62,13 @@ class Record: xml_record.parse_dict (input_dict) return xml_record.toxml() - def dump(self, format=None, dump_parents=False): + def dump(self, format=None, dump_parents=False, sort=False): if not format: format = 'text' else: format = format.lower() if format == 'text': - self.dump_text(dump_parents) + self.dump_text(dump_parents,sort=sort) elif format == 'xml': print self.save_as_xml() elif format == 'simple': @@ -76,11 +76,13 @@ class Record: else: raise Exception, "Invalid format %s" % format - def dump_text(self, dump_parents=False): - print "".join(['=' for i in range(40)]) + def dump_text(self, dump_parents=False, sort=False): + print 40*'=' print "RECORD" # print remaining fields - for attrib_name in self.fields(): + fields=self.fields() + if sort: fields.sort() + for attrib_name in fields: attrib = getattr(self, attrib_name) # skip internals if attrib_name.startswith('_'): continue diff --git a/sfa/util/xrn.py b/sfa/util/xrn.py index 60b57b98..19b1fc18 100644 --- a/sfa/util/xrn.py +++ b/sfa/util/xrn.py @@ -92,16 +92,21 @@ class Xrn: return True return False + ########## basic tools on URNs URN_PREFIX = "urn:publicid:IDN" + URN_PREFIX_lower = "urn:publicid:idn" + + @staticmethod + def is_urn (text): + return text.lower().startswith(Xrn.URN_PREFIX_lower) - ########## basic tools on URNs @staticmethod def urn_full (urn): - if urn.startswith(Xrn.URN_PREFIX): return urn + if Xrn.is_urn(urn): return urn else: return Xrn.URN_PREFIX+urn @staticmethod def urn_meaningful (urn): - if urn.startswith(Xrn.URN_PREFIX): return urn[len(Xrn.URN_PREFIX):] + if Xrn.is_urn(urn): return urn[len(Xrn.URN_PREFIX):] else: return urn @staticmethod def urn_split (urn): @@ -118,7 +123,7 @@ class Xrn: if not xrn: xrn = "" # user has specified xrn : guess if urn or hrn - if xrn.startswith(Xrn.URN_PREFIX): + if Xrn.is_urn(xrn): self.hrn=None self.urn=xrn self.urn_to_hrn() @@ -182,7 +187,7 @@ class Xrn: """ # if not self.urn or not self.urn.startswith(Xrn.URN_PREFIX): - if not self.urn.startswith(Xrn.URN_PREFIX): + if not Xrn.is_urn(self.urn): raise SfaAPIError, "Xrn.urn_to_hrn" parts = Xrn.urn_split(self.urn) @@ -216,7 +221,7 @@ class Xrn: """ # if not self.hrn or self.hrn.startswith(Xrn.URN_PREFIX): - if self.hrn.startswith(Xrn.URN_PREFIX): + if Xrn.is_urn(self.hrn): raise SfaAPIError, "Xrn.hrn_to_urn, hrn=%s"%self.hrn if self.type and self.type.startswith('authority'):