Setting tag PLCAPI-5.0-1 PLCAPI-5.0-1
authorThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Fri, 29 Jan 2010 14:28:43 +0000 (14:28 +0000)
committerThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Fri, 29 Jan 2010 14:28:43 +0000 (14:28 +0000)
first working version of 5.0:
pld.c/, db-config.d/ and nodeconfig/ scripts should now sit in the module they belong to
nodefamily is 3-fold with pldistro-fcdistro-arch
site and person tags
new methods GetSliceFamily and GetNodeFlavour
deprecated the dummynet stuff that were for the external dummyboxes
tags definition : more consistency between db-config scripts and accessors
(get accessor to create the tag type too if absent)
logging an event for AddSliceToNodes

311 files changed:
Makefile
ModPython.py
PLC/API.py
PLC/Accessor.py [new file with mode: 0644]
PLC/Accessors/Accessors_example_ple.py [new file with mode: 0644]
PLC/Accessors/Accessors_site.py
PLC/Accessors/Accessors_standard.py
PLC/Accessors/Accessors_wireless.py
PLC/Accessors/Factory.py
PLC/AddressTypes.py
PLC/Addresses.py
PLC/Auth.py
PLC/Boot.py
PLC/BootStates.py
PLC/ConfFiles.py
PLC/Config.py
PLC/Debug.py
PLC/EventObjects.py
PLC/Events.py
PLC/Faults.py
PLC/Filter.py
PLC/GPG.py
PLC/Ilinks.py
PLC/InitScripts.py
PLC/InterfaceSettings.py [deleted file]
PLC/InterfaceTags.py [new file with mode: 0644]
PLC/Interfaces.py
PLC/KeyTypes.py
PLC/Keys.py
PLC/Legacy/AddNodeNetwork.py [new file with mode: 0644]
PLC/Legacy/AddNodeNetworkSetting.py [new file with mode: 0644]
PLC/Legacy/AddNodeNetworkSettingType.py [new file with mode: 0644]
PLC/Legacy/AddSliceAttribute.py [new file with mode: 0644]
PLC/Legacy/AddSliceAttributeType.py [new file with mode: 0644]
PLC/Legacy/DeleteNodeNetwork.py [new file with mode: 0644]
PLC/Legacy/DeleteNodeNetworkSetting.py [new file with mode: 0644]
PLC/Legacy/DeleteNodeNetworkSettingType.py [new file with mode: 0644]
PLC/Legacy/DeleteSliceAttribute.py [new file with mode: 0644]
PLC/Legacy/DeleteSliceAttributeType.py [new file with mode: 0644]
PLC/Legacy/GetNodeNetworkSettingTypes.py [new file with mode: 0644]
PLC/Legacy/GetNodeNetworkSettings.py [new file with mode: 0644]
PLC/Legacy/GetNodeNetworks.py [new file with mode: 0644]
PLC/Legacy/GetSliceAttributeTypes.py [new file with mode: 0644]
PLC/Legacy/GetSliceAttributes.py [new file with mode: 0644]
PLC/Legacy/NodeNetworks.py [deleted file]
PLC/Legacy/Types.py [deleted file]
PLC/Legacy/UpdateNodeNetwork.py [new file with mode: 0644]
PLC/Legacy/UpdateNodeNetworkSetting.py [new file with mode: 0644]
PLC/Legacy/UpdateNodeNetworkSettingType.py [new file with mode: 0644]
PLC/Legacy/UpdateSliceAttribute.py [new file with mode: 0644]
PLC/Legacy/UpdateSliceAttributeType.py [new file with mode: 0644]
PLC/Legacy/__init__.py
PLC/Messages.py
PLC/Method.py
PLC/Methods/AddAddressType.py
PLC/Methods/AddAddressTypeToAddress.py
PLC/Methods/AddBootState.py
PLC/Methods/AddConfFile.py
PLC/Methods/AddConfFileToNode.py
PLC/Methods/AddConfFileToNodeGroup.py
PLC/Methods/AddIlink.py
PLC/Methods/AddInitScript.py
PLC/Methods/AddInterface.py
PLC/Methods/AddInterfaceTag.py [moved from PLC/Methods/AddInterfaceSetting.py with 76% similarity]
PLC/Methods/AddKeyType.py
PLC/Methods/AddMessage.py
PLC/Methods/AddNetworkMethod.py
PLC/Methods/AddNetworkType.py
PLC/Methods/AddNode.py
PLC/Methods/AddNodeGroup.py
PLC/Methods/AddNodeTag.py
PLC/Methods/AddNodeToPCU.py
PLC/Methods/AddNodeType.py [new file with mode: 0644]
PLC/Methods/AddPCU.py
PLC/Methods/AddPCUProtocolType.py
PLC/Methods/AddPCUType.py
PLC/Methods/AddPeer.py
PLC/Methods/AddPerson.py
PLC/Methods/AddPersonKey.py
PLC/Methods/AddPersonTag.py [new file with mode: 0644]
PLC/Methods/AddPersonToSite.py
PLC/Methods/AddPersonToSlice.py
PLC/Methods/AddRole.py
PLC/Methods/AddRoleToPerson.py
PLC/Methods/AddSession.py
PLC/Methods/AddSite.py
PLC/Methods/AddSiteAddress.py
PLC/Methods/AddSiteTag.py [new file with mode: 0644]
PLC/Methods/AddSlice.py
PLC/Methods/AddSliceAttribute.py [deleted file]
PLC/Methods/AddSliceInstantiation.py
PLC/Methods/AddSliceTag.py [new file with mode: 0644]
PLC/Methods/AddSliceToNodes.py
PLC/Methods/AddSliceToNodesWhitelist.py
PLC/Methods/AddTagType.py
PLC/Methods/AuthCheck.py
PLC/Methods/BindObjectToPeer.py [new file with mode: 0644]
PLC/Methods/BlacklistKey.py
PLC/Methods/BootCheckAuthentication.py
PLC/Methods/BootGetNodeDetails.py
PLC/Methods/BootNotifyOwners.py
PLC/Methods/BootUpdateNode.py
PLC/Methods/DeleteAddress.py
PLC/Methods/DeleteAddressType.py
PLC/Methods/DeleteAddressTypeFromAddress.py
PLC/Methods/DeleteBootState.py
PLC/Methods/DeleteConfFile.py
PLC/Methods/DeleteConfFileFromNode.py
PLC/Methods/DeleteConfFileFromNodeGroup.py
PLC/Methods/DeleteIlink.py
PLC/Methods/DeleteInitScript.py
PLC/Methods/DeleteInterface.py
PLC/Methods/DeleteInterfaceTag.py [moved from PLC/Methods/DeleteInterfaceSetting.py with 65% similarity]
PLC/Methods/DeleteKey.py
PLC/Methods/DeleteKeyType.py
PLC/Methods/DeleteMessage.py
PLC/Methods/DeleteNetworkMethod.py
PLC/Methods/DeleteNetworkType.py
PLC/Methods/DeleteNode.py
PLC/Methods/DeleteNodeFromPCU.py
PLC/Methods/DeleteNodeGroup.py
PLC/Methods/DeleteNodeTag.py
PLC/Methods/DeleteNodeType.py [new file with mode: 0644]
PLC/Methods/DeletePCU.py
PLC/Methods/DeletePCUProtocolType.py
PLC/Methods/DeletePCUType.py
PLC/Methods/DeletePeer.py
PLC/Methods/DeletePerson.py
PLC/Methods/DeletePersonFromSite.py
PLC/Methods/DeletePersonFromSlice.py
PLC/Methods/DeletePersonTag.py [new file with mode: 0644]
PLC/Methods/DeleteRole.py
PLC/Methods/DeleteRoleFromPerson.py
PLC/Methods/DeleteSession.py
PLC/Methods/DeleteSite.py
PLC/Methods/DeleteSiteTag.py [new file with mode: 0644]
PLC/Methods/DeleteSlice.py
PLC/Methods/DeleteSliceFromNodes.py
PLC/Methods/DeleteSliceFromNodesWhitelist.py
PLC/Methods/DeleteSliceInstantiation.py
PLC/Methods/DeleteSliceTag.py [moved from PLC/Methods/DeleteSliceAttribute.py with 66% similarity]
PLC/Methods/DeleteTagType.py
PLC/Methods/GenerateNodeConfFile.py
PLC/Methods/GetAddressTypes.py
PLC/Methods/GetAddresses.py
PLC/Methods/GetBootMedium.py
PLC/Methods/GetBootStates.py
PLC/Methods/GetConfFiles.py
PLC/Methods/GetEventObjects.py
PLC/Methods/GetEvents.py
PLC/Methods/GetIlinks.py
PLC/Methods/GetInitScripts.py
PLC/Methods/GetInterfaceTags.py [moved from PLC/Methods/GetInterfaceSettings.py with 59% similarity]
PLC/Methods/GetInterfaces.py
PLC/Methods/GetKeyTypes.py
PLC/Methods/GetKeys.py
PLC/Methods/GetMessages.py
PLC/Methods/GetNetworkMethods.py
PLC/Methods/GetNetworkTypes.py
PLC/Methods/GetNodeFlavour.py [new file with mode: 0644]
PLC/Methods/GetNodeGroups.py
PLC/Methods/GetNodeTags.py
PLC/Methods/GetNodeTypes.py [new file with mode: 0644]
PLC/Methods/GetNodes.py
PLC/Methods/GetPCUProtocolTypes.py
PLC/Methods/GetPCUTypes.py
PLC/Methods/GetPCUs.py
PLC/Methods/GetPeerData.py
PLC/Methods/GetPeerName.py
PLC/Methods/GetPeers.py
PLC/Methods/GetPersonTags.py [new file with mode: 0644]
PLC/Methods/GetPersons.py
PLC/Methods/GetPlcRelease.py
PLC/Methods/GetRoles.py
PLC/Methods/GetSession.py
PLC/Methods/GetSessions.py
PLC/Methods/GetSiteTags.py [new file with mode: 0644]
PLC/Methods/GetSites.py
PLC/Methods/GetSliceFamily.py [new file with mode: 0644]
PLC/Methods/GetSliceInstantiations.py
PLC/Methods/GetSliceKeys.py
PLC/Methods/GetSliceTags.py [moved from PLC/Methods/GetSliceAttributes.py with 59% similarity]
PLC/Methods/GetSliceTicket.py
PLC/Methods/GetSlices.py
PLC/Methods/GetSlicesMD5.py [deleted file]
PLC/Methods/GetSlivers.py
PLC/Methods/GetTagTypes.py
PLC/Methods/GetWhitelist.py
PLC/Methods/NotifyPersons.py
PLC/Methods/NotifySupport.py
PLC/Methods/RebootNode.py
PLC/Methods/RebootNodeWithPCU.py [new file with mode: 0644]
PLC/Methods/RefreshPeer.py
PLC/Methods/ReportRunlevel.py [new file with mode: 0644]
PLC/Methods/ResetPassword.py
PLC/Methods/ResolveSlices.py [new file with mode: 0644]
PLC/Methods/SetPersonPrimarySite.py
PLC/Methods/SliceCreate.py
PLC/Methods/SliceDelete.py
PLC/Methods/SliceExtendedInfo.py
PLC/Methods/SliceGetTicket.py
PLC/Methods/SliceInfo.py
PLC/Methods/SliceListNames.py
PLC/Methods/SliceListUserSlices.py
PLC/Methods/SliceNodesAdd.py
PLC/Methods/SliceNodesDel.py
PLC/Methods/SliceNodesList.py
PLC/Methods/SliceRenew.py
PLC/Methods/SliceTicketGet.py
PLC/Methods/SliceUpdate.py
PLC/Methods/SliceUserAdd.py
PLC/Methods/SliceUserDel.py
PLC/Methods/SliceUsersList.py
PLC/Methods/UnBindObjectFromPeer.py [new file with mode: 0644]
PLC/Methods/UpdateAddress.py
PLC/Methods/UpdateAddressType.py
PLC/Methods/UpdateConfFile.py
PLC/Methods/UpdateIlink.py
PLC/Methods/UpdateInitScript.py
PLC/Methods/UpdateInterface.py
PLC/Methods/UpdateInterfaceTag.py [moved from PLC/Methods/UpdateInterfaceSetting.py with 64% similarity]
PLC/Methods/UpdateKey.py
PLC/Methods/UpdateMessage.py
PLC/Methods/UpdateNode.py
PLC/Methods/UpdateNodeGroup.py
PLC/Methods/UpdateNodeTag.py
PLC/Methods/UpdatePCU.py
PLC/Methods/UpdatePCUProtocolType.py
PLC/Methods/UpdatePCUType.py
PLC/Methods/UpdatePeer.py
PLC/Methods/UpdatePerson.py
PLC/Methods/UpdatePersonTag.py [new file with mode: 0644]
PLC/Methods/UpdateSite.py
PLC/Methods/UpdateSiteTag.py [new file with mode: 0644]
PLC/Methods/UpdateSlice.py
PLC/Methods/UpdateSliceAttribute.py [deleted file]
PLC/Methods/UpdateSliceTag.py [new file with mode: 0644]
PLC/Methods/UpdateTagType.py
PLC/Methods/VerifyPerson.py
PLC/Methods/__init__.py
PLC/Methods/system/listMethods.py
PLC/NetworkMethods.py
PLC/NetworkTypes.py
PLC/NodeGroups.py
PLC/NodeTags.py
PLC/NodeTypes.py [new file with mode: 0644]
PLC/Nodes.py
PLC/PCUProtocolTypes.py
PLC/PCUTypes.py
PLC/PCUs.py
PLC/POD.py
PLC/Parameter.py
PLC/Peers.py
PLC/PersonTags.py [new file with mode: 0644]
PLC/Persons.py
PLC/PostgreSQL.py
PLC/PyCurl.py
PLC/Roles.py
PLC/Sessions.py
PLC/Shell.py
PLC/SiteTags.py [new file with mode: 0644]
PLC/Sites.py
PLC/SliceAttributes.py [deleted file]
PLC/SliceInstantiations.py
PLC/SliceTags.py [new file with mode: 0644]
PLC/Slices.py
PLC/Table.py
PLC/TagTypes.py
PLC/__init__.py
PLC/sendmail.py
PLC/v42Legacy.py [new file with mode: 0644]
PLC/v42LegacyNodeNetworkSettingTypes.py [new file with mode: 0644]
PLC/v42LegacyNodeNetworkSettings.py [new file with mode: 0644]
PLC/v42LegacyNodeNetworks.py [new file with mode: 0644]
PLC/v42LegacySliceAttributeTypes.py [new file with mode: 0644]
PLC/v42LegacySliceAttributes.py [new file with mode: 0644]
PLCAPI.spec
Server.py
db-config.d/000-functions [new file with mode: 0644]
db-config.d/001-admin_user [new file with mode: 0644]
db-config.d/002-system_site [new file with mode: 0644]
db-config.d/010-slice_tags [new file with mode: 0644]
db-config.d/011-standard_tags [new file with mode: 0644]
db-config.d/012-wireless_tags [new file with mode: 0644]
db-config.d/020-boot_states [new file with mode: 0644]
db-config.d/050-pcu_types [new file with mode: 0644]
db-config.d/060-messages [new file with mode: 0644]
doc/DocBook.py
doc/DocBookLocal.py
doc/Makefile
doc/PLCAPI.xml.in
migrations/011-down-site-and-person-tags.sql [new file with mode: 0644]
migrations/011-up-site-and-person-tags.sql [new file with mode: 0644]
migrations/v42-to-v43/migrate.sed [moved from migrations/v4-to-v5/migrate.sed with 58% similarity]
migrations/v42-to-v43/migrate.sh [moved from migrations/v4-to-v5/migrate.sh with 75% similarity]
migrations/v42-to-v43/migrate.sql [moved from migrations/v4-to-v5/migrate.sql with 71% similarity]
migrations/v42-to-v43/parse-site-nodegroups.py [moved from migrations/v4-to-v5/parse-site-nodegroups.py with 78% similarity]
migrations/v42-to-v43/site-nodegroups.def [moved from migrations/v4-to-v5/site-nodegroups.def with 97% similarity]
php/Makefile
php/header.php
php/methods.py
php/xmlrpc/xmlrpc-epi-php.c
planetlab5.sql
plc.d/api [new file with mode: 0755]
plc.d/db [new file with mode: 0755]
plc.d/postgresql [new file with mode: 0755]
setup.py
tools/slice_attributes.py
wsdl/Makefile [new file with mode: 0644]
wsdl/api2wsdl.py [new file with mode: 0755]
wsdl/globals.py [new file with mode: 0644]

index 2df9ee9..d2e44b0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,10 +5,11 @@
 # Copyright (C) 2005 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 # Metafiles - manage Legacy/ and Accessors by hand
-init := PLC/__init__.py PLC/Methods/__init__.py 
+init := PLC/__init__.py PLC/Methods/__init__.py PLC/Legacy/__init__.py
 
 # python-pycurl and python-psycopg2 avail. from fedora 5
 # we used to ship our own version of psycopg2 and pycurl, for fedora4
@@ -63,7 +64,7 @@ ifneq ($(PLC_now),$(PLC_files))
 PLC/__init__.py: force
 endif
 PLC/__init__.py: 
-       (echo 'all = """' ; cd PLC; ls -1 *.py | grep -v __init__ | sed -e 's,.py$$,,' ; echo '""".split()') > $@
+       (echo '## Please use make index to update this file' ; echo 'all = """' ; cd PLC; ls -1 *.py | grep -v __init__ | sed -e 's,.py$$,,' ; echo '""".split()') > $@
 
 ########## Methods/
 # the current content of __init__.py
@@ -76,9 +77,25 @@ ifneq ($(METHODS_now),$(METHODS_files))
 PLC/Methods/__init__.py: force
 endif
 PLC/Methods/__init__.py: 
-       (echo 'native_methods = """' ; cd PLC/Methods; ls -1 *.py system/*.py | grep -v __init__ | sed -e 's,.py$$,,' -e 's,system/,system.,' ; echo '""".split()') > $@
+       (echo '## Please use make index to update this file' ; echo 'native_methods = """' ; cd PLC/Methods; ls -1 *.py system/*.py | grep -v __init__ | sed -e 's,.py$$,,' -e 's,system/,system.,' ; echo '""".split()') > $@
+
+########## Legacy/
+# the current content of __init__.py
+LEGACY_now := $(sort $(shell fgrep -v '"' PLC/Legacy/__init__.py 2>/dev/null))
+# what should be declared
+LEGACY_paths := $(filter-out %/__init__.py, $(wildcard PLC/Legacy/*.py))
+LEGACY_files := $(sort $(notdir $(LEGACY_paths:.py=)))
+
+ifneq ($(LEGACY_now),$(LEGACY_files))
+PLC/Legacy/__init__.py: force
+endif
+PLC/Legacy/__init__.py: 
+       (echo '## Please use make index to update this file' ; echo 'native_methods = """' ; cd PLC/Legacy; ls -1 *.py | grep -v __init__ | sed -e 's,.py$$,,' -e 's,system/,system.,' ; echo '""".split()') > $@
 
 ##########
+
+
+
 force:
 
 .PHONY: all install force clean index tags $(subdirs)
@@ -96,12 +113,12 @@ tags:
 # (*) otherwise, entering through the root context
 # make sync PLCHOST=testbox1.inria.fr GUEST=vplc03.inria.fr
 
+PLCHOST ?= testplc.onelab.eu
+
 ifdef GUEST
-ifdef PLCHOST
 SSHURL:=root@$(PLCHOST):/vservers/$(GUEST)
 SSHCOMMAND:=ssh root@$(PLCHOST) vserver $(GUEST)
 endif
-endif
 ifdef PLC
 SSHURL:=root@$(PLC):/
 SSHCOMMAND:=ssh root@$(PLC)
@@ -115,11 +132,11 @@ RSYNC                     := rsync -a -v $(RSYNC_COND_DRY_RUN) $(RSYNC_EXCLUDES)
 sync:
 ifeq (,$(SSHURL))
        @echo "sync: You must define, either PLC, or PLCHOST & GUEST, on the command line"
-       @echo "  e.g. make sync PLC=private.one-lab.org"
+       @echo "  e.g. make sync PLC=boot.planetlab.eu"
        @echo "  or   make sync PLCHOST=testbox1.inria.fr GUEST=vplc03.inria.fr"
        @exit 1
 else
-       +$(RSYNC) PLC planetlab5.sql migrations $(SSHURL)/usr/share/plc_api/
+       +$(RSYNC) plcsh PLC planetlab5.sql migrations $(SSHURL)/usr/share/plc_api/
        $(SSHCOMMAND) exec apachectl graceful
 endif
 
index c722114..0d630d7 100644 (file)
@@ -6,6 +6,7 @@
 #
 # Copyright (C) 2004-2006 The Trustees of Princeton University
 # $Id$
+# $URL$
 #
 
 import sys
index 0710d95..c4ec4c2 100644 (file)
@@ -6,6 +6,7 @@
 #
 # Copyright (C) 2004-2006 The Trustees of Princeton University
 # $Id$
+# $URL$
 #
 
 import sys
@@ -83,8 +84,8 @@ except ImportError:
 from PLC.Config import Config
 from PLC.Faults import *
 import PLC.Methods
-import PLC.Accessors
 import PLC.Legacy
+import PLC.Accessors
 
 def import_deep(name):
     mod = __import__(name)
@@ -97,11 +98,12 @@ class PLCAPI:
 
     # flat list of method names
     native_methods = PLC.Methods.native_methods
+    legacy_methods = PLC.Legacy.native_methods
 
     # other_methods_map : dict {methodname: fullpath}
     # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
     other_methods_map={}
-    for subdir in [ 'Accessors', 'Legacy']:
+    for subdir in [ 'Accessors' ]:
         path="PLC."+subdir
         # scan e.g. PLC.Accessors.__all__
         pkg = __import__(path).__dict__[subdir]
@@ -110,7 +112,7 @@ class PLCAPI:
             for method in getattr(import_deep(fullpath),"methods"):
                 other_methods_map[method] = fullpath
 
-    all_methods = native_methods + other_methods_map.keys()
+    all_methods = native_methods + legacy_methods + other_methods_map.keys()
     
     def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
         self.encoding = encoding
@@ -143,14 +145,15 @@ class PLCAPI:
         try:
             classname = method.split(".")[-1]
             if method in self.native_methods:
-                module = __import__("PLC.Methods." + method, globals(), locals(), [classname])
-                return getattr(module, classname)(self)
+                fullpath="PLC.Methods." + method
+            elif method in self.legacy_methods:
+                fullpath="PLC.Legacy." + method
             else:
                 fullpath=self.other_methods_map[method]
-                module = __import__(fullpath, globals(), locals(), [classname])
-                return getattr(module, classname)(self)
+            module = __import__(fullpath, globals(), locals(), [classname])
+            return getattr(module, classname)(self)
         except ImportError, AttributeError:
-            raise PLCInvalidAPIMethod, method
+            raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
 
     def call(self, source, method, *args):
         """
diff --git a/PLC/Accessor.py b/PLC/Accessor.py
new file mode 100644 (file)
index 0000000..8a8d16d
--- /dev/null
@@ -0,0 +1,58 @@
+# $Id$
+# $URL$
+#
+# Thierry Parmentelat - INRIA
+#
+#
+# just a placeholder for storing accessor-related tag checkers
+# this is filled by the accessors factory
+
+from PLC.TagTypes import TagTypes, TagType
+
+# implementation
+class Accessor (object) :
+    """This is placeholder for storing accessor-related tag checkers
+methods in this class are defined by the accessors factory
+
+This is implemented as a singleton, so we can cache results over time"""
+
+    _instance = None
+
+    def __init__ (self, api):
+        self.api=api
+        # 'tagname'=>'tag_id'
+        self.cache={}
+
+    def has_cache (self,tagname): return self.cache.has_key(tagname)
+    def get_cache (self,tagname): return self.cache[tagname]
+    def set_cache (self,tagname,tag_id): self.cache[tagname]=tag_id
+
+    def locate_or_create_tag (self,tagname,category, description, min_role_id):
+        "search tag type from tagname & create if needed"
+        
+        # cached ?
+        if self.has_cache (tagname):
+            return self.get_cache(tagname)
+        # search
+        tag_types = TagTypes (self.api, {'tagname':tagname})
+        if tag_types:
+            tag_type = tag_types[0]
+        else:
+            # not found: create it
+            tag_type_fields = {'tagname':tagname, 
+                               'category' :  category,
+                               'description' : description,
+                               'min_role_id': min_role_id}
+            tag_type = TagType (self.api, tag_type_fields)
+            tag_type.sync()
+        tag_type_id = tag_type['tag_type_id']
+        self.set_cache(tagname,tag_type_id)
+        return tag_type_id
+
+
+####################
+# make it a singleton so we can cache stuff in there over time
+def AccessorSingleton (api):
+    if not Accessor._instance:
+        Accessor._instance = Accessor(api)
+    return Accessor._instance
diff --git a/PLC/Accessors/Accessors_example_ple.py b/PLC/Accessors/Accessors_example_ple.py
new file mode 100644 (file)
index 0000000..94ef84e
--- /dev/null
@@ -0,0 +1,51 @@
+# Thierry Parmentelat - INRIA
+# $Id: Accessors_site.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-29/PLC/Accessors/Accessors_site.py $
+#
+
+methods=[]
+
+from PLC.Nodes import Node
+from PLC.Interfaces import Interface
+from PLC.Slices import Slice
+#from PLC.Ilinks import Ilink
+
+from PLC.Accessors.Factory import define_accessors, all_roles, tech_roles
+
+import sys
+current_module = sys.modules[__name__]
+
+#### example 1 : attach vlan ids on interfaces
+# The third argument expose_in_api is a boolean flag that tells whether this tag may be handled 
+#   through the Add/Get/Update methods as a native field
+# 
+#define_accessors(current_module, Interface, "Vlan", "vlan",
+#                  "interface/general", "tag for setting VLAN id",
+#                  get_roles=all_roles, set_roles=tech_roles)
+
+##### example 2 :
+# the slice page uses the category field in the following way
+# it considers all tag types for which 'category' matches 'node*/ui*'
+# for these, the category field is split into pieces using /
+# the parts may define the following settings:
+# header: to use instead of the full tagname (in which case a footnote appears with the 'description')
+# type: exported as the type for the javascript table (used for how-to-sort)
+# rank: to be used for sorting columns (defaults to tagname)
+
+#################### MySlice tags
+define_accessors(current_module, Node, "Reliability", "reliability",
+                 # category
+                 "node/monitor/ui/header=R/type=int/rank=ad", 
+                 # description : used to add a footnote to the table if header is set in category
+                 "average reliability (% uptime) over the last week",
+                  get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
+
+define_accessors(current_module, Node, "Load", "load",
+                 "node/monitor/ui/header=l/type=sortAlphaNumericBottom", 
+                 "average load (% CPU utilization) over the last week",
+                  get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
+
+define_accessors(current_module, Node, "ASNumber", "asnumber",
+                 "node/location/ui/header=AS/type=sortAlphaNumericBottom/rank=z", 
+                 "Autonomous System id",
+                 get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
index 1816117..f27cd8d 100644 (file)
@@ -1,8 +1,12 @@
 # Thierry Parmentelat - INRIA
 # $Id$
+# $URL$
 #
-# Shortcuts_site.py is the place where you can define your own tag accessors
-# this will not be overwritten through rpm upgrades in a myplc-devel packaging
+# Accessors_site.py is the place where you can define your own local tag accessors
+# this will not be overwritten through rpm upgrades
+#
+# Now that Sites are taggable too, the name may be confusing, think of is as
+# Accessors_local.py
 #
 # methods denotes the set of methods (names) that get inserted into the API
 # it is updated by define_accessors
@@ -12,7 +16,9 @@ methods=[]
 from PLC.Nodes import Node
 from PLC.Interfaces import Interface
 from PLC.Slices import Slice
-from PLC.Ilinks import Ilink
+from PLC.Sites import Site
+from PLC.Persons import Person
+#from PLC.Ilinks import Ilink
 
 from PLC.Accessors.Factory import define_accessors, all_roles, tech_roles
 
@@ -20,5 +26,9 @@ import sys
 current_module = sys.modules[__name__]
 
 #### example : attach vlan ids on interfaces
-###define_accessors(current_module, Interface, "Vlan", "vlan","interface/general", "tag for setting VLAN id",
-###                 get_roles=all_roles, set_roles=tech_roles)
+# The third argument expose_in_api is a boolean flag that tells whether this tag may be handled 
+#   through the Add/Get/Update methods as a native field
+# 
+#define_accessors(current_module, Interface, "Vlan", "vlan",
+#                  "interface/general", "tag for setting VLAN id",
+#                  get_roles=all_roles, set_roles=tech_roles)
index 55da25d..a5f42d9 100644 (file)
 # Thierry Parmentelat - INRIA
 # $Id$
+# $URL$
 
 from PLC.Nodes import Node
 from PLC.Interfaces import Interface
 from PLC.Slices import Slice
-from PLC.Ilinks import Ilink
+from PLC.Slices import Slice
+from PLC.Sites import Site
+from PLC.Persons import Person
+#from PLC.Ilinks import Ilink
 
-from PLC.Accessors.Factory import define_accessors, all_roles, tech_roles
+from PLC.Accessors.Factory import define_accessors, admin_roles, all_roles, tech_roles
 
 import sys
 current_module = sys.modules[__name__]
 
 # NOTE.
-# most of these tag types are defined in MyPLC/db-config, so any details here in the 
-# description/category area is unlikely to make it into the database
+# The 'Get' and 'Set' accessors defined here automagically create the corresponding TagType in the database
+# for safety, some crucial tags are forced to be created at plc startup time, through the db-config.d mechanism
+# You might wish to keep this roughly in sync with db-config.d/011-standard_tags
 #
 
-# node architecture 
-define_accessors(current_module, Node, "Arch", 'arch',  'node/config', 'architecture name', 
-                 get_roles=all_roles, set_roles=tech_roles )
-# distribution to be deployed
-define_accessors(current_module, Node, "Pldistro", 'pldistro',  'node/config', 'PlanetLab distribution', 
-                 get_roles=all_roles, set_roles=['admin'] )
-# node deployment (alpha, beta, ...)
-define_accessors(current_module, Node, "Deployment", "deployment", "node/operation", 
-                 'deployment flavour, typically "alpha", "beta", or "production"',
-                 get_roles=all_roles, set_roles=['admin'])
+# 
+### system slices - at least planetflow - still rely on 'vref'
+# 
+
+# These following accessors are mostly of interest for implementing the 
+# GetSliceFamily method, that takes into account the vref attribute, 
+# as well as the 3 attributes below, and the PLC_FLAVOUR config category
+
+### slice vref 
+define_accessors(current_module, Slice, "Vref", "vref", 
+                 "slice/config", "vserver reference image name",
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
+
+# xxx the accessor factory should enforce the category and descriptions provided here.
+# and BTW the tag should be created right away as far as possible, or at least when a Get is issued
+# also note that the two 'arch' instances use here will end in the same unique TagType object,
+# so you should make sure to use the same category/description for both 
+define_accessors(current_module, Slice, "Arch", "arch", 
+                 "node/slice/config", "node arch or slivers arch",
+                 get_roles=all_roles, set_roles=["user"], expose_in_api=True)
+define_accessors(current_module, Slice, "Pldistro", "pldistro",
+                 "node/slice/config", "PlanetLab distribution to use for node or slivers", 
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
+define_accessors(current_module, Slice, "Fcdistro", "fcdistro",
+                 "node/slice/config", "Fedora or CentOS distribution to use for node or slivers", 
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
 
-# interface alias
-define_accessors(current_module, Interface, "Ifname", "ifname", "interface/config", "linux name",
+# Ditto for the GetNodeFlavour method
+define_accessors(current_module, Node, "Arch", "arch",  
+                 "node/slice/config", "node arch or slivers arch",
+                 get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
+define_accessors(current_module, Node, "Pldistro", "pldistro",
+                 "node/slice/config", "PlanetLab distribution to use for node or slivers", 
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
+define_accessors(current_module, Node, "Fcdistro", "fcdistro",
+                 "node/slice/config", "Fedora or CentOS distribution to use for node or slivers", 
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
+# node deployment (alpha, beta, ...)
+define_accessors(current_module, Node, "Deployment", "deployment",
+                 "node/operation", 'typically "alpha", "beta", or "production"',
+                 get_roles=all_roles, set_roles=["admin"], expose_in_api=True)
+# extension
+define_accessors(current_module, Node, "Extensions", "extensions", 
+                 "node/config", "space-separated list of extensions to install",
+                 get_roles=all_roles, set_roles=["admin"],expose_in_api=True)
+# test nodes perform their installation from an uncompressed bootstrapfs
+define_accessors(current_module, Node, "PlainBootstrapfs", "plain-bootstrapfs", 
+                 "node/config", "use uncompressed bootstrapfs when set",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Driver", "driver", "interface/config", "driver name",
+
+# the tags considered when creating a boot CD
+define_accessors(current_module, Node, "Serial", "serial",
+                 "node/bootcd", "serial to use when creating the boot CD -- see GetBootMedium")
+define_accessors(current_module, Node, "Cramfs", "cramfs",
+                 "node/bootcd", "boot CD to use cramfs if set -- see GetBootMedium")
+define_accessors(current_module, Node, "Kvariant", "kvariant",
+                 "node/bootcd", "the variant to use for creating the boot CD -- see GetBootMedium")
+define_accessors(current_module, Node, "Kargs", "kargs",
+                 "node/bootcd", "extra args to pass the kernel on the Boot CD -- see GetBootMedium")
+define_accessors(current_module, Node, "NoHangcheck", "no-hangcheck",
+                 "node/bootcd", "disable hangcheck on the boot CD if set -- see GetBootMedium")
+
+# interface 
+# xxx - don't expose yet in api interface and slices dont know how to use that yet
+define_accessors(current_module, Interface, "Ifname", "ifname", 
+                 "interface/config", "linux name",
+                 get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
+define_accessors(current_module, Interface, "Driver", "driver", 
+                 "interface/config", "driver name",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Alias", "alias", "interface/config", "interface alias",
+define_accessors(current_module, Interface, "Alias", "alias", 
+                 "interface/config", "interface alias",
                  get_roles=all_roles, set_roles=tech_roles)
+define_accessors(current_module, Interface, "Backdoor", "backdoor",
+                 "interface/hidden", "For testing new settings",
+                 get_roles=all_roles, set_roles=admin_roles)
+
index 1b8a7ce..3a021f2 100644 (file)
@@ -1,10 +1,11 @@
 # Thierry Parmentelat - INRIA
-# $Id: Accessors_standard.py 10295 2008-08-19 21:49:06Z thierry $
+# $Id$
+# $URL$
 
 from PLC.Nodes import Node
 from PLC.Interfaces import Interface
 from PLC.Slices import Slice
-from PLC.Ilinks import Ilink
+#from PLC.Ilinks import Ilink
 
 from PLC.Accessors.Factory import define_accessors, all_roles, tech_roles
 
@@ -12,35 +13,48 @@ import sys
 current_module = sys.modules[__name__]
 
 #### Wireless
-
-define_accessors(current_module, Interface, "WifiMode", "wifi_mode", "interface/wifi", "Wifi operation mode - see iwconfig",
+define_accessors(current_module, Interface, "Mode", "mode", 
+                 "interface/wifi", "Wifi operation mode - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Essid", "essid", "interface/wifi", "Wireless essid - see iwconfig",
+define_accessors(current_module, Interface, "Essid", "essid",
+                 "interface/wifi", "Wireless essid - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Nw", "nw", "interface/wifi", "Wireless nw - see iwconfig",
+define_accessors(current_module, Interface, "Nw", "nw",
+                 "interface/wifi", "Wireless nw - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Freq", "freq", "interface/wifi", "Wireless freq - see iwconfig",
+define_accessors(current_module, Interface, "Freq", "freq", 
+                 "interface/wifi", "Wireless freq - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Channel", "channel", "interface/wifi", "Wireless channel - see iwconfig",
+define_accessors(current_module, Interface, "Channel", "channel", 
+                 "interface/wifi", "Wireless channel - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Sens", "sens", "interface/wifi", "Wireless sens - see iwconfig",
+define_accessors(current_module, Interface, "Sens", "sens", 
+                 "interface/wifi", "Wireless sens - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Rate", "rate", "interface/wifi", "Wireless rate - see iwconfig",
+define_accessors(current_module, Interface, "Rate", "rate", 
+                 "interface/wifi", "Wireless rate - see iwconfig",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Key", "key", "interface/wifi", "Wireless key - see iwconfig key",
+define_accessors(current_module, Interface, "Key", "key", 
+                 "interface/wifi", "Wireless key - see iwconfig key",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Key1", "key1", "interface/wifi", "Wireless key1 - see iwconfig key[1]",
+define_accessors(current_module, Interface, "Key1", "key1", 
+                 "interface/wifi", "Wireless key1 - see iwconfig key[1]",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Key2", "key2", "interface/wifi", "Wireless key2 - see iwconfig key[2]",
+define_accessors(current_module, Interface, "Key2", "key2", 
+                 "interface/wifi", "Wireless key2 - see iwconfig key[2]",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Key3", "key3", "interface/wifi", "Wireless key3 - see iwconfig key[3]",
+define_accessors(current_module, Interface, "Key3", "key3", 
+                 "interface/wifi", "Wireless key3 - see iwconfig key[3]",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Key4", "key4", "interface/wifi", "Wireless key4 - see iwconfig key[4]",
+define_accessors(current_module, Interface, "Key4", "key4", 
+                 "interface/wifi", "Wireless key4 - see iwconfig key[4]",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "SecurityMode", "securitymode", "interface/wifi", "Wireless securitymode - see iwconfig enc",
+define_accessors(current_module, Interface, "SecurityMode", "securitymode", 
+                 "interface/wifi", "Wireless securitymode - see iwconfig enc",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Iwconfig", "iwconfig", "interface/wifi", "Wireless iwconfig - see ifup-wireless",
+define_accessors(current_module, Interface, "Iwconfig", "iwconfig", 
+                 "interface/wifi", "Wireless iwconfig - see ifup-wireless",
                  get_roles=all_roles, set_roles=tech_roles)
-define_accessors(current_module, Interface, "Iwpriv", "iwpriv", "interface/wifi", "Wireless iwpriv - see ifup-wireless",
+define_accessors(current_module, Interface, "Iwpriv", "iwpriv", 
+                 "interface/wifi", "Wireless iwpriv - see ifup-wireless",
                  get_roles=all_roles, set_roles=tech_roles)
-
index 6b13e0a..abd34fd 100644 (file)
@@ -1,20 +1,26 @@
 # Thierry Parmentelat - INRIA
 # $Id$
+# $URL$
 
 from types import NoneType
 
-from PLC.Method import Method
+from PLC.Faults import *
+
 from PLC.Auth import Auth
 from PLC.Parameter import Parameter, Mixed
-
-from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Accessor import Accessor, AccessorSingleton
 
 from PLC.Nodes import Nodes, Node
 from PLC.NodeTags import NodeTags, NodeTag
 from PLC.Interfaces import Interfaces, Interface
-from PLC.InterfaceSettings import InterfaceSettings, InterfaceSetting
+from PLC.InterfaceTags import InterfaceTags, InterfaceTag
 from PLC.Slices import Slices, Slice
-from PLC.SliceAttributes import SliceAttributes, SliceAttribute
+from PLC.SliceTags import SliceTags, SliceTag
+from PLC.Sites import Sites, Site
+from PLC.SiteTags import SiteTags, SiteTag
+from PLC.Persons import Persons, Person
+from PLC.PersonTags import PersonTags, PersonTag
 
 # this is another story..
 #from PLC.Ilinks import Ilink
@@ -24,60 +30,76 @@ from PLC.TagTypes import TagTypes, TagType
 # known classes : { class -> secondary_key }
 taggable_classes = { Node : {'table_class' : Nodes, 
                              'joins_class' : NodeTags, 'join_class' : NodeTag,
-                             'value_key': 'tagvalue', 'secondary_key': 'hostname'},
+                             'secondary_key': 'hostname'},
                      Interface : {'table_class' : Interfaces, 
-                                  'joins_class': InterfaceSettings, 'join_class': InterfaceSetting,
-                                  'value_key' : 'value' }, 
+                                  'joins_class': InterfaceTags, 'join_class': InterfaceTag,
+                                  'secondary_key' : 'ip'},
                      Slice: {'table_class' : Slices, 
-                             'joins_class': SliceAttributes, 'join_class': SliceAttribute,
-                             'value_key' : 'value', 'secondary_key':'login_base'},
+                             'joins_class': SliceTags, 'join_class': SliceTag,
+                             'secondary_key':'name'},
+                     Site: {'table_class' : Sites, 
+                             'joins_class': SiteTags, 'join_class': SiteTag,
+                             'secondary_key':'login_base'},
+                     Person: {'table_class' : Persons, 
+                             'joins_class': PersonTags, 'join_class': PersonTag,
+                             'secondary_key':'email'},
 #                     Ilink : xxx
                      }
 
 # xxx probably defined someplace else
+admin_roles = ['admin']
 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
 tech_roles = [ 'admin', 'pi', 'tech' ]
 
+#
 # generates 2 method classes:
-# Get<classname><methodsuffix> (auth, id_or_name) -> tagvalue or None
-# Set<classname><methodsuffix> (auth, id_or_name, tagvalue) -> None
-# tagvalue is always a string, no cast nor typecheck for now
+# Get<classname><methodsuffix> (auth, id_or_name) -> value or None
+# Set<classname><methodsuffix> (auth, id_or_name, value) -> None
+# value is always a string, no cast nor typecheck for now
+#
+# The expose_in_api flag tells whether this tag may be handled 
+#   through the Add/Get/Update methods as a native field
 #
 # note: tag_min_role_id gets attached to the tagtype instance, 
 # while get_roles and set_roles get attached to the created methods
+# this might need a cleanup
 # 
-# returns a tuple (get_method, set_method)
-# See Accessors* for examples
+# in addition a convenience method like e.g. LocateNodeArch is defined 
+# in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
 
-def define_accessors (module, objclass, methodsuffix, 
-                      tagname, category, description, tag_min_role_id=10,
-                      get_roles=['admin'], set_roles=['admin']):
+def define_accessors (module, objclass, methodsuffix, tagname, 
+                      category, description, 
+                      get_roles=['admin'], set_roles=['admin'], 
+                      tag_min_role_id=10, expose_in_api = False):
     
     if objclass not in taggable_classes:
         try:
             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
         except:
             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
+
+    # side-effect on, say, Node.tags, if required
+    if expose_in_api:
+        getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
+
     classname=objclass.__name__
     get_name = "Get" + classname + methodsuffix
     set_name = "Set" + classname + methodsuffix
+    locator_name = "Locate" + classname + methodsuffix
 
-    # create method objects under PLC.Method.Method
+    # accessor method objects under PLC.Method.Method
     get_class = type (get_name, (Method,),
                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
     set_class = type (set_name, (Method,),
                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
+
     # accepts 
     get_accepts = [ Auth () ]
     primary_key=objclass.primary_key
-    try:
-        secondary_key = taggable_classes[objclass]['secondary_key']
-        get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
-    except:
-        secondary_key = None
-        get_accepts += [ objclass.fields[primary_key] ]
+    secondary_key = taggable_classes[objclass]['secondary_key']
+    get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
     # for set, idem set of arguments + one additional arg, the new value
     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
 
@@ -99,57 +121,56 @@ def define_accessors (module, objclass, methodsuffix,
     table_class = taggable_classes[objclass]['table_class']
     joins_class = taggable_classes[objclass]['joins_class']
     join_class = taggable_classes[objclass]['join_class']
-    value_key = taggable_classes[objclass]['value_key']
+
+    # locate the tag and create it if needed
+    # this method is attached to the Accessor class
+    def locate_or_create_tag (self):
+        return self.locate_or_create_tag (tagname=tagname,
+                                          category=category,
+                                          description=description,
+                                          min_role_id=tag_min_role_id)
+
+    # attach it to the Accessor class
+    setattr(Accessor,locator_name,locate_or_create_tag)
 
     # body of the get method
     def get_call (self, auth, id_or_name):
-        # search the tagtype - xxx - might need a cache
-        tag_types = TagTypes (self.api, {'tagname': tagname})
-        if not tag_types:
-            return None
-        tag_type_id = tag_types[0]['tag_type_id']
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type_id = locator(AccessorSingleton(self.api))
+
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
         else:
             filter[secondary_key]=id_or_name
-        joins = joins_class (self.api,filter,[value_key])
+        joins = joins_class (self.api,filter,['value'])
         if not joins:
             # xxx - we return None even if id_or_name is not valid 
             return None
         else:
-            return joins[0][value_key]
+            return joins[0]['value']
 
     # attach it
     setattr (get_class,"call",get_call)
 
     # body of the set method 
-    def set_call (self, auth, id_or_name, tagvalue):
+    def set_call (self, auth, id_or_name, value):
         # locate the object
         if isinstance (id_or_name, int):
             filter={primary_key:id_or_name}
         else:
             filter={secondary_key:id_or_name}
-        objs = table_class(self.api, filter,[primary_key])
+        objs = table_class(self.api, filter,[primary_key,secondary_key])
         if not objs:
             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
         primary_id = objs[0][primary_key]
                            
-        # search tag type & create if needed
-        tag_types = TagTypes (self.api, {'tagname':tagname})
-        if tag_types:
-            tag_type = tag_types[0]
-        else:
-            # not found: create it
-            tag_type_fields = {'tagname':tagname, 
-                               'category' :  category,
-                               'description' : description,
-                               'min_role_id': tag_min_role_id}
-            tag_type = TagType (self.api, tag_type_fields)
-            tag_type.sync()
-        tag_type_id = tag_type['tag_type_id']
-
-        # locate the join object (e.g. NodeTag, SliceAttribute or InterfaceSetting)
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type_id = locator(AccessorSingleton(self.api))
+
+        # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
@@ -157,21 +178,29 @@ def define_accessors (module, objclass, methodsuffix,
             filter[secondary_key]=id_or_name
         joins = joins_class (self.api,filter)
         # setting to something non void
-        if tagvalue is not None:
+        if value is not None:
             if not joins:
                 join = join_class (self.api)
                 join['tag_type_id']=tag_type_id
                 join[primary_key]=primary_id
-                join[value_key]=tagvalue
+                join['value']=value
                 join.sync()
             else:
-                joins[0][value_key]=tagvalue
+                joins[0]['value']=value
                 joins[0].sync()
         # providing an empty value means clean up
         else:
             if joins:
                 join=joins[0]
                 join.delete()
+        # log it
+        self.event_objects= { objclass.__name__ : [primary_id] }
+        self.message=objclass.__name__
+        if secondary_key in objs[0]:
+            self.message += " %s "%objs[0][secondary_key]
+        else:
+            self.message += " %d "%objs[0][primary_key]
+        self.message += "updated"
 
     # attach it
     setattr (set_class,"call",set_call)
index dd6afff..227b974 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
index 36c0d1d..ce5fdc7 100644 (file)
@@ -1,3 +1,6 @@
+# $Id$
+# $URL$
+
 from PLC.Faults import *
 from PLC.Parameter import Parameter
 from PLC.Table import Row, Table
index e4a5a11..b11e855 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import crypt
@@ -21,6 +22,23 @@ from PLC.Sessions import Session, Sessions
 from PLC.Peers import Peer, Peers
 from PLC.Boot import notify_owners
 
+def map_auth(auth):
+    if auth['AuthMethod'] == "session":
+        expected = SessionAuth()
+    elif auth['AuthMethod'] == "password" or \
+         auth['AuthMethod'] == "capability":
+        expected = PasswordAuth()
+    elif auth['AuthMethod'] == "gpg":
+        expected = GPGAuth()
+    elif auth['AuthMethod'] == "hmac" or \
+         auth['AuthMethod'] == "hmac_dummybox":
+        expected = BootAuth()
+    elif auth['AuthMethod'] == "anonymous":
+        expected = AnonymousAuth()
+    else:
+        raise PLCInvalidArgument("must be 'session', 'password', 'gpg', 'hmac', 'hmac_dummybox', or 'anonymous'", "AuthMethod")
+    return expected
+
 class Auth(Parameter):
     """
     Base class for all API authentication methods, as well as a class
@@ -38,19 +56,7 @@ class Auth(Parameter):
         # mandatory fields were present.
         assert 'AuthMethod' in auth
 
-        if auth['AuthMethod'] == "session":
-            expected = SessionAuth()
-        elif auth['AuthMethod'] == "password" or \
-             auth['AuthMethod'] == "capability":
-            expected = PasswordAuth()
-        elif auth['AuthMethod'] == "gpg":
-            expected = GPGAuth()
-        elif auth['AuthMethod'] == "hmac":
-            expected = BootAuth()
-        elif auth['AuthMethod'] == "anonymous":
-            expected = AnonymousAuth()
-        else:
-            raise PLCInvalidArgument("must be 'session', 'password', 'gpg', 'hmac', or 'anonymous'", "AuthMethod")
+        expected = map_auth(auth)
 
         # Re-check using the specified authentication method
         method.type_check("auth", auth, expected, (auth,) + args)
@@ -250,7 +256,8 @@ class BootAuth(Auth):
 
             # We encode in UTF-8 before calculating the HMAC, which is
             # an 8-bit algorithm.
-            digest = hmac.new(key, msg.encode('utf-8'), sha).hexdigest()
+            # python 2.6 insists on receiving a 'str' as opposed to a 'unicode'
+            digest = hmac.new(str(key), msg.encode('utf-8'), sha).hexdigest()
 
             if digest != auth['value']:
                 raise PLCAuthenticationFailure, "Call could not be authenticated"
index 221fbb2..5926bbc 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2007 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index f1bfcf9..952a2d5 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 63474e3..641908f 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 6a4a7e2..23b4df2 100644 (file)
@@ -7,6 +7,7 @@
 # Copyright (C) 2004-2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os
index b8dac85..2cdac8c 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 import sys
 import syslog
index 1c6c4bb..3bd4cc9 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index c01f589..a237657 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 3c9328e..3309cb6 100644 (file)
@@ -6,6 +6,7 @@
 #
 # Copyright (C) 2004-2006 The Trustees of Princeton University
 # $Id$
+# $URL$
 #
 
 import xmlrpclib
index 195c4e6..520c6ff 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from types import StringTypes
 try:
     set
@@ -18,8 +20,9 @@ class Filter(Parameter, dict):
     as well as sorting and clipping.
 
 
-    fields should be a dictionary of field names and types
-    Only filters on non-sequence type fields are supported.
+    fields should be a dictionary of field names and types.
+    As of PLCAPI-4.3-26, we provide support for filtering on
+    sequence types as well, with the special '&' and '|' modifiers.
     example : fields = {'node_id': Parameter(int, "Node identifier"),
                         'hostname': Parameter(int, "Fully qualified hostname", max = 255),
                         ...}
@@ -34,6 +37,15 @@ class Filter(Parameter, dict):
 
     Special features:
 
+    * a field starting with '&' or '|' should refer to a sequence type
+      the semantic is then that the object value (expected to be a list)
+      should contain all (&) or any (|) value specified in the corresponding
+      filter value. See other examples below.
+    example : filter = { '|role_ids' : [ 20, 40 ] }
+    example : filter = { '|roles' : ['tech', 'pi'] }
+    example : filter = { '&roles' : ['admin', 'tech'] }
+    example : filter = { '&roles' : 'tech' }
+
     * a field starting with the ~ character means negation.
     example :  filter = { '~peer_id' : None }
 
@@ -53,7 +65,7 @@ class Filter(Parameter, dict):
       SQL wildcard character.
     example :  filter = { 'hostname' : '*.jp' } 
 
-    * fields starting with - are special and relate to row selection, i.e. sorting and clipping
+    * the filter's keys starting with '-' are special and relate to sorting and clipping
     * '-SORT' : a field name, or an ordered list of field names that are used for sorting
       these fields may start with + (default) or - for denoting increasing or decreasing order
     example : filter = { '-SORT' : [ '+node_id', '-hostname' ] }
@@ -61,9 +73,19 @@ class Filter(Parameter, dict):
     * '-LIMIT' : the amount of rows to be returned 
     example : filter = { '-OFFSET' : 100, '-LIMIT':25}
 
-    A realistic example would read
-    GetNodes ( { 'hostname' : '*.edu' , '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 } )
-    and that would return nodes matching '*.edu' in alphabetical order from 31th to 55th
+    Here are a few realistic examples
+
+    GetNodes ( { 'node_type' : 'regular' , 'hostname' : '*.edu' , '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 } )
+      would return regular (usual) nodes matching '*.edu' in alphabetical order from 31th to 55th
+
+    GetPersons ( { '|role_ids' : [ 20 , 40] } )
+      would return all persons that have either pi (20) or tech (40) roles
+
+    GetPersons ( { '&role_ids' : 10 } )
+    GetPersons ( { '&role_ids' : 10 } )
+    GetPersons ( { '|role_ids' : [ 10 ] } )
+    GetPersons ( { '|role_ids' : [ 10 ] } )
+      all 4 forms are equivalent and would return all admin users in the system
     """
 
     def __init__(self, fields = {}, filter = {}, doc = "Attribute filter"):
@@ -73,28 +95,12 @@ class Filter(Parameter, dict):
         # Declare ourselves as a type of parameter that can take
         # either a value or a list of values for each of the specified
         # fields.
-        self.fields = {}
-
-        for field, expected in fields.iteritems():
-            # Cannot filter on sequences
-            if python_type(expected) in (list, tuple, set):
-                continue
-            
-            # Accept either a value or a list of values of the specified type
-            self.fields[field] = Mixed(expected, [expected])
+        self.fields = dict ( [ ( field, Mixed (expected, [expected])) 
+                                 for (field,expected) in fields.iteritems() ] )
 
         # Null filter means no filter
         Parameter.__init__(self, self.fields, doc = doc, nullok = True)
 
-    # this code is not used anymore
-    # at some point the select in the DB for event objects was done on
-    # the events table directly, that is stored as a timestamp, thus comparisons
-    # needed to be done based on SQL timestamps as well
-    def unix2timestamp (self,unix):
-       s = time.gmtime(unix)
-       return "TIMESTAMP'%04d-%02d-%02d %02d:%02d:%02d'" % (s.tm_year,s.tm_mon,s.tm_mday,
-                                                            s.tm_hour,s.tm_min,s.tm_sec)
-
     def sql(self, api, join_with = "AND"):
         """
         Returns a SQL conditional that represents this filter.
@@ -120,27 +126,50 @@ class Filter(Parameter, dict):
                       '<' : False, '>' : False,
                       '[' : False, ']' : False,
                        '-' : False,
+                       '&' : False, '|' : False,
                       }
-
-           for char in modifiers.keys():
-               if field[0] == char:
-                   modifiers[char]=True;
-                   field = field[1:]
-                   break
+            def check_modifiers(field):
+                if field[0] in modifiers.keys():
+                    modifiers[field[0]] = True
+                    field = field[1:]
+                    return check_modifiers(field)
+                return field
+            field = check_modifiers(field)
 
             # filter on fields
             if not modifiers['-']:
                 if field not in self.fields:
                     raise PLCInvalidArgument, "Invalid filter field '%s'" % field
 
+                # handling array fileds always as compound values
+                if modifiers['&'] or modifiers['|']:
+                    if not isinstance(value, (list, tuple, set)):
+                        value = [value,]
+
                 if isinstance(value, (list, tuple, set)):
-                    # Turn empty list into (NULL) instead of invalid ()
+                    # handling filters like '~slice_id':[]
+                    # this should return true, as it's the opposite of 'slice_id':[] which is false
+                    # prior to this fix, 'slice_id':[] would have returned ``slice_id IN (NULL) '' which is unknown 
+                    # so it worked by coincidence, but the negation '~slice_ids':[] would return false too
                     if not value:
-                        value = [None]
-
-                    operator = "IN"
-                    value = map(str, map(api.db.quote, value))
-                    value = "(%s)" % ", ".join(value)
+                        if modifiers['&'] or modifiers['|']:
+                            operator = "="
+                            value = "'{}'"
+                        else:
+                            field=""
+                            operator=""
+                            value = "FALSE"
+                    else:
+                        value = map(str, map(api.db.quote, value))
+                        if modifiers['&']:
+                            operator = "@>"
+                            value = "ARRAY[%s]" % ", ".join(value)
+                        elif modifiers['|']:
+                            operator = "&&"
+                            value = "ARRAY[%s]" % ", ".join(value)
+                        else:
+                            operator = "IN"
+                            value = "(%s)" % ", ".join(value)
                 else:
                     if value is None:
                         operator = "IS"
@@ -148,7 +177,12 @@ class Filter(Parameter, dict):
                     elif isinstance(value, StringTypes) and \
                             (value.find("*") > -1 or value.find("%") > -1):
                         operator = "LIKE"
-                        value = str(api.db.quote(value.replace("*", "%")))
+                        # insert *** in pattern instead of either * or %
+                        # we dont use % as requests are likely to %-expansion later on
+                        # actual replacement to % done in PostgreSQL.py
+                        value = value.replace ('*','***')
+                        value = value.replace ('%','***')
+                        value = str(api.db.quote(value))
                     else:
                         operator = "="
                         if modifiers['<']:
@@ -159,8 +193,8 @@ class Filter(Parameter, dict):
                             operator='<='
                         if modifiers[']']:
                             operator='>='
-                        else:
-                            value = str(api.db.quote(value))
+
+                        value = str(api.db.quote(value))
  
                 clause = "%s %s %s" % (field, operator, value)
 
index 2499bab..97187e5 100644 (file)
@@ -8,6 +8,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os
index 28c95e2..5015236 100644 (file)
@@ -1,8 +1,8 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
-# $Revision: 9423 $
-#
 from PLC.Faults import *
 from PLC.Parameter import Parameter
 from PLC.Filter import Filter
index 028293b..de9cd75 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Functions for interacting with the initscripts table in the database
 #
diff --git a/PLC/InterfaceSettings.py b/PLC/InterfaceSettings.py
deleted file mode 100644 (file)
index c7b0af8..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Thierry Parmentelat - INRIA
-#
-# $Revision$
-#
-from PLC.Faults import *
-from PLC.Parameter import Parameter
-from PLC.Filter import Filter
-from PLC.Table import Row, Table
-from PLC.TagTypes import TagType, TagTypes
-
-class InterfaceSetting(Row):
-    """
-    Representation of a row in the interface_setting.
-    To use, instantiate with a dict of values.
-    """
-
-    table_name = 'interface_setting'
-    primary_key = 'interface_setting_id'
-    fields = {
-        'interface_setting_id': Parameter(int, "Interface setting identifier"),
-        'interface_id': Parameter(int, "Interface identifier"),
-        'tag_type_id': TagType.fields['tag_type_id'],
-        'tagname': TagType.fields['tagname'],
-        'description': TagType.fields['description'],
-        'category': TagType.fields['category'],
-        'min_role_id': TagType.fields['min_role_id'],
-        'value': Parameter(str, "Interface setting value"),
-       ### relations
-       
-        }
-
-class InterfaceSettings(Table):
-    """
-    Representation of row(s) from the interface_setting table in the
-    database.
-    """
-
-    def __init__(self, api, interface_setting_filter = None, columns = None):
-        Table.__init__(self, api, InterfaceSetting, columns)
-
-        sql = "SELECT %s FROM view_interface_settings WHERE True" % \
-              ", ".join(self.columns)
-
-        if interface_setting_filter is not None:
-            if isinstance(interface_setting_filter, (list, tuple, set)):
-                interface_setting_filter = Filter(InterfaceSetting.fields, {'interface_setting_id': interface_setting_filter})
-            elif isinstance(interface_setting_filter, dict):
-                interface_setting_filter = Filter(InterfaceSetting.fields, interface_setting_filter)
-            elif isinstance(interface_setting_filter, int):
-                interface_setting_filter = Filter(InterfaceSetting.fields, {'interface_setting_id': [interface_setting_filter]})
-            else:
-                raise PLCInvalidArgument, "Wrong interface setting filter %r"%interface_setting_filter
-            sql += " AND (%s) %s" % interface_setting_filter.sql(api)
-
-
-        self.selectall(sql)
diff --git a/PLC/InterfaceTags.py b/PLC/InterfaceTags.py
new file mode 100644 (file)
index 0000000..1993256
--- /dev/null
@@ -0,0 +1,61 @@
+# $Id$
+# $URL$
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision$
+#
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.TagTypes import TagType, TagTypes
+from PLC.Interfaces import Interface
+
+class InterfaceTag(Row):
+    """
+    Representation of a row in the interface_tag.
+    To use, instantiate with a dict of values.
+    """
+
+    table_name = 'interface_tag'
+    primary_key = 'interface_tag_id'
+    fields = {
+        'interface_tag_id': Parameter(int, "Interface setting identifier"),
+        'interface_id': Interface.fields['interface_id'],
+        'ip': Interface.fields['ip'],
+        'tag_type_id': TagType.fields['tag_type_id'],
+        'tagname': TagType.fields['tagname'],
+        'description': TagType.fields['description'],
+        'category': TagType.fields['category'],
+        'min_role_id': TagType.fields['min_role_id'],
+        'value': Parameter(str, "Interface setting value"),
+       ### relations
+       
+        }
+
+class InterfaceTags(Table):
+    """
+    Representation of row(s) from the interface_tag table in the
+    database.
+    """
+
+    def __init__(self, api, interface_tag_filter = None, columns = None):
+        Table.__init__(self, api, InterfaceTag, columns)
+
+        sql = "SELECT %s FROM view_interface_tags WHERE True" % \
+              ", ".join(self.columns)
+
+        if interface_tag_filter is not None:
+            if isinstance(interface_tag_filter, (list, tuple, set)):
+                interface_tag_filter = Filter(InterfaceTag.fields, {'interface_tag_id': interface_tag_filter})
+            elif isinstance(interface_tag_filter, dict):
+                interface_tag_filter = Filter(InterfaceTag.fields, interface_tag_filter)
+            elif isinstance(interface_tag_filter, int):
+                interface_tag_filter = Filter(InterfaceTag.fields, {'interface_tag_id': [interface_tag_filter]})
+            else:
+                raise PLCInvalidArgument, "Wrong interface setting filter %r"%interface_tag_filter
+            sql += " AND (%s) %s" % interface_tag_filter.sql(api)
+
+
+        self.selectall(sql)
index ca1fe7e..bd933cd 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
@@ -48,7 +49,7 @@ class Interface(Row):
 
     table_name = 'interfaces'
     primary_key = 'interface_id'
-    join_tables = ['interface_setting']
+    join_tables = ['interface_tag']
     fields = {
         'interface_id': Parameter(int, "Node interface identifier"),
         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
@@ -65,9 +66,12 @@ class Interface(Row):
         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
         'node_id': Parameter(int, "Node associated with this interface"),
         'is_primary': Parameter(bool, "Is the primary interface for this node"),
-        'interface_setting_ids' : Parameter([int], "List of interface settings"),
+        'interface_tag_ids' : Parameter([int], "List of interface settings"),
         }
 
+    view_tags_name = "view_interface_tags"
+    tags = {}
+
     def validate_method(self, method):
         network_methods = [row['method'] for row in NetworkMethods(self.api)]
         if method not in network_methods:
@@ -214,18 +218,34 @@ class Interfaces(Table):
     def __init__(self, api, interface_filter = None, columns = None):
         Table.__init__(self, api, Interface, columns)
 
-        sql = "SELECT %s FROM view_interfaces WHERE True" % \
-              ", ".join(self.columns)
+        # the view that we're selecting upon: start with view_nodes
+        view = "view_interfaces"
+        # as many left joins as requested tags
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
+                                                Interface.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE True" % \
+            (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if interface_filter is not None:
             if isinstance(interface_filter, (list, tuple, set)):
-                interface_filter = Filter(Interface.fields, {'interface_id': interface_filter})
+                # Separate the list into integers and strings
+                ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
+                strs = filter(lambda x: isinstance(x, StringTypes), interface_filter)
+                interface_filter = Filter(Interface.fields, {'interface_id': ints, 'ip': strs})
+                sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
             elif isinstance(interface_filter, dict):
-                interface_filter = Filter(Interface.fields, interface_filter)
+                allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
+                interface_filter = Filter(allowed_fields, interface_filter)
+                sql += " AND (%s) %s" % interface_filter.sql(api)
             elif isinstance(interface_filter, int):
                 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
+                sql += " AND (%s) %s" % interface_filter.sql(api)
+            elif isinstance (interface_filter, StringTypes):
+                interface_filter = Filter(Interface.fields, {'ip':[interface_filter]})
+                sql += " AND (%s) %s" % interface_filter.sql(api, "AND")
             else:
-                raise PLCInvalidArgument, "Wrong node network filter %r"%interface_filter
-            sql += " AND (%s) %s" % interface_filter.sql(api)
+                raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter
 
         self.selectall(sql)
index 34df019..e491b45 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 91ce92a..d8e54e6 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import re
 
 from PLC.Faults import *
diff --git a/PLC/Legacy/AddNodeNetwork.py b/PLC/Legacy/AddNodeNetwork.py
new file mode 100644 (file)
index 0000000..7fdfd79
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworks import v42rename, v43rename
+from PLC.Methods.AddInterface import AddInterface
+class AddNodeNetwork(AddInterface):
+    """ Legacy version of AddInterface. """
+    status = "deprecated"
+    def call(self, auth, node_id_or_hostname, interface_fields):
+       node_id_or_hostname=patch(node_id_or_hostname,v42rename)
+       interface_fields=patch(interface_fields,v42rename)
+       result=AddInterface.call(self,auth,node_id_or_hostname,interface_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/AddNodeNetworkSetting.py b/PLC/Legacy/AddNodeNetworkSetting.py
new file mode 100644 (file)
index 0000000..d2421c9
--- /dev/null
@@ -0,0 +1,14 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettings import v42rename, v43rename
+from PLC.Methods.AddInterfaceTag import AddInterfaceTag
+class AddNodeNetworkSetting(AddInterfaceTag):
+    """ Legacy version of AddInterfaceTag. """
+    status = "deprecated"
+    def call(self, auth, interface_id, tag_type_id_or_name, value):
+       interface_id=patch(interface_id,v42rename)
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       value=patch(value,v42rename)
+       result=AddInterfaceTag.call(self,auth,interface_id,tag_type_id_or_name,value)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/AddNodeNetworkSettingType.py b/PLC/Legacy/AddNodeNetworkSettingType.py
new file mode 100644 (file)
index 0000000..cdfc0d1
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettingTypes import v42rename, v43rename
+from PLC.Methods.AddTagType import AddTagType
+class AddNodeNetworkSettingType(AddTagType):
+    """ Legacy version of AddTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_fields):
+       tag_type_fields=patch(tag_type_fields,v42rename)
+       result=AddTagType.call(self,auth,tag_type_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/AddSliceAttribute.py b/PLC/Legacy/AddSliceAttribute.py
new file mode 100644 (file)
index 0000000..15c8412
--- /dev/null
@@ -0,0 +1,16 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributes import v42rename, v43rename
+from PLC.Methods.AddSliceTag import AddSliceTag
+class AddSliceAttribute(AddSliceTag):
+    """ Legacy version of AddSliceTag. """
+    status = "deprecated"
+    def call(self, auth, slice_id_or_name, tag_type_id_or_name, value, node_id_or_hostname = None, nodegroup_id_or_name = None):
+       slice_id_or_name=patch(slice_id_or_name,v42rename)
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       value=patch(value,v42rename)
+       node_id_or_hostname=patch(node_id_or_hostname,v42rename)
+       nodegroup_id_or_name=patch(nodegroup_id_or_name,v42rename)
+       result=AddSliceTag.call(self,auth,slice_id_or_name,tag_type_id_or_name,value,node_id_or_hostname,nodegroup_id_or_name)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/AddSliceAttributeType.py b/PLC/Legacy/AddSliceAttributeType.py
new file mode 100644 (file)
index 0000000..b75efb6
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributeTypes import v42rename, v43rename
+from PLC.Methods.AddTagType import AddTagType
+class AddSliceAttributeType(AddTagType):
+    """ Legacy version of AddTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_fields):
+       tag_type_fields=patch(tag_type_fields,v42rename)
+       result=AddTagType.call(self,auth,tag_type_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/DeleteNodeNetwork.py b/PLC/Legacy/DeleteNodeNetwork.py
new file mode 100644 (file)
index 0000000..426457e
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworks import v42rename, v43rename
+from PLC.Methods.DeleteInterface import DeleteInterface
+class DeleteNodeNetwork(DeleteInterface):
+    """ Legacy version of DeleteInterface. """
+    status = "deprecated"
+    def call(self, auth, interface_id):
+       interface_id=patch(interface_id,v42rename)
+       result=DeleteInterface.call(self,auth,interface_id)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/DeleteNodeNetworkSetting.py b/PLC/Legacy/DeleteNodeNetworkSetting.py
new file mode 100644 (file)
index 0000000..05d76ee
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettings import v42rename, v43rename
+from PLC.Methods.DeleteInterfaceTag import DeleteInterfaceTag
+class DeleteNodeNetworkSetting(DeleteInterfaceTag):
+    """ Legacy version of DeleteInterfaceTag. """
+    status = "deprecated"
+    def call(self, auth, interface_tag_id):
+       interface_tag_id=patch(interface_tag_id,v42rename)
+       result=DeleteInterfaceTag.call(self,auth,interface_tag_id)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/DeleteNodeNetworkSettingType.py b/PLC/Legacy/DeleteNodeNetworkSettingType.py
new file mode 100644 (file)
index 0000000..ef3517b
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettingTypes import v42rename, v43rename
+from PLC.Methods.DeleteTagType import DeleteTagType
+class DeleteNodeNetworkSettingType(DeleteTagType):
+    """ Legacy version of DeleteTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_id_or_name):
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       result=DeleteTagType.call(self,auth,tag_type_id_or_name)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/DeleteSliceAttribute.py b/PLC/Legacy/DeleteSliceAttribute.py
new file mode 100644 (file)
index 0000000..e562918
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributes import v42rename, v43rename
+from PLC.Methods.DeleteSliceTag import DeleteSliceTag
+class DeleteSliceAttribute(DeleteSliceTag):
+    """ Legacy version of DeleteSliceTag. """
+    status = "deprecated"
+    def call(self, auth, slice_tag_id):
+       slice_tag_id=patch(slice_tag_id,v42rename)
+       result=DeleteSliceTag.call(self,auth,slice_tag_id)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/DeleteSliceAttributeType.py b/PLC/Legacy/DeleteSliceAttributeType.py
new file mode 100644 (file)
index 0000000..dc568b6
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributeTypes import v42rename, v43rename
+from PLC.Methods.DeleteTagType import DeleteTagType
+class DeleteSliceAttributeType(DeleteTagType):
+    """ Legacy version of DeleteTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_id_or_name):
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       result=DeleteTagType.call(self,auth,tag_type_id_or_name)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/GetNodeNetworkSettingTypes.py b/PLC/Legacy/GetNodeNetworkSettingTypes.py
new file mode 100644 (file)
index 0000000..0a62eb4
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettingTypes import v42rename, v43rename
+from PLC.Methods.GetTagTypes import GetTagTypes
+class GetNodeNetworkSettingTypes(GetTagTypes):
+    """ Legacy version of GetTagTypes. """
+    status = "deprecated"
+    def call(self, auth, tag_type_filter = None, return_fields = None):
+       tag_type_filter=patch(tag_type_filter,v42rename)
+       return_fields=patch(return_fields,v42rename)
+       result=GetTagTypes.call(self,auth,tag_type_filter,return_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/GetNodeNetworkSettings.py b/PLC/Legacy/GetNodeNetworkSettings.py
new file mode 100644 (file)
index 0000000..e528bb4
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettings import v42rename, v43rename
+from PLC.Methods.GetInterfaceTags import GetInterfaceTags
+class GetNodeNetworkSettings(GetInterfaceTags):
+    """ Legacy version of GetInterfaceTags. """
+    status = "deprecated"
+    def call(self, auth, interface_tag_filter = None, return_fields = None):
+       interface_tag_filter=patch(interface_tag_filter,v42rename)
+       return_fields=patch(return_fields,v42rename)
+       result=GetInterfaceTags.call(self,auth,interface_tag_filter,return_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/GetNodeNetworks.py b/PLC/Legacy/GetNodeNetworks.py
new file mode 100644 (file)
index 0000000..a1c95dd
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworks import v42rename, v43rename
+from PLC.Methods.GetInterfaces import GetInterfaces
+class GetNodeNetworks(GetInterfaces):
+    """ Legacy version of GetInterfaces. """
+    status = "deprecated"
+    def call(self, auth, interface_filter = None, return_fields = None):
+       interface_filter=patch(interface_filter,v42rename)
+       return_fields=patch(return_fields,v42rename)
+       result=GetInterfaces.call(self,auth,interface_filter,return_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/GetSliceAttributeTypes.py b/PLC/Legacy/GetSliceAttributeTypes.py
new file mode 100644 (file)
index 0000000..2d15413
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributeTypes import v42rename, v43rename
+from PLC.Methods.GetTagTypes import GetTagTypes
+class GetSliceAttributeTypes(GetTagTypes):
+    """ Legacy version of GetTagTypes. """
+    status = "deprecated"
+    def call(self, auth, tag_type_filter = None, return_fields = None):
+       tag_type_filter=patch(tag_type_filter,v42rename)
+       return_fields=patch(return_fields,v42rename)
+       result=GetTagTypes.call(self,auth,tag_type_filter,return_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/GetSliceAttributes.py b/PLC/Legacy/GetSliceAttributes.py
new file mode 100644 (file)
index 0000000..aa0b321
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributes import v42rename, v43rename
+from PLC.Methods.GetSliceTags import GetSliceTags
+class GetSliceAttributes(GetSliceTags):
+    """ Legacy version of GetSliceTags. """
+    status = "deprecated"
+    def call(self, auth, slice_tag_filter = None, return_fields = None):
+       slice_tag_filter=patch(slice_tag_filter,v42rename)
+       return_fields=patch(return_fields,v42rename)
+       result=GetSliceTags.call(self,auth,slice_tag_filter,return_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/NodeNetworks.py b/PLC/Legacy/NodeNetworks.py
deleted file mode 100644 (file)
index 0c34ea7..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-# Thierry Parmentelat - INRIA
-# $Id$
-
-from PLC.Method import Method
-
-def import_deep(name):
-    mod = __import__(name)
-    components = name.split('.')
-    for comp in components[1:]:
-        mod = getattr(mod, comp)
-    return mod
-
-methods = [
-    "AddNodeNetwork",
-    "AddNodeNetworkSetting",
-    "DeleteNodeNetwork",
-    "DeleteNodeNetworkSetting",
-    "GetNodeNetworkSettings",
-    "GetNodeNetworks",
-    "UpdateNodeNetwork",
-    "UpdateNodeNetworkSetting",
-]
-
-# does any required renaming
-def rename (x):
-    if x=='nodenetwork_id':
-        return 'interface_id'
-    if x=='nodenetwork_ids':
-        return 'interface_ids'
-    else:
-        return x
-
-# apply rename on list (columns) or dict (filter) args
-def patch_legacy_arg (arg):
-    if isinstance(arg,list):
-        return [rename(x) for x in arg]
-    if isinstance(arg,dict):
-        return dict ( [ (rename(k),v) for (k,v) in arg.iteritems() ] )
-    return arg
-
-def legacy_method (legacyname):
-    # new method name
-    newname=legacyname.replace("NodeNetwork","Interface")
-    # locate new class
-    newclass=getattr(import_deep("PLC.Methods."+newname),newname)
-    # create class for legacy name
-    legacyclass = type(legacyname,(newclass,), 
-                       {"__doc__":"Legacy method - please use %s instead"%newname})
-    # xxx should rewrite 'call' to handle any argument using nodenetwork_id(s)
-    for internal in ["roles","accepts","returns"]:
-        setattr(legacyclass,internal,getattr(newclass,internal))
-    # turn off type checking, as introspection code fails on wrapped_call
-    setattr(legacyclass,"skip_typecheck",True)
-    # rewrite call
-    def wrapped_call (self,auth,*args, **kwds):
-        newargs=[patch_legacy_arg(x) for x in args]
-        newkwds=dict ( [ (k,patch_legacy_arg(v)) for (k,v) in kwds.iteritems() ] )
-        return getattr(newclass,"call")(self,auth,*newargs,**newkwds)
-    setattr(legacyclass,"call",wrapped_call)
-
-    return legacyclass
-
-import sys
-current_module=sys.modules[__name__]
-
-# attach
-for legacyname in methods:
-    setattr(current_module,legacyname,legacy_method(legacyname))
-
diff --git a/PLC/Legacy/Types.py b/PLC/Legacy/Types.py
deleted file mode 100644 (file)
index c7eb749..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Thierry Parmentelat - INRIA
-# $Id$
-
-from PLC.Method import Method
-
-def import_deep(name):
-    mod = __import__(name)
-    components = name.split('.')
-    for comp in components[1:]:
-        mod = getattr(mod, comp)
-    return mod
-
-map = {
-    "AddSliceAttributeType"         : "AddTagType",
-    "DeleteSliceAttributeType"      : "DeleteTagType",
-    "GetSliceAttributeTypes"        : "GetTagTypes",
-    "UpdateSliceAttributeType"      : "UpdateTagType",
-    "AddNodeNetworkSettingType"     : "AddTagType",
-    "DeleteNodeNetworkSettingType"  : "DeleteTagType",
-    "GetNodeNetworkSettingTypes"    : "GetTagTypes",
-    "UpdateNodeNetworkSettingType"  : "UpdateTagType",
-}    
-
-methods = map.keys()
-
-# does any required renaming
-def rename (x):
-    if x=='name':
-        return 'tagname'
-    else:
-        return x
-
-# apply rename on list (columns) or dict (filter) args
-def patch_legacy_arg (arg):
-    if isinstance(arg,list):
-        return [rename(x) for x in arg]
-    if isinstance(arg,dict):
-        return dict ( [ (rename(k),v) for (k,v) in arg.iteritems() ] )
-    return arg
-
-def legacy_method (legacyname, newname):
-    # locate new class
-    newclass=getattr(import_deep("PLC.Methods."+newname),newname)
-    # create class for legacy name
-    legacyclass = type(legacyname,(newclass,), 
-                       {"__doc__":"Legacy method - please use %s instead"%newname})
-    for internal in ["roles","accepts","returns"]:
-        setattr(legacyclass,internal,getattr(newclass,internal))
-    # turn off type checking, as introspection code fails on wrapped_call
-    setattr(legacyclass,"skip_typecheck",True)
-    # rewrite call
-    def wrapped_call (self,auth,*args, **kwds):
-        newargs=[patch_legacy_arg(x) for x in args]
-        newkwds=dict ( [ (k,patch_legacy_arg(v)) for (k,v) in kwds.iteritems() ] )
-        return getattr(newclass,"call")(self,auth,*newargs,**newkwds)
-    setattr(legacyclass,"call",wrapped_call)
-
-    return legacyclass
-
-import sys
-current_module=sys.modules[__name__]
-
-for (legacyname,newname) in map.iteritems():
-    setattr(current_module,legacyname,legacy_method(legacyname,newname))
-
diff --git a/PLC/Legacy/UpdateNodeNetwork.py b/PLC/Legacy/UpdateNodeNetwork.py
new file mode 100644 (file)
index 0000000..411445a
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworks import v42rename, v43rename
+from PLC.Methods.UpdateInterface import UpdateInterface
+class UpdateNodeNetwork(UpdateInterface):
+    """ Legacy version of UpdateInterface. """
+    status = "deprecated"
+    def call(self, auth, interface_id, interface_fields):
+       interface_id=patch(interface_id,v42rename)
+       interface_fields=patch(interface_fields,v42rename)
+       result=UpdateInterface.call(self,auth,interface_id,interface_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/UpdateNodeNetworkSetting.py b/PLC/Legacy/UpdateNodeNetworkSetting.py
new file mode 100644 (file)
index 0000000..0f3da52
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettings import v42rename, v43rename
+from PLC.Methods.UpdateInterfaceTag import UpdateInterfaceTag
+class UpdateNodeNetworkSetting(UpdateInterfaceTag):
+    """ Legacy version of UpdateInterfaceTag. """
+    status = "deprecated"
+    def call(self, auth, interface_tag_id, value):
+       interface_tag_id=patch(interface_tag_id,v42rename)
+       value=patch(value,v42rename)
+       result=UpdateInterfaceTag.call(self,auth,interface_tag_id,value)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/UpdateNodeNetworkSettingType.py b/PLC/Legacy/UpdateNodeNetworkSettingType.py
new file mode 100644 (file)
index 0000000..9e2cc28
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacyNodeNetworkSettingTypes import v42rename, v43rename
+from PLC.Methods.UpdateTagType import UpdateTagType
+class UpdateNodeNetworkSettingType(UpdateTagType):
+    """ Legacy version of UpdateTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_id_or_name, tag_type_fields):
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       tag_type_fields=patch(tag_type_fields,v42rename)
+       result=UpdateTagType.call(self,auth,tag_type_id_or_name,tag_type_fields)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/UpdateSliceAttribute.py b/PLC/Legacy/UpdateSliceAttribute.py
new file mode 100644 (file)
index 0000000..9508ae3
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributes import v42rename, v43rename
+from PLC.Methods.UpdateSliceTag import UpdateSliceTag
+class UpdateSliceAttribute(UpdateSliceTag):
+    """ Legacy version of UpdateSliceTag. """
+    status = "deprecated"
+    def call(self, auth, slice_tag_id, value):
+       slice_tag_id=patch(slice_tag_id,v42rename)
+       value=patch(value,v42rename)
+       result=UpdateSliceTag.call(self,auth,slice_tag_id,value)
+       return patch(result,v43rename)
diff --git a/PLC/Legacy/UpdateSliceAttributeType.py b/PLC/Legacy/UpdateSliceAttributeType.py
new file mode 100644 (file)
index 0000000..7395bfe
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+from PLC.v42Legacy import patch
+from PLC.v42LegacySliceAttributeTypes import v42rename, v43rename
+from PLC.Methods.UpdateTagType import UpdateTagType
+class UpdateSliceAttributeType(UpdateTagType):
+    """ Legacy version of UpdateTagType. """
+    status = "deprecated"
+    def call(self, auth, tag_type_id_or_name, tag_type_fields):
+       tag_type_id_or_name=patch(tag_type_id_or_name,v42rename)
+       tag_type_fields=patch(tag_type_fields,v42rename)
+       result=UpdateTagType.call(self,auth,tag_type_id_or_name,tag_type_fields)
+       return patch(result,v43rename)
index 25f8ded..64f72be 100644 (file)
@@ -1,5 +1,23 @@
-# each module to define in "methods" the set of methods that it defines
-__all__ = """
-NodeNetworks
-Types
+## Please use make index to update this file
+native_methods = """
+AddNodeNetwork
+AddNodeNetworkSetting
+AddNodeNetworkSettingType
+AddSliceAttribute
+AddSliceAttributeType
+DeleteNodeNetwork
+DeleteNodeNetworkSetting
+DeleteNodeNetworkSettingType
+DeleteSliceAttribute
+DeleteSliceAttributeType
+GetNodeNetworkSettingTypes
+GetNodeNetworkSettings
+GetNodeNetworks
+GetSliceAttributeTypes
+GetSliceAttributes
+UpdateNodeNetwork
+UpdateNodeNetworkSetting
+UpdateNodeNetworkSettingType
+UpdateSliceAttribute
+UpdateSliceAttributeType
 """.split()
index 3ec310f..4c7299e 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Parameter import Parameter
index a7f070e..ed03974 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import xmlrpclib
@@ -126,6 +127,11 @@ class Method (object):
        # Do not log system or Get calls
         #if self.name.startswith('system') or self.name.startswith('Get'):
         #    return False
+        # Do not log ReportRunlevel 
+        if self.name.startswith('system'):
+            return False
+        if self.name.startswith('ReportRunlevel'):
+            return False
 
         # Create a new event
         event = Event(self.api)
@@ -135,22 +141,28 @@ class Method (object):
         event['runtime'] = runtime
 
         # Redact passwords and sessions
-        if args and isinstance(args[0], dict):
-           # what type of auth this is
-           if args[0].has_key('AuthMethod'):
-               auth_methods = ['session', 'password', 'capability', 'gpg', 'hmac','anonymous']
-               auth_method = args[0]['AuthMethod']
-               if auth_method in auth_methods:
-                   event['auth_type'] = auth_method
-            for password in 'AuthString', 'session':
-                if args[0].has_key(password):
-                    auth = args[0].copy()
-                    auth[password] = "Removed by API"
-                    args = (auth,) + args[1:]
+        newargs = args
+        if args:
+            newargs = []
+            for arg in args:
+                if not isinstance(arg, dict):
+                    newargs.append(arg)
+                    continue
+                # what type of auth this is
+                if arg.has_key('AuthMethod'):
+                    auth_methods = ['session', 'password', 'capability', 'gpg', 'hmac','anonymous']
+                    auth_method = arg['AuthMethod']
+                    if auth_method in auth_methods:
+                        event['auth_type'] = auth_method
+                for password in 'AuthString', 'session', 'password':
+                    if arg.has_key(password):
+                        arg = arg.copy()
+                        arg[password] = "Removed by API"
+                newargs.append(arg)
 
         # Log call representation
         # XXX Truncate to avoid DoS
-        event['call'] = self.name + pprint.saferepr(args)
+        event['call'] = self.name + pprint.saferepr(newargs)
        event['call_name'] = self.name
 
         # Both users and nodes can call some methods
index 9fc771b..ae88e58 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d69e627..2278110 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index fc75254..061edd4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 5604cef..f2a300d 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 2d1542c..e592916 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 25ea249..f9166ad 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 78d47ba..d132561 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index 8c247cb..ed3efac 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 6bfb4e5..8c6d4cb 100644 (file)
@@ -1,52 +1,65 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
+from PLC.Auth import Auth
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
+from PLC.Table import Row
+
 from PLC.Nodes import Node, Nodes
 from PLC.Interfaces import Interface, Interfaces
-from PLC.Auth import Auth
+from PLC.TagTypes import TagTypes
+from PLC.InterfaceTags import InterfaceTags
+from PLC.Methods.AddInterfaceTag import AddInterfaceTag
+from PLC.Methods.UpdateInterfaceTag import UpdateInterfaceTag
 
-can_update = lambda (field, value): field not in ['interface_id', 'node_id']
+cannot_update = ['interface_id', 'node_id']
 
 class AddInterface(Method):
     """
 
     Adds a new network for a node. Any values specified in
     interface_fields are used, otherwise defaults are
-    used. Acceptable values for method may be retrieved via
-    GetNetworkMethods. Acceptable values for type may be retrieved via
-    GetNetworkTypes.
+    used. 
 
-    If type is static, ip, gateway, network, broadcast, netmask, and
-    dns1 must all be specified in interface_fields. If type is dhcp,
-    these parameters, even if specified, are ignored.
+    If type is static, then ip, gateway, network, broadcast, netmask,
+    and dns1 must all be specified in interface_fields. If type is
+    dhcp, these parameters, even if specified, are ignored.
 
-    PIs and techs may only add networks to their own nodes. Admins may
-    add networks to any node.
+    PIs and techs may only add interfaces to their own nodes. Admins may
+    add interfaces to any node.
 
     Returns the new interface_id (> 0) if successful, faults otherwise.
     """
 
     roles = ['admin', 'pi', 'tech']
 
-    interface_fields = dict(filter(can_update, Interface.fields.items()))
+    accepted_fields = Row.accepted_fields(cannot_update, Interface.fields, exclude=True)
+    accepted_fields.update(Interface.tags)
 
     accepts = [
         Auth(),
         Mixed(Node.fields['node_id'],
               Node.fields['hostname']),
-        interface_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, 'New interface_id (> 0) if successful')
 
     
     def call(self, auth, node_id_or_hostname, interface_fields):
-        interface_fields = dict(filter(can_update, interface_fields.items()))
+
+        [native,tags,rejected]=Row.split_fields(interface_fields,[Interface.fields,Interface.tags])
+
+        # type checking
+        native = Row.check_fields (native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot add Interface with column(s) %r"%rejected
 
         # Check if node exists
         nodes = Nodes(self.api, [node_id_or_hostname])
         if not nodes:
-            raise PLCInvalidArgument, "No such node"
+            raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
        node = nodes[0]
 
         # Authenticated function
@@ -56,18 +69,29 @@ class AddInterface(Method):
         # member of the site where the node exists.
         if 'admin' not in self.caller['roles']:
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to add node network for specified node"
+                raise PLCPermissionDenied, "Not allowed to add an interface to the specified node"
 
-        # Add node network
-       interface = Interface(self.api, interface_fields)
+        # Add interface
+       interface = Interface(self.api, native)
         interface['node_id'] = node['node_id']
-       # if this is the first node network, make it primary
+       # if this is the first interface, make it primary
        if not node['interface_ids']:
                interface['is_primary'] = True
         interface.sync()
        
        # Logging variables
-       self.object_ids = [node['node_id'], interface['interface_id']]  
-       self.messgage = "Node network %d added" % interface['interface_id']
+       self.object_objects = { 'Node': [node['node_id']], 
+                                'Interface' : [interface['interface_id']] }
+       self.message = "Interface %d added" % interface['interface_id']
+
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            interface_tags=InterfaceTags(self.api,{'tagname':tagname,'interface_id':interface['interface_id']})
+            if not interface_tags:
+                AddInterfaceTag(self.api).__call__(auth,interface['interface_id'],tagname,value)
+            else:
+                UpdateInterfaceTag(self.api).__call__(auth,interface_tags[0]['interface_tag_id'],value)
 
         return interface['interface_id']
similarity index 76%
rename from PLC/Methods/AddInterfaceSetting.py
rename to PLC/Methods/AddInterfaceTag.py
index c9e99f6..9b87e8f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -9,13 +11,13 @@ from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 
 from PLC.TagTypes import TagType, TagTypes
-from PLC.InterfaceSettings import InterfaceSetting, InterfaceSettings
+from PLC.InterfaceTags import InterfaceTag, InterfaceTags
 from PLC.Interfaces import Interface, Interfaces
 
 from PLC.Nodes import Nodes
 from PLC.Sites import Sites
 
-class AddInterfaceSetting(Method):
+class AddInterfaceTag(Method):
     """
     Sets the specified setting for the specified interface
     to the specified value.
@@ -23,7 +25,7 @@ class AddInterfaceSetting(Method):
     In general only tech(s), PI(s) and of course admin(s) are allowed to
     do the change, but this is defined in the tag type object.
 
-    Returns the new interface_setting_id (> 0) if successful, faults
+    Returns the new interface_tag_id (> 0) if successful, faults
     otherwise.
     """
 
@@ -32,13 +34,13 @@ class AddInterfaceSetting(Method):
     accepts = [
         Auth(),
         # no other way to refer to a interface
-        InterfaceSetting.fields['interface_id'],
+        InterfaceTag.fields['interface_id'],
         Mixed(TagType.fields['tag_type_id'],
               TagType.fields['tagname']),
-        InterfaceSetting.fields['value'],
+        InterfaceTag.fields['value'],
         ]
 
-    returns = Parameter(int, 'New interface_setting_id (> 0) if successful')
+    returns = Parameter(int, 'New interface_tag_id (> 0) if successful')
 
     object_type = 'Interface'
 
@@ -55,7 +57,7 @@ class AddInterfaceSetting(Method):
         tag_type = tag_types[0]
 
        # checks for existence - does not allow several different settings
-        conflicts = InterfaceSettings(self.api,
+        conflicts = InterfaceTags(self.api,
                                         {'interface_id':interface['interface_id'],
                                          'tag_type_id':tag_type['tag_type_id']})
 
@@ -78,12 +80,12 @@ class AddInterfaceSetting(Method):
                    min(self.caller['role_ids']) > required_min_role:
                raise PLCPermissionDenied, "Not allowed to modify the specified interface setting, requires role %d",required_min_role
 
-        interface_setting = InterfaceSetting(self.api)
-        interface_setting['interface_id'] = interface['interface_id']
-        interface_setting['tag_type_id'] = tag_type['tag_type_id']
-        interface_setting['value'] = value
+        interface_tag = InterfaceTag(self.api)
+        interface_tag['interface_id'] = interface['interface_id']
+        interface_tag['tag_type_id'] = tag_type['tag_type_id']
+        interface_tag['value'] = value
 
-        interface_setting.sync()
-       self.object_ids = [interface_setting['interface_setting_id']]
+        interface_tag.sync()
+       self.object_ids = [interface_tag['interface_tag_id']]
 
-        return interface_setting['interface_setting_id']
+        return interface_tag['interface_tag_id']
index b3690a8..97c69b8 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 62a2da7..20f74c1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter
index 11f3845..3932f34 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 6533053..d2712ac 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 8d253df..8403162 100644 (file)
@@ -1,13 +1,19 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
+from PLC.Auth import Auth
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.Nodes import Node, Nodes
-from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Table import Row
+
 from PLC.Sites import Site, Sites
-from PLC.Auth import Auth
+from PLC.Nodes import Node, Nodes
+from PLC.TagTypes import TagTypes
+from PLC.NodeTags import NodeTags
+from PLC.Methods.AddNodeTag import AddNodeTag
+from PLC.Methods.UpdateNodeTag import UpdateNodeTag
 
-can_update = lambda (field, value): field in \
-             ['hostname', 'boot_state', 'model', 'version']
+can_update = ['hostname', 'node_type', 'boot_state', 'model', 'version']
 
 class AddNode(Method):
     """
@@ -22,19 +28,26 @@ class AddNode(Method):
 
     roles = ['admin', 'pi', 'tech']
 
-    node_fields = dict(filter(can_update, Node.fields.items()))
+    accepted_fields = Row.accepted_fields(can_update,Node.fields)
+    accepted_fields.update(Node.tags)
 
     accepts = [
         Auth(),
         Mixed(Site.fields['site_id'],
               Site.fields['login_base']),
-        node_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, 'New node_id (> 0) if successful')
 
     def call(self, auth, site_id_or_login_base, node_fields):
-        node_fields = dict(filter(can_update, node_fields.items()))
+
+        [native,tags,rejected]=Row.split_fields(node_fields,[Node.fields,Node.tags])
+
+        # type checking
+        native = Row.check_fields(native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot add Node with column(s) %r"%rejected
 
         # Get site information
         sites = Sites(self.api, [site_id_or_login_base])
@@ -55,12 +68,22 @@ class AddNode(Method):
             else:
                 assert self.caller['person_id'] in site['person_ids']
 
-        node = Node(self.api, node_fields)
+        node = Node(self.api, native)
         node['site_id'] = site['site_id']
         node.sync()
 
-       self.event_objects = {'Site': [site['site_id']],
-                            'Node': [node['node_id']]} 
-       self.message = "Node %s created" % node['node_id']
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
+            if not node_tags:
+                AddNodeTag(self.api).__call__(auth,node['node_id'],tagname,value)
+            else:
+                UpdateNodeTag(self.api).__call__(auth,node_tags[0]['node_tag_id'],value)
 
+        self.event_objects = {'Site': [site['site_id']],
+                            'Node': [node['node_id']]} 
+        self.message = "Node %s created" % node['node_id']
+        
         return node['node_id']
index 404fb81..88210c1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -26,13 +28,13 @@ class AddNodeGroup(Method):
         NodeGroup.fields['groupname'],
         Mixed(TagType.fields['tag_type_id'],
               TagType.fields['tagname']),
-        NodeTag.fields['tagvalue'],
+        NodeTag.fields['value'],
         ]
 
     returns = Parameter(int, 'New nodegroup_id (> 0) if successful')
 
 
-    def call(self, auth, groupname, tag_type_id_or_tagname, tagvalue):
+    def call(self, auth, groupname, tag_type_id_or_tagname, value):
         # locate tag type
         tag_types = TagTypes (self.api,[tag_type_id_or_tagname])
         if not(tag_types):
@@ -41,7 +43,7 @@ class AddNodeGroup(Method):
 
         nodegroup_fields = { 'groupname' : groupname,
                              'tag_type_id' : tag_type['tag_type_id'],
-                             'tagvalue' : tagvalue }
+                             'value' : value }
         nodegroup = NodeGroup(self.api, nodegroup_fields)
         nodegroup.sync()
 
index 62e166c..ac6bbd1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -8,11 +10,11 @@ from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 
+from PLC.Sites import Sites
+from PLC.Nodes import Node, Nodes
 from PLC.TagTypes import TagType, TagTypes
 from PLC.NodeTags import NodeTag, NodeTags
-from PLC.Nodes import Node, Nodes
 
-from PLC.Sites import Sites
 
 class AddNodeTag(Method):
     """
@@ -35,7 +37,7 @@ class AddNodeTag(Method):
               Node.fields['hostname']),
         Mixed(TagType.fields['tag_type_id'],
               TagType.fields['tagname']),
-        NodeTag.fields['tagvalue'],
+        NodeTag.fields['value'],
         ]
 
     returns = Parameter(int, 'New node_tag_id (> 0) if successful')
@@ -81,7 +83,7 @@ class AddNodeTag(Method):
         node_tag = NodeTag(self.api)
         node_tag['node_id'] = node['node_id']
         node_tag['tag_type_id'] = tag_type['tag_type_id']
-        node_tag['tagvalue'] = value
+        node_tag['value'] = value
 
         node_tag.sync()
        self.object_ids = [node_tag['node_tag_id']]
index c0d5eff..279731d 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/AddNodeType.py b/PLC/Methods/AddNodeType.py
new file mode 100644 (file)
index 0000000..034ed41
--- /dev/null
@@ -0,0 +1,31 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeTypes import NodeType, NodeTypes
+from PLC.Auth import Auth
+
+class AddNodeType(Method):
+    """
+    Adds a new node node type.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        Auth(),
+        NodeType.fields['node_type']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    
+    def call(self, auth, name):
+        node_type = NodeType(self.api)
+        node_type['node_type'] = name
+        node_type.sync(insert = True)
+
+        return 1
index 3c46194..9def186 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 76dad3b..f722917 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 106791a..58bb80d 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d6dc576..e4aef4c 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 # 
@@ -8,7 +10,7 @@ from PLC.Auth import Auth
 from PLC.Peers import Peer, Peers
 
 can_update = lambda (field, value): field in \
-             ['peername', 'peer_url', 'key', 'cacert']
+             ['peername', 'peer_url', 'key', 'cacert', 'shortname', 'hrn_root']
 
 class AddPeer(Method):
     """
index cb0aa71..a8b4e2c 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -36,8 +38,8 @@ class AddPerson(Method):
         person = Person(self.api, person_fields)
         person.sync()
 
-       # Logging variables
-       self.event_objects = {'Person': [person['person_id']]}
-       self.message = 'Person %d added' % person['person_id']  
+        # Logging variables
+        self.event_objects = {'Person': [person['person_id']]}
+        self.message = 'Person %d added' % person['person_id'] 
 
         return person['person_id']
index aa4ed00..5212b44 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -41,7 +43,7 @@ class AddPersonKey(Method):
         if person['peer_id'] is not None:
             raise PLCInvalidArgument, "Not a local account"
 
-       # If we are not admin, make sure caller is adding a key to their account
+        # If we are not admin, make sure caller is adding a key to their account
         if 'admin' not in self.caller['roles']:
             if person['person_id'] != self.caller['person_id']:
                 raise PLCPermissionDenied, "You may only modify your own keys"
@@ -51,9 +53,9 @@ class AddPersonKey(Method):
         person.add_key(key, commit = True)
 
         # Logging variables
-       self.event_objects = {'Person': [person['person_id']],
-                             'Key': [key['key_id']]}
-       self.message = 'Key %d added to person %d' % \
-               (key['key_id'], person['person_id'])
+        self.event_objects = {'Person': [person['person_id']],
+                              'Key': [key['key_id']]}
+        self.message = 'Key %d added to person %d' % \
+                        (key['key_id'], person['person_id'])
 
         return key['key_id']
diff --git a/PLC/Methods/AddPersonTag.py b/PLC/Methods/AddPersonTag.py
new file mode 100644 (file)
index 0000000..6824657
--- /dev/null
@@ -0,0 +1,86 @@
+# $Id: AddPersonTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/AddPersonTag.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.TagTypes import TagType, TagTypes
+from PLC.PersonTags import PersonTag, PersonTags
+from PLC.Persons import Person, Persons
+
+from PLC.Nodes import Nodes
+
+class AddPersonTag(Method):
+    """
+    Sets the specified setting for the specified person
+    to the specified value.
+
+    In general only tech(s), PI(s) and of course admin(s) are allowed to
+    do the change, but this is defined in the tag type object.
+
+    Returns the new person_tag_id (> 0) if successful, faults
+    otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        # no other way to refer to a person
+        PersonTag.fields['person_id'],
+        Mixed(TagType.fields['tag_type_id'],
+              TagType.fields['tagname']),
+        PersonTag.fields['value'],
+        ]
+
+    returns = Parameter(int, 'New person_tag_id (> 0) if successful')
+
+    object_type = 'Person'
+
+
+    def call(self, auth, person_id, tag_type_id_or_name, value):
+        persons = Persons(self.api, [person_id])
+        if not persons:
+            raise PLCInvalidArgument, "No such person %r"%person_id
+        person = persons[0]
+
+        tag_types = TagTypes(self.api, [tag_type_id_or_name])
+        if not tag_types:
+            raise PLCInvalidArgument, "No such tag type %r"%tag_type_id_or_name
+        tag_type = tag_types[0]
+
+       # checks for existence - does not allow several different settings
+        conflicts = PersonTags(self.api,
+                                        {'person_id':person['person_id'],
+                                         'tag_type_id':tag_type['tag_type_id']})
+
+        if len(conflicts) :
+            raise PLCInvalidArgument, "Person %d already has setting %d"%(person['person_id'],
+                                                                               tag_type['tag_type_id'])
+
+       # check permission : it not admin, is the user affiliated with the same site as this person
+       if 'admin' not in self.caller['roles']:
+           # check caller is affiliated with at least one of Person's sites
+           if len(set(person['site_ids']) & set(self.caller['site_ids'])) == 0:
+               raise PLCPermissionDenied, "Not a member of the person's sites: %s"%person['site_ids']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified person setting, requires role %d",required_min_role
+
+        person_tag = PersonTag(self.api)
+        person_tag['person_id'] = person['person_id']
+        person_tag['tag_type_id'] = tag_type['tag_type_id']
+        person_tag['value'] = value
+
+        person_tag.sync()
+       self.object_ids = [person_tag['person_tag_id']]
+
+        return person_tag['person_tag_id']
index 7d5ac10..595399f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -48,9 +50,10 @@ class AddPersonToSite(Method):
         if site['site_id'] not in person['site_ids']:
             site.add_person(person)
 
-       # Logging variables
-       self.event_objects = {'Site': [site['site_id']],
-                             'Person': [person['person_id']]}
-       self.message = 'Person %d added to site %d' % \
-               (person['person_id'], site['site_id'])
+        # Logging variables
+        self.event_objects = {'Site': [site['site_id']],
+                              'Person': [person['person_id']]}
+        self.message = 'Person %d added to site %d' % \
+                       (person['person_id'], site['site_id'])
+
         return 1
index e3392eb..b38e111 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 7266180..2ab69f1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 5a8e241..9fa85df 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 6f5bc88..d06b7a0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Method import Method
index 475c8b1..239de70 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -7,7 +9,7 @@ from PLC.Auth import Auth
 can_update = lambda (field, value): field in \
              ['name', 'abbreviated_name', 'login_base',
               'is_public', 'latitude', 'longitude', 'url',
-              'max_slices', 'max_slivers', 'enabled']
+              'max_slices', 'max_slivers', 'enabled', 'ext_consortium_id']
 
 class AddSite(Method):
     """
@@ -34,8 +36,8 @@ class AddSite(Method):
         site = Site(self.api, site_fields)
         site.sync()
        
-       # Logging variables 
-       self.event_objects = {'Site': [site['site_id']]}
+        # Logging variables 
+        self.event_objects = {'Site': [site['site_id']]}
         self.message = 'Site %d created' % site['site_id']
        
-       return site['site_id']
+        return site['site_id']
index a3a4eff..7a93821 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/AddSiteTag.py b/PLC/Methods/AddSiteTag.py
new file mode 100644 (file)
index 0000000..dda3f57
--- /dev/null
@@ -0,0 +1,89 @@
+# $Id: AddSiteTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/AddSiteTag.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.TagTypes import TagType, TagTypes
+from PLC.SiteTags import SiteTag, SiteTags
+from PLC.Sites import Site, Sites
+
+from PLC.Nodes import Nodes
+from PLC.Sites import Sites
+
+class AddSiteTag(Method):
+    """
+    Sets the specified setting for the specified site
+    to the specified value.
+
+    In general only tech(s), PI(s) and of course admin(s) are allowed to
+    do the change, but this is defined in the tag type object.
+
+    Returns the new site_tag_id (> 0) if successful, faults
+    otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        # no other way to refer to a site
+        SiteTag.fields['site_id'],
+        Mixed(TagType.fields['tag_type_id'],
+              TagType.fields['tagname']),
+        SiteTag.fields['value'],
+        ]
+
+    returns = Parameter(int, 'New site_tag_id (> 0) if successful')
+
+    object_type = 'Site'
+
+
+    def call(self, auth, site_id, tag_type_id_or_name, value):
+        sites = Sites(self.api, [site_id])
+        if not sites:
+            raise PLCInvalidArgument, "No such site %r"%site_id
+        site = sites[0]
+
+        tag_types = TagTypes(self.api, [tag_type_id_or_name])
+        if not tag_types:
+            raise PLCInvalidArgument, "No such tag type %r"%tag_type_id_or_name
+        tag_type = tag_types[0]
+
+       # checks for existence - does not allow several different settings
+        conflicts = SiteTags(self.api,
+                                        {'site_id':site['site_id'],
+                                         'tag_type_id':tag_type['tag_type_id']})
+
+        if len(conflicts) :
+            raise PLCInvalidArgument, "Site %d already has setting %d"%(site['site_id'],
+                                                                               tag_type['tag_type_id'])
+
+       # check permission : it not admin, is the user affiliated with the right site
+       if 'admin' not in self.caller['roles']:
+           # locate site
+           site = Sites (self.api, site_id)[0]
+           # check caller is affiliated with this site
+           if self.caller['person_id'] not in site['person_ids']:
+               raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified site setting, requires role %d",required_min_role
+
+        site_tag = SiteTag(self.api)
+        site_tag['site_id'] = site['site_id']
+        site_tag['tag_type_id'] = tag_type['tag_type_id']
+        site_tag['value'] = value
+
+        site_tag.sync()
+       self.object_ids = [site_tag['site_tag_id']]
+
+        return site_tag['site_tag_id']
index 6cc056c..937c551 100644 (file)
@@ -1,14 +1,21 @@
+# $Id$
+# $URL$
 import re
 
 from PLC.Faults import *
+from PLC.Auth import Auth
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
+from PLC.Table import Row
+
 from PLC.Slices import Slice, Slices
-from PLC.Auth import Auth
 from PLC.Sites import Site, Sites
+from PLC.TagTypes import TagTypes
+from PLC.SliceTags import SliceTags
+from PLC.Methods.AddSliceTag import AddSliceTag
+from PLC.Methods.UpdateSliceTag import UpdateSliceTag
 
-can_update = lambda (field, value): field in \
-             ['name', 'instantiation', 'url', 'description', 'max_nodes']
+can_update = ['name', 'instantiation', 'url', 'description', 'max_nodes']
 
 class AddSlice(Method):
     """
@@ -29,17 +36,27 @@ class AddSlice(Method):
 
     roles = ['admin', 'pi']
 
-    slice_fields = dict(filter(can_update, Slice.fields.items()))
+    accepted_fields = Row.accepted_fields(can_update, Slice.fields)
+    accepted_fields.update(Slice.tags)
 
     accepts = [
         Auth(),
-        slice_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, 'New slice_id (> 0) if successful')
 
     def call(self, auth, slice_fields):
-        slice_fields = dict(filter(can_update, slice_fields.items()))
+
+        [native,tags,rejected]=Row.split_fields(slice_fields,[Slice.fields,Slice.tags])
+
+        # type checking
+        native = Row.check_fields (native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot add Slice with column(s) %r"%rejected
+
+        # Authenticated function
+        assert self.caller is not None
 
         # 1. Lowercase.
         # 2. Begins with login_base (letters or numbers).
@@ -60,21 +77,32 @@ class AddSlice(Method):
 
         if 'admin' not in self.caller['roles']:
             if site['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Slice prefix %s must be the same as the login_base of one of your sites"%login_base
+                raise PLCPermissionDenied, "Slice prefix %s must match one of your sites' login_base"%login_base
 
         if len(site['slice_ids']) >= site['max_slices']:
-            raise PLCInvalidArgument, "Site %s has reached (%d) its maximum allowable slice count (%d)"%(site['name'],
-                                                                                                         len(site['slice_ids']),
-                                                                                                         site['max_slices'])
-
+            raise PLCInvalidArgument, \
+                "Site %s has reached (%d) its maximum allowable slice count (%d)"%(site['name'],
+                                                                                   len(site['slice_ids']),
+                                                                                   site['max_slices'])
        if not site['enabled']:
-           raise PLCInvalidArgument, "Site %s is disabled can cannot create slices" % (site['name'])
+           raise PLCInvalidArgument, "Site %s is disabled and can cannot create slices" % (site['name'])
         
-        slice = Slice(self.api, slice_fields)
+        slice = Slice(self.api, native)
         slice['creator_person_id'] = self.caller['person_id']
         slice['site_id'] = site['site_id']
         slice.sync()
 
-       self.event_objects = {'Slice': [slice['slice_id']]}
-
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            slice_tags=SliceTags(self.api,{'tagname':tagname,'slice_id':slice['slice_id']})
+            if not slice_tags:
+                AddSliceTag(self.api).__call__(auth,slice['slice_id'],tagname,value)
+            else:
+                UpdateSliceTag(self.api).__call__(auth,slice_tags[0]['slice_tag_id'],value)
+
+        self.event_objects = {'Slice': [slice['slice_id']]}
+        self.message = "Slice %d created" % slice['slice_id']
+    
         return slice['slice_id']
diff --git a/PLC/Methods/AddSliceAttribute.py b/PLC/Methods/AddSliceAttribute.py
deleted file mode 100644 (file)
index 692dce0..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-from PLC.Faults import *
-from PLC.Method import Method
-from PLC.Parameter import Parameter, Mixed
-from PLC.TagTypes import TagType, TagTypes
-from PLC.Slices import Slice, Slices
-from PLC.Nodes import Node, Nodes
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
-from PLC.NodeGroups import NodeGroup, NodeGroups
-from PLC.InitScripts import InitScript, InitScripts
-from PLC.Auth import Auth
-
-class AddSliceAttribute(Method):
-    """
-    Sets the specified attribute of the slice (or sliver, if
-    node_id_or_hostname is specified) to the specified value.
-
-    Attributes may require the caller to have a particular role in
-    order to be set or changed. Users may only set attributes of
-    slices or slivers of which they are members. PIs may only set
-    attributes of slices or slivers at their sites, or of which they
-    are members. Admins may set attributes of any slice or sliver.
-
-    Returns the new slice_attribute_id (> 0) if successful, faults
-    otherwise.
-    """
-
-    roles = ['admin', 'pi', 'user']
-
-    accepts = [
-        Auth(),
-        Mixed(Slice.fields['slice_id'],
-              Slice.fields['name']),
-        Mixed(SliceAttribute.fields['tag_type_id'],
-              SliceAttribute.fields['tagname']),
-        Mixed(SliceAttribute.fields['value'],
-             InitScript.fields['name']),
-        Mixed(Node.fields['node_id'],
-              Node.fields['hostname'],
-             None),
-       Mixed(NodeGroup.fields['nodegroup_id'],
-              NodeGroup.fields['groupname'])
-        ]
-
-    returns = Parameter(int, 'New slice_attribute_id (> 0) if successful')
-
-    def call(self, auth, slice_id_or_name, tag_type_id_or_name, value, node_id_or_hostname = None, nodegroup_id_or_name = None):
-        slices = Slices(self.api, [slice_id_or_name])
-        if not slices:
-            raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
-        slice = slices[0]
-
-        tag_types = TagTypes(self.api, [tag_type_id_or_name])
-        if not tag_types:
-            raise PLCInvalidArgument, "No such tag type %r"%tag_type_id_or_name
-        tag_type = tag_types[0]
-
-        if 'admin' not in self.caller['roles']:
-            if self.caller['person_id'] in slice['person_ids']:
-                pass
-            elif 'pi' not in self.caller['roles']:
-                raise PLCPermissionDenied, "Not a member of the specified slice"
-            elif slice['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
-
-            if tag_type['min_role_id'] is not None and \
-               min(self.caller['role_ids']) > tag_type['min_role_id']:
-                raise PLCPermissionDenied, "Not allowed to set the specified slice attribute"
-
-       # if initscript is specified, validate value
-       if tag_type['tagname'] in ['initscript']:
-           initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
-           if not initscripts: 
-               raise PLCInvalidArgument, "No such plc initscript %r"%value
-
-        slice_attribute = SliceAttribute(self.api)
-        slice_attribute['slice_id'] = slice['slice_id']
-        slice_attribute['tag_type_id'] = tag_type['tag_type_id']
-        slice_attribute['value'] = unicode(value)
-
-        # Sliver attribute if node is specified
-        if node_id_or_hostname is not None:
-            nodes = Nodes(self.api, [node_id_or_hostname])
-            if not nodes:
-                raise PLCInvalidArgument, "No such node"
-            node = nodes[0]
-            
-            if node['node_id'] not in slice['node_ids']:
-                raise PLCInvalidArgument, "Node not in the specified slice"
-            slice_attribute['node_id'] = node['node_id']
-
-       # Sliver attribute shared accross nodes if nodegroup is sepcified
-       if nodegroup_id_or_name is not None:
-           nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
-           if not nodegroups:
-               raise PLCInvalidArgument, "No such nodegroup %r"%nodegroup_id_or_name
-           nodegroup = nodegroups[0]
-       
-           slice_attribute['nodegroup_id'] = nodegroup['nodegroup_id']
-
-       # Check if slice attribute alreay exists
-        slice_attributes_check = SliceAttributes(self.api, {'slice_id': slice['slice_id'], 
-                                                            'tagname': tag_type['tagname'], 
-                                                            'value': value})
-        for slice_attribute_check in slice_attributes_check:
-            if 'node_id' in slice_attribute and slice_attribute['node_id'] == slice_attribute_check['node_id']:
-               raise PLCInvalidArgument, "Sliver attribute already exists"
-           if 'nodegroup_id' in slice_attribute and slice_attribute['nodegroup_id'] == slice_attribute_check['nodegroup_id']:
-               raise PLCInvalidArgument, "Slice attribute already exists for this nodegroup"
-            if node_id_or_hostname is None and nodegroup_id_or_name is None:
-                raise PLCInvalidArgument, "Slice attribute already exists"
-
-        slice_attribute.sync()
-       self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
-
-        return slice_attribute['slice_attribute_id']
index 0374957..2a4262f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/AddSliceTag.py b/PLC/Methods/AddSliceTag.py
new file mode 100644 (file)
index 0000000..284a24f
--- /dev/null
@@ -0,0 +1,138 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.TagTypes import TagType, TagTypes
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.SliceTags import SliceTag, SliceTags
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class AddSliceTag(Method):
+    """
+    Sets the specified attribute of the slice (or sliver, if
+    node_id_or_hostname is specified) to the specified value.
+
+    Attributes may require the caller to have a particular role in
+    order to be set or changed. Users may only set attributes of
+    slices or slivers of which they are members. PIs may only set
+    attributes of slices or slivers at their sites, or of which they
+    are members. Admins may set attributes of any slice or sliver.
+
+    Returns the new slice_tag_id (> 0) if successful, faults
+    otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        Mixed(SliceTag.fields['tag_type_id'],
+              SliceTag.fields['tagname']),
+        Mixed(SliceTag.fields['value'],
+             InitScript.fields['name']),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname'],
+             None),
+       Mixed(NodeGroup.fields['nodegroup_id'],
+              NodeGroup.fields['groupname'])
+        ]
+
+    returns = Parameter(int, 'New slice_tag_id (> 0) if successful')
+
+    def call(self, auth, slice_id_or_name, tag_type_id_or_name, value, node_id_or_hostname = None, nodegroup_id_or_name = None):
+        slices = Slices(self.api, [slice_id_or_name])
+        if not slices:
+            raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
+        slice = slices[0]
+
+        tag_types = TagTypes(self.api, [tag_type_id_or_name])
+        if not tag_types:
+            raise PLCInvalidArgument, "No such tag type %r"%tag_type_id_or_name
+        tag_type = tag_types[0]
+
+        if not isinstance(self.caller, Node):
+            if ('admin' not in self.caller['roles']):
+                if self.caller['person_id'] in slice['person_ids']:
+                    pass
+                elif 'pi' not in self.caller['roles']:
+                    raise PLCPermissionDenied, "Not a member of the specified slice"
+                elif slice['site_id'] not in self.caller['site_ids']:
+                    raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+                if tag_type['min_role_id'] is not None and \
+                       min(self.caller['role_ids']) > tag_type['min_role_id']:
+                    raise PLCPermissionDenied, "Not allowed to set the specified slice attribute"
+        else:
+            ### make node's min_role_id == PI min_role_id
+            node_role_id = 20
+            if tag_type['min_role_id'] is not None and node_role_id > tag_type['min_role_id']:
+                raise PLCPermissionDenied, "Not allowed to set the specified slice attribute"
+            
+       # if initscript is specified, validate value
+       if tag_type['tagname'] in ['initscript']:
+           initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
+           if not initscripts: 
+               raise PLCInvalidArgument, "No such plc initscript %r"%value
+
+        slice_tag = SliceTag(self.api)
+        slice_tag['slice_id'] = slice['slice_id']
+        slice_tag['tag_type_id'] = tag_type['tag_type_id']
+        slice_tag['value'] = unicode(value)
+
+        # Sliver attribute if node is specified
+        if node_id_or_hostname is not None or isinstance(self.caller, Node):
+            node_id = None
+            if isinstance(self.caller, Node):
+                node = self.caller
+                node_id = node['node_id']
+
+            if node_id_or_hostname is not None:
+                nodes = Nodes(self.api, [node_id_or_hostname])
+                if not nodes:
+                    raise PLCInvalidArgument, "No such node"
+                node = nodes[0]
+                if node_id <> None and node_id <> node['node_id']:
+                    raise PLCPermissionDenied, "Not allowed to set another node's sliver attribute"
+                else:                    
+                    node_id = node['node_id']
+            
+            system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
+            system_slice_ids = system_slice_tags.keys()
+           if slice['slice_id'] not in system_slice_ids and node_id not in slice['node_ids']:
+                raise PLCInvalidArgument, "AddSliceTag: slice %s not on specified node %s nor is it a system slice (%r)"%(slice['name'],node['hostname'],system_slice_ids)
+            slice_tag['node_id'] = node['node_id']
+
+       # Sliver attribute shared accross nodes if nodegroup is sepcified
+       if nodegroup_id_or_name is not None:
+            if isinstance(self.caller, Node):
+                    raise PLCPermissionDenied, "Not allowed to set nodegroup slice attributes"
+                
+           nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+           if not nodegroups:
+               raise PLCInvalidArgument, "No such nodegroup %r"%nodegroup_id_or_name
+           nodegroup = nodegroups[0]
+       
+           slice_tag['nodegroup_id'] = nodegroup['nodegroup_id']
+
+       # Check if slice attribute alreay exists
+        slice_tags_check = SliceTags(self.api, {'slice_id': slice['slice_id'], 
+                                                            'tagname': tag_type['tagname'], 
+                                                            'value': value})
+        for slice_tag_check in slice_tags_check:
+            if 'node_id' in slice_tag and slice_tag['node_id'] == slice_tag_check['node_id']:
+               raise PLCInvalidArgument, "Sliver attribute already exists"
+           if 'nodegroup_id' in slice_tag and slice_tag['nodegroup_id'] == slice_tag_check['nodegroup_id']:
+               raise PLCInvalidArgument, "Slice attribute already exists for this nodegroup"
+            if node_id_or_hostname is None and nodegroup_id_or_name is None:
+                raise PLCInvalidArgument, "Slice attribute already exists"
+
+        slice_tag.sync()
+       self.event_objects = {'SliceTag': [slice_tag['slice_tag_id']]}
+
+        return slice_tag['slice_tag_id']
index d5a2c8c..57724cb 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -33,7 +35,7 @@ class AddSliceToNodes(Method):
         # Get slice information
         slices = Slices(self.api, [slice_id_or_name])
         if not slices:
-            raise PLCInvalidArgument, "No such slice"
+            raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
         slice = slices[0]
 
         if slice['peer_id'] is not None:
@@ -63,7 +65,9 @@ class AddSliceToNodes(Method):
 
         slice.sync()
 
-       self.event_objects = {'Node': [node['node_id'] for node in nodes],
+        nodeids = [node['node_id'] for node in nodes]
+       self.event_objects = {'Node': nodeids,
                              'Slice': [slice['slice_id']]}
+        self.message = 'Slice %d added to nodes %s' % (slice['slice_id'], nodeids)
 
         return 1
index a6b4bd1..5b0dc91 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 978af37..3de86b0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index 0a4c260..4cea8d8 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth, BootAuth
diff --git a/PLC/Methods/BindObjectToPeer.py b/PLC/Methods/BindObjectToPeer.py
new file mode 100644 (file)
index 0000000..70f24c3
--- /dev/null
@@ -0,0 +1,75 @@
+# $Id$
+# $URL$
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Persons import Persons
+from PLC.Sites import Sites
+from PLC.Nodes import Nodes
+from PLC.Slices import Slices
+from PLC.Keys import Keys
+from PLC.Peers import Peers
+from PLC.Faults import *
+class BindObjectToPeer(Method):
+    """
+    This method is a hopefully temporary hack to let the sfa correctly
+    attach the objects it creates to a remote peer object. This is
+    needed so that the sfa federation link can work in parallel with
+    RefreshPeer, as RefreshPeer depends on remote objects being
+    correctly marked. 
+
+    BindRemoteObjectToPeer is allowed to admins only.
+    """
+
+    roles = ['admin']
+
+    known_types = ['site','person','slice','node','key']
+    types_doc = ",".join(["'%s'"%type for type in known_types])
+
+    accepts = [
+        Auth(),
+        Parameter(str,"Object type, among "+types_doc),
+        Parameter(int,"object_id"),
+        Parameter(str,"peer shortname"),
+        Parameter(int,"remote object_id, set to 0 if unknown"),
+        ]
+
+    returns = Parameter (int, '1 if successful')
+
+    def locate_object (self, object_type, object_id):
+        # locate e.g. the Nodes symbol
+        class_obj = globals()[object_type.capitalize()+'s']
+        id_name=object_type+'_id'
+        # invoke e.g. Nodes ({'node_id':node_id})
+        objs=class_obj(self.api,{id_name:object_id})
+        if len(objs) != 1:
+            raise PLCInvalidArgument,"Cannot locate object, type=%s id=%d"%\
+                (type,object_id)
+        return objs[0]
+
+
+    def call(self, auth, object_type, object_id, shortname,remote_object_id):
+
+        object_type = object_type.lower()
+        if object_type not in self.known_types:
+            raise PLCInvalidArgument, 'Unrecognized object type %s'%object_type
+
+        peers=Peers(self.api,{'shortname':shortname.upper()})
+        if len(peers) !=1:
+            raise PLCInvalidArgument, 'No such peer with shortname %s'%shortname
+
+        peer=peers[0]
+        object = self.locate_object (object_type, object_id)
+        
+        # There is no need to continue if the object is already bound to this peer  
+        if object['peer_id'] in [peer['peer_id']]:
+            return 1
+        
+        adder_name = 'add_'+object_type
+        add_function = getattr(type(peer),adder_name)
+        add_function(peer,object,remote_object_id)
+
+        return 1
index 7953e7a..298e4e3 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index ea9b098..7369a3b 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Methods.AuthCheck import AuthCheck
 
 class BootCheckAuthentication(AuthCheck):
index 8f228bc..ae338f9 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import BootAuth
@@ -49,7 +51,7 @@ class BootGetNodeDetails(Method):
                         else:
                             network[field] = ""
 
-       self.messge = "Node request boot_state (%s) and networks" % \
+       self.message = "Node request boot_state (%s) and networks" % \
                (details['boot_state'])
         return details
 
index 81a7cb0..3564814 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth, BootAuth, SessionAuth
@@ -17,7 +19,7 @@ class BootNotifyOwners(Method):
     roles = ['node']
 
     accepts = [
-        Mixed(BootAuth(), SessionAuth()),
+        Auth(),
         Message.fields['message_id'],
         Parameter(int, "Notify PIs"),
         Parameter(int, "Notify technical contacts"),
index 530a24f..7d8949c 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -30,6 +32,10 @@ class BootUpdateNode(Method):
 
     returns = Parameter(int, '1 if successful')
 
+    # xxx this method is a bit spamming the events log
+    #    todo : log only when a change occurs
+    # also this seems to expect the user-provided node_fields to optionally 
+    #    contain the 'primary_network' key that should be renamed into 'primary_interface'
     def call(self, auth, node_fields):
         # Update node state
         if node_fields.has_key('boot_state'):
@@ -37,28 +43,33 @@ class BootUpdateNode(Method):
         if node_fields.has_key('ssh_host_key'):
             self.caller['ssh_rsa_key'] = node_fields['ssh_host_key']
 
-        # Update primary node network state
+        # Update primary interface state
         if node_fields.has_key('primary_network'):
             primary_network = node_fields['primary_network'] 
 
             if 'interface_id' not in primary_network:
-                raise PLCInvalidArgument, "Node network not specified"
+                raise PLCInvalidArgument, "Interface not specified"
             if primary_network['interface_id'] not in self.caller['interface_ids']:
-                raise PLCInvalidArgument, "Node network not associated with calling node"
+                raise PLCInvalidArgument, "Interface not associated with calling node"
 
             interfaces = Interfaces(self.api, [primary_network['interface_id']])
             if not interfaces:
-                raise PLCInvalidArgument, "No such node network"
+                raise PLCInvalidArgument, "No such interface %r"%interface_id
             interface = interfaces[0]
 
             if not interface['is_primary']:
-                raise PLCInvalidArgument, "Not the primary node network on record"
+                raise PLCInvalidArgument, "Not the primary interface on record"
 
             interface_fields = dict(filter(can_update, primary_network.items()))
             interface.update(interface_fields)
             interface.sync(commit = False)
 
+        # indicate that node has booted & contacted PLC.
+        if isinstance(self.caller, Node):
+            node = self.caller
+            node.update_last_contact()
+
         self.caller.sync(commit = True)
-       self.message = "Node updated: %s" % ", ".join(node_fields.keys())
+        self.message = "Node updated: %s" % ", ".join(node_fields.keys())
 
         return 1
index d4f98bc..0b0924d 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 4fd1d9b..3a43d70 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d4ea928..4115759 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 507fc7b..2339778 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index f05ae43..2d7183b 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 50b08e6..838755f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 7e3eca5..3e0ba48 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index f8b862e..163a7b5 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index 7c4b01e..1363874 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 046f1e0..6b888c7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -7,10 +9,10 @@ from PLC.Interfaces import Interface, Interfaces
 
 class DeleteInterface(Method):
     """
-    Deletes an existing node network interface.
+    Deletes an existing interface.
 
-    Admins may delete any node network. PIs and techs may only delete
-    node network interfaces associated with nodes at their sites.
+    Admins may delete any interface. PIs and techs may only delete
+    interface interfaces associated with nodes at their sites.
 
     Returns 1 if successful, faults otherwise.
     """
@@ -27,16 +29,16 @@ class DeleteInterface(Method):
 
     def call(self, auth, interface_id):
 
-        # Get node network information
+        # Get interface information
         interfaces = Interfaces(self.api, [interface_id])
         if not interfaces:
-            raise PLCInvalidArgument, "No such node network"
+            raise PLCInvalidArgument, "No such interface %r"%interface_id
        interface = interfaces[0]
        
        # Get node information
        nodes = Nodes(self.api, [interface['node_id']])
        if not nodes:
-               raise PLCInvalidArgument, "No such node"
+               raise PLCInvalidArgument, "No such node %r"%node_id
        node = nodes[0]
 
         # Authenticated functino
@@ -46,12 +48,12 @@ class DeleteInterface(Method):
         # member of the site at which the node is located.
         if 'admin' not in self.caller['roles']:
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to delete this node network"
+                raise PLCPermissionDenied, "Not allowed to delete this interface"
 
         interface.delete()
 
        # Logging variables
        self.event_objects = {'Interface': [interface['interface_id']]}
-       self.message = "Node network %d deleted" % interface['interface_id']
+       self.message = "Interface %d deleted" % interface['interface_id']
 
         return 1
similarity index 65%
rename from PLC/Methods/DeleteInterfaceSetting.py
rename to PLC/Methods/DeleteInterfaceTag.py
index 8ea1b77..bbf2a51 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -9,13 +11,13 @@ from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 
-from PLC.InterfaceSettings import InterfaceSetting, InterfaceSettings
+from PLC.InterfaceTags import InterfaceTag, InterfaceTags
 from PLC.Interfaces import Interface, Interfaces
 
 from PLC.Nodes import Node, Nodes
 from PLC.Sites import Site, Sites
 
-class DeleteInterfaceSetting(Method):
+class DeleteInterfaceTag(Method):
     """
     Deletes the specified interface setting
 
@@ -30,7 +32,7 @@ class DeleteInterfaceSetting(Method):
 
     accepts = [
         Auth(),
-        InterfaceSetting.fields['interface_setting_id']
+        InterfaceTag.fields['interface_tag_id']
         ]
 
     returns = Parameter(int, '1 if successful')
@@ -38,19 +40,19 @@ class DeleteInterfaceSetting(Method):
     object_type = 'Interface'
 
 
-    def call(self, auth, interface_setting_id):
-        interface_settings = InterfaceSettings(self.api, [interface_setting_id])
-        if not interface_settings:
-            raise PLCInvalidArgument, "No such interface setting %r"%interface_setting_id
-        interface_setting = interface_settings[0]
+    def call(self, auth, interface_tag_id):
+        interface_tags = InterfaceTags(self.api, [interface_tag_id])
+        if not interface_tags:
+            raise PLCInvalidArgument, "No such interface tag %r"%interface_tag_id
+        interface_tag = interface_tags[0]
 
-        ### reproducing a check from UpdateSliceAttribute, looks dumb though
-        interfaces = Interfaces(self.api, [interface_setting['interface_id']])
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        interfaces = Interfaces(self.api, [interface_tag['interface_id']])
         if not interfaces:
-            raise PLCInvalidArgument, "No such interface %r"%interface_setting['interface_id']
+            raise PLCInvalidArgument, "No such interface %r"%interface_tag['interface_id']
         interface = interfaces[0]
 
-        assert interface_setting['interface_setting_id'] in interface['interface_setting_ids']
+        assert interface_tag['interface_tag_id'] in interface['interface_tag_ids']
 
        # check permission : it not admin, is the user affiliated with the right site
        if 'admin' not in self.caller['roles']:
@@ -67,7 +69,7 @@ class DeleteInterfaceSetting(Method):
                    min(self.caller['role_ids']) > required_min_role:
                raise PLCPermissionDenied, "Not allowed to modify the specified interface setting, requires role %d",required_min_role
 
-        interface_setting.delete()
-       self.object_ids = [interface_setting['interface_setting_id']]
+        interface_tag.delete()
+       self.object_ids = [interface_tag['interface_tag_id']]
 
         return 1
index 86c16a5..e4203fc 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -39,8 +41,8 @@ class DeleteKey(Method):
 
         key.delete()
        
-       # Logging variables
-       self.event_objects = {'Key': [key['key_id']]}
-       self.message = 'Key %d deleted' % key['key_id']
+        # Logging variables
+        self.event_objects = {'Key': [key['key_id']]}
+        self.message = 'Key %d deleted' % key['key_id']
 
         return 1
index e09e5c5..95d3bea 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 4989942..f316998 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d0f982e..aeeef52 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index a02f6e0..424628e 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index bc92718..01e382a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -43,10 +45,13 @@ class DeleteNode(Method):
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
 
+        node_id=node['node_id']
+        site_id=node['site_id']
         node.delete()
 
-       # Logging variables
-       self.event_objects = {'Node': [node['node_id']]}
-       self.message = "Node %d deleted" % node['node_id']
+        # Logging variables
+        # it's not much use to attach to the node as it's going to vanish...
+        self.event_objects = {'Node': [node_id], 'Site': [site_id] }
+        self.message = "Node %d deleted" % node['node_id']
 
         return 1
index 8e728ef..5cf00f7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 7e65ad6..ae66dc7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index e76849c..8ad9072 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -44,13 +46,13 @@ class DeleteNodeTag(Method):
             raise PLCInvalidArgument, "No such node tag %r"%node_tag_id
         node_tag = node_tags[0]
 
-        ### reproducing a check from UpdateSliceAttribute, looks dumb though
+        ### reproducing a check from UpdateSliceTag, looks dumb though
         nodes = Nodes(self.api, [node_tag['node_id']])
         if not nodes:
             raise PLCInvalidArgument, "No such node %r"%node_tag['node_id']
         node = nodes[0]
 
-        assert node_tag['node_tag_id'] in node['tag_ids']
+        assert node_tag['node_tag_id'] in node['node_tag_ids']
 
        # check permission : it not admin, is the user affiliated with the right site
        if 'admin' not in self.caller['roles']:
diff --git a/PLC/Methods/DeleteNodeType.py b/PLC/Methods/DeleteNodeType.py
new file mode 100644 (file)
index 0000000..1b6fd38
--- /dev/null
@@ -0,0 +1,37 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeTypes import NodeType, NodeTypes
+from PLC.Auth import Auth
+
+class DeleteNodeType(Method):
+    """
+    Deletes a node node type.
+
+    WARNING: This will cause the deletion of all nodes in this boot
+    state.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        Auth(),
+        NodeType.fields['node_type']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+
+    def call(self, auth, name):
+        node_types = NodeTypes(self.api, [name])
+        if not node_types:
+            raise PLCInvalidArgument, "No such node type"
+        node_type = node_types[0]
+
+        node_type.delete()
+       
+        return 1
index 944882b..6b6c45f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index ab66520..b4c0ac7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d73c204..f27e117 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 4260d25..f5ba75d 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 448f808..a199aec 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -44,8 +46,8 @@ class DeletePerson(Method):
 
         person.delete()
        
-       # Logging variables
-       self.event_objects = {'Person': [person['person_id']]}
-       self.message = 'Person %d deleted' % person['person_id']
+        # Logging variables
+        self.event_objects = {'Person': [person['person_id']]}
+        self.message = 'Person %d deleted' % person['person_id']
 
         return 1
index db2af2d..0345100 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -48,9 +50,10 @@ class DeletePersonFromSite(Method):
         if site['site_id'] in person['site_ids']:
             site.remove_person(person)
 
-       # Logging variables
-       self.event_objects = {'Site': [site['site_id']],
-                             'Person': [person['person_id']]}  
-       self.message = 'Person %d deleted from site %d  ' % \
-               (person['person_id'], site['site_id'])
+        # Logging variables
+        self.event_objects = {'Site': [site['site_id']],
+                              'Person': [person['person_id']]} 
+        self.message = 'Person %d deleted from site %d ' % \
+                        (person['person_id'], site['site_id'])
+
         return 1
index c990ccd..d36a1c3 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/DeletePersonTag.py b/PLC/Methods/DeletePersonTag.py
new file mode 100644 (file)
index 0000000..51a1432
--- /dev/null
@@ -0,0 +1,71 @@
+# $Id: DeletePersonTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/DeletePersonTag.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.PersonTags import PersonTag, PersonTags
+from PLC.Persons import Person, Persons
+
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+
+class DeletePersonTag(Method):
+    """
+    Deletes the specified person setting
+
+    Attributes may require the caller to have a particular role in order
+    to be deleted, depending on the related tag type.
+    Admins may delete attributes of any slice or sliver.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user']
+
+    accepts = [
+        Auth(),
+        PersonTag.fields['person_tag_id']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Person'
+
+
+    def call(self, auth, person_tag_id):
+        person_tags = PersonTags(self.api, [person_tag_id])
+        if not person_tags:
+            raise PLCInvalidArgument, "No such person tag %r"%person_tag_id
+        person_tag = person_tags[0]
+
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        persons = Persons(self.api, [person_tag['person_id']])
+        if not persons:
+            raise PLCInvalidArgument, "No such person %r"%person_tag['person_id']
+        person = persons[0]
+
+        assert person_tag['person_tag_id'] in person['person_tag_ids']
+
+       # check permission : it not admin, is the user affiliated with the right person
+       if 'admin' not in self.caller['roles']:
+           # check caller is affiliated with this person's site
+           if len(set(person['site_ids']) & set(self.caller['site_ids'])) == 0:
+               raise PLCPermissionDenied, "Not a member of the person's sites: %s"%person['site_ids']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified person setting, requires role %d",required_min_role
+
+        person_tag.delete()
+       self.object_ids = [person_tag['person_tag_id']]
+
+        return 1
index f707280..fb5a515 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 151ba25..1506e9f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 3898f51..45bb861 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Method import Method
index c23fff4..8c7feb3 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -39,8 +41,9 @@ class DeleteSite(Method):
 
         site.delete()
        
-       # Logging variables
-       self.event_objects = {'Site': [site['site_id']]}
-       self.message = 'Site %d deleted' % site['site_id']      
+        # Logging variables
+        self.event_objects = {'Site': [site['site_id']]}
+        self.message = 'Site %d deleted' % site['site_id']     
 
+        
         return 1
diff --git a/PLC/Methods/DeleteSiteTag.py b/PLC/Methods/DeleteSiteTag.py
new file mode 100644 (file)
index 0000000..f1e06aa
--- /dev/null
@@ -0,0 +1,71 @@
+# $Id: DeleteSiteTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/DeleteSiteTag.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.SiteTags import SiteTag, SiteTags
+from PLC.Sites import Site, Sites
+
+from PLC.Nodes import Node, Nodes
+from PLC.Sites import Site, Sites
+
+class DeleteSiteTag(Method):
+    """
+    Deletes the specified site setting
+
+    Attributes may require the caller to have a particular role in order
+    to be deleted, depending on the related tag type.
+    Admins may delete attributes of any slice or sliver.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user']
+
+    accepts = [
+        Auth(),
+        SiteTag.fields['site_tag_id']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Site'
+
+
+    def call(self, auth, site_tag_id):
+        site_tags = SiteTags(self.api, [site_tag_id])
+        if not site_tags:
+            raise PLCInvalidArgument, "No such site tag %r"%site_tag_id
+        site_tag = site_tags[0]
+
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        sites = Sites(self.api, [site_tag['site_id']])
+        if not sites:
+            raise PLCInvalidArgument, "No such site %r"%site_tag['site_id']
+        site = sites[0]
+
+        assert site_tag['site_tag_id'] in site['site_tag_ids']
+
+       # check permission : it not admin, is the user affiliated with the right site
+       if 'admin' not in self.caller['roles']:
+           # check caller is affiliated with this site
+           if self.caller['person_id'] not in site['person_ids']:
+               raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified site setting, requires role %d",required_min_role
+
+        site_tag.delete()
+       self.object_ids = [site_tag['site_tag_id']]
+
+        return 1
index 297f8a9..d3bc8f4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 2390be5..22117e0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 8899d88..2d95851 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 5098a9d..9438ee3 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
similarity index 66%
rename from PLC/Methods/DeleteSliceAttribute.py
rename to PLC/Methods/DeleteSliceTag.py
index 06a99f3..7a574a3 100644 (file)
@@ -1,12 +1,14 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 from PLC.Slices import Slice, Slices
 from PLC.Nodes import Node, Nodes
 from PLC.Auth import Auth
 
-class DeleteSliceAttribute(Method):
+class DeleteSliceTag(Method):
     """
     Deletes the specified slice or sliver attribute.
 
@@ -23,23 +25,23 @@ class DeleteSliceAttribute(Method):
 
     accepts = [
         Auth(),
-        SliceAttribute.fields['slice_attribute_id']
+        SliceTag.fields['slice_tag_id']
         ]
 
     returns = Parameter(int, '1 if successful')
 
-    def call(self, auth, slice_attribute_id):
-        slice_attributes = SliceAttributes(self.api, [slice_attribute_id])
-        if not slice_attributes:
+    def call(self, auth, slice_tag_id):
+        slice_tags = SliceTags(self.api, [slice_tag_id])
+        if not slice_tags:
             raise PLCInvalidArgument, "No such slice attribute"
-        slice_attribute = slice_attributes[0]
+        slice_tag = slice_tags[0]
 
-        slices = Slices(self.api, [slice_attribute['slice_id']])
+        slices = Slices(self.api, [slice_tag['slice_id']])
         if not slices:
             raise PLCInvalidArgument, "No such slice"
         slice = slices[0]
 
-        assert slice_attribute['slice_attribute_id'] in slice['slice_attribute_ids']
+        assert slice_tag['slice_tag_id'] in slice['slice_tag_ids']
 
         if 'admin' not in self.caller['roles']:
             if self.caller['person_id'] in slice['person_ids']:
@@ -49,11 +51,11 @@ class DeleteSliceAttribute(Method):
             elif slice['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
 
-            if slice_attribute['min_role_id'] is not None and \
-               min(self.caller['role_ids']) > slice_attribute['min_role_id']:
+            if slice_tag['min_role_id'] is not None and \
+               min(self.caller['role_ids']) > slice_tag['min_role_id']:
                 raise PLCPermissioinDenied, "Not allowed to delete the specified attribute"
 
-        slice_attribute.delete()
-       self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
+        slice_tag.delete()
+       self.event_objects = {'SliceTag': [slice_tag['slice_tag_id']]}
 
         return 1
index 6a485e0..69c7bba 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index b993984..ece5fd1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import random
 import base64
 
@@ -47,7 +49,7 @@ class GenerateNodeConfFile(Method):
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to generate a configuration file for that node"
 
-       # Get node networks for this node
+       # Get interfaces for this node
         primary = None
         interfaces = Interfaces(self.api, node['interface_ids'])
         for interface in interfaces:
index d10be73..08256ca 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index b299295..a45cc87 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d556525..2eb52f9 100644 (file)
@@ -1,8 +1,10 @@
 # $Id$
+# $URL$
 import random
 import base64
 import os
 import os.path
+import time
 
 from PLC.Faults import *
 from PLC.Method import Method
@@ -11,20 +13,24 @@ from PLC.Auth import Auth
 
 from PLC.Nodes import Node, Nodes
 from PLC.Interfaces import Interface, Interfaces
-from PLC.InterfaceSettings import InterfaceSetting, InterfaceSettings
-from PLC.NodeTags import NodeTags
+from PLC.InterfaceTags import InterfaceTag, InterfaceTags
+from PLC.NodeTags import NodeTag, NodeTags
+
+from PLC.Accessors.Accessors_standard import *                 # import node accessors
 
 # could not define this in the class..
-boot_medium_actions = [ 'node-preview',
-                        'node-floppy',
-                        'node-iso',
-                        'node-usb',
-                        'generic-iso',
-                        'generic-usb',
-                        ]
+# create a dict with the allowed actions for each type of node
+allowed_actions = {
+                'regular' : [ 'node-preview',
+                              'node-floppy',
+                              'node-iso',
+                              'node-usb',
+                              'generic-iso',
+                              'generic-usb',
+                               ],
+                }
 
 # compute a new key
-# xxx used by GetDummyBoxMedium
 def compute_key():
     # Generate 32 random bytes
     bytes = random.sample(xrange(0, 256), 32)
@@ -40,7 +46,7 @@ class GetBootMedium(Method):
     This method is a redesign based on former, supposedly dedicated, 
     AdmGenerateNodeConfFile
 
-    As compared with its ancestor, this method provides a much more detailed
+    As compared with its ancestor, this method provides a much more 
     detailed interface, that allows to
     (*) either just preview the node config file -- in which case 
         the node key is NOT recomputed, and NOT provided in the output
@@ -51,7 +57,10 @@ class GetBootMedium(Method):
     (*) or just provide the generic ISO or USB boot images 
         in which case of course the node_id_or_hostname parameter is not used
 
-    action is expected among the following string constants
+    action is expected among the following string constants according the
+    node type value:
+
+    for a 'regular' node:
     (*) node-preview
     (*) node-floppy
     (*) node-iso
@@ -88,7 +97,7 @@ class GetBootMedium(Method):
 
     Options: an optional array of keywords. 
         options are not supported for generic images
-    Currently supported are
+      Currently supported are
         - 'partition' - for USB actions only
         - 'cramfs'
         - 'serial' or 'serial:<console_spec>'
@@ -96,7 +105,15 @@ class GetBootMedium(Method):
         it is expected to be a colon separated string denoting
         tty - baudrate - parity - bits
         e.g. ttyS0:115200:n:8
-
+        - 'variant:<variantname>'
+        passed to build.sh as -V <variant> 
+        variants are used to run a different kernel on the bootCD
+        see kvariant.sh for how to create a variant
+        - 'no-hangcheck' - disable hangcheck
+
+    Tags: the following tags are taken into account when attached to the node:
+        'serial', 'cramfs', 'kvariant', 'kargs', 'no-hangcheck'
+        
     Security:
         - Non-admins can only generate files for nodes at their sites.
         - Non-admins, when they provide a filename, *must* specify it in the %d area
@@ -114,13 +131,14 @@ class GetBootMedium(Method):
         Auth(),
         Mixed(Node.fields['node_id'],
               Node.fields['hostname']),
-        Parameter (str, "Action mode, expected in " + "|".join(boot_medium_actions)),
+        Parameter (str, "Action mode, expected value depends of the type of node"),
         Parameter (str, "Empty string for verbatim result, resulting file full path otherwise"),
         Parameter ([str], "Options"),
         ]
 
     returns = Parameter(str, "Node boot medium, either inlined, or filename, depending on the filename parameter")
 
+    # define globals for regular nodes, override later for other types
     BOOTCDDIR = "/usr/share/bootcd-@NODEFAMILY@/"
     BOOTCDBUILD = "/usr/share/bootcd-@NODEFAMILY@/build.sh"
     GENERICDIR = "/var/www/html/download-@NODEFAMILY@/"
@@ -139,9 +157,15 @@ class GetBootMedium(Method):
             raise PLCInvalidArgument, "Node hostname %s is invalid"%node['hostname']
         return parts
         
-    # plnode.txt content
+    # Generate the node (plnode.txt) configuration content.
+    #
+    # This function will create the configuration file a node
+    # composed by:
+    #  - a common part, regardless of the 'node_type' tag
+    #  - XXX a special part, depending on the 'node_type' tag value.
     def floppy_contents (self, node, renew_key):
 
+        # Do basic checks
         if node['peer_id'] is not None:
             raise PLCInvalidArgument, "Not a local node"
 
@@ -151,7 +175,7 @@ class GetBootMedium(Method):
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to generate a configuration file for %s"%node['hostname']
 
-        # Get node networks for this node
+        # Get interface for this node
         primary = None
         interfaces = Interfaces(self.api, node['interface_ids'])
         for interface in interfaces:
@@ -163,9 +187,9 @@ class GetBootMedium(Method):
 
         ( host, domain ) = self.split_hostname (node)
 
+        # renew the key and save it on the database
         if renew_key:
             node['key'] = compute_key()
-            # Save it
             node.sync()
 
         # Generate node configuration file suitable for BootCD
@@ -174,6 +198,8 @@ class GetBootMedium(Method):
         if renew_key:
             file += 'NODE_ID="%d"\n' % node['node_id']
             file += 'NODE_KEY="%s"\n' % node['key']
+            # not used anywhere, just a note for operations people
+            file += 'KEY_RENEWAL_DATE="%s"\n' % time.strftime('%Y/%m/%d at %H:%M +0000',time.gmtime())
 
         if primary['mac']:
             file += 'NET_DEVICE="%s"\n' % primary['mac'].lower()
@@ -193,7 +219,7 @@ class GetBootMedium(Method):
         file += 'DOMAIN_NAME="%s"\n' % domain
 
         # define various interface settings attached to the primary interface
-        settings = InterfaceSettings (self.api, {'interface_id':interface['interface_id']})
+        settings = InterfaceTags (self.api, {'interface_id':interface['interface_id']})
 
         categories = set()
         for setting in settings:
@@ -201,12 +227,12 @@ class GetBootMedium(Method):
                 categories.add(setting['category'])
         
         for category in categories:
-            category_settings = InterfaceSettings(self.api,{'interface_id':interface['interface_id'],
+            category_settings = InterfaceTags(self.api,{'interface_id':interface['interface_id'],
                                                               'category':category})
             if category_settings:
                 file += '### Category : %s\n'%category
                 for setting in category_settings:
-                    file += '%s_%s="%s"\n'%(category.upper(),setting['name'].upper(),setting['value'])
+                    file += '%s_%s="%s"\n'%(category.upper(),setting['tagname'].upper(),setting['value'])
 
         for interface in interfaces:
             if interface['method'] == 'ipmi':
@@ -217,30 +243,27 @@ class GetBootMedium(Method):
 
         return file
 
-    # see also InstallBootstrapFS in bootmanager that does similar things
-    def get_nodefamily (self, node):
-        # get defaults from the myplc build
-        try:
-            (pldistro,arch) = file("/etc/planetlab/nodefamily").read().strip().split("-")
-        except:
-            (pldistro,arch) = ("planetlab","i386")
-            
-        # with no valid argument, return system-wide defaults
+    # see also GetNodeFlavour that does similar things
+    def get_nodefamily (self, node, auth):
+        pldistro = self.api.config.PLC_FLAVOUR_NODE_PLDISTRO
+        fcdistro = self.api.config.PLC_FLAVOUR_NODE_FCDISTRO
+        arch = self.api.config.PLC_FLAVOUR_NODE_ARCH
         if not node:
-            return (pldistro,arch)
-
+            return (pldistro,fcdistro,arch)
+        
         node_id=node['node_id']
-        # cannot use accessors in the API itself
-        # the 'arch' tag type is assumed to exist, see db-config
-        arch_tags = NodeTags (self.api, {'tagname':'arch','node_id':node_id},['tagvalue'])
-        if arch_tags:
-            arch=arch_tags[0]['tagvalue']
-        # ditto
-        pldistro_tags = NodeTags (self.api, {'tagname':'pldistro','node_id':node_id},['tagvalue'])
-        if pldistro_tags:
-            pldistro=pldistro_tags[0]['tagvalue']
-
-        return (pldistro,arch)
+        
+        # no support for deployment-based BootCD's, use kvariants instead
+        node_pldistro = GetNodePldistro (self.api).call(auth, node_id)
+        if node_pldistro: pldistro = node_pldistro
+
+        node_fcdistro = GetNodeFcdistro (self.api).call(auth, node_id)
+        if node_fcdistro: fcdistro = node_fcdistro
+
+        node_arch = GetNodeArch (self.api).call(auth,node_id)
+        if node_arch: arch = node_arch
+        
+        return (pldistro,fcdistro,arch)
 
     def bootcd_version (self):
         try:
@@ -255,12 +278,86 @@ class GetBootMedium(Method):
             else:
                 os.unlink(file)
 
+    ### handle filename
+    # build the filename string 
+    # check for permissions and concurrency
+    # returns the filename
+    def handle_filename (self, filename, nodename, suffix, arch):
+        # allow to set filename to None or any other empty value
+        if not filename: filename=''
+        filename = filename.replace ("%d",self.WORKDIR)
+        filename = filename.replace ("%n",nodename)
+        filename = filename.replace ("%s",suffix)
+        filename = filename.replace ("%p",self.api.config.PLC_NAME)
+        # let's be cautious
+        try: filename = filename.replace ("%f", self.nodefamily)
+        except: pass
+        try: filename = filename.replace ("%a", arch)
+        except: pass
+        try: filename = filename.replace ("%v",self.bootcd_version())
+        except: pass
+
+        ### Check filename location
+        if filename != '':
+            if 'admin' not in self.caller['roles']:
+                if ( filename.index(self.WORKDIR) != 0):
+                    raise PLCInvalidArgument, "File %s not under %s"%(filename,self.WORKDIR)
+
+            ### output should not exist (concurrent runs ..)
+            if os.path.exists(filename):
+                raise PLCInvalidArgument, "Resulting file %s already exists"%filename
+
+            ### we can now safely create the file, 
+            ### either we are admin or under a controlled location
+            filedir=os.path.dirname(filename)
+            # dirname does not return "." for a local filename like its shell counterpart
+            if filedir:
+                if not os.path.exists(filedir):
+                    try:
+                        os.makedirs (filedir,0777)
+                    except:
+                        raise PLCPermissionDenied, "Could not create dir %s"%filedir
+
+        return filename
+
+    # Build the command line to be executed
+    # according the node type
+    def build_command(self, node_type, build_sh_spec, node_image, type, floppy_file, log_file):
+
+        command = ""
+
+        # regular node, make build's arguments
+        # and build the full command line to be called
+        if node_type == 'regular':
+
+            build_sh_options=""
+            if "cramfs" in build_sh_spec: 
+                type += "_cramfs"
+            if "serial" in build_sh_spec: 
+                build_sh_options += " -s %s"%build_sh_spec['serial']
+            if "variant" in build_sh_spec:
+                build_sh_options += " -V %s"%build_sh_spec['variant']
+            
+            for karg in build_sh_spec['kargs']:
+                build_sh_options += ' -k "%s"'%karg
+
+            log_file="%s.log"%node_image
+
+            command = '%s -f "%s" -o "%s" -t "%s" %s &> %s' % (self.BOOTCDBUILD,
+                                                                 floppy_file,
+                                                                 node_image,
+                                                                 type,
+                                                                 build_sh_options,
+                                                                 log_file)
+
+        if self.DEBUG:
+            print "The build command line is %s" % command
+
+        return command 
+
     def call(self, auth, node_id_or_hostname, action, filename, options = []):
 
         self.trash=[]
-        ### check action
-        if action not in boot_medium_actions:
-            raise PLCInvalidArgument, "Unknown action %s"%action
 
         ### compute file suffix and type
         if action.find("-iso") >= 0 :
@@ -273,36 +370,69 @@ class GetBootMedium(Method):
             suffix=".txt"
             type = "txt"
 
-        # handle / caconicalize options
+        # check for node existence and get node_type
+        nodes = Nodes(self.api, [node_id_or_hostname])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
+        node = nodes[0]
+
+        if self.DEBUG: print "%s required on node %s. Node type is: %s" \
+                % (action, node['node_id'], node['node_type'])
+
+        # check the required action against the node type
+        node_type = node['node_type']
+        if action not in allowed_actions[node_type]:
+            raise PLCInvalidArgument, "Action %s not valid for %s nodes, valid actions are %s" \
+                                   % (action, node_type, "|".join(allowed_actions[node_type]))
+
+        # handle / canonicalize options
         if type == "txt":
             if options:
                 raise PLCInvalidArgument, "Options are not supported for node configs"
         else:
             # create a dict for build.sh 
-            optdict={}
+            build_sh_spec={'kargs':[]}
+            # use node tags as defaults
+            # check for node tag equivalents
+            tags = NodeTags(self.api, 
+                            {'node_id': node['node_id'], 
+                             'tagname': ['serial', 'cramfs', 'kvariant', 'kargs', 'no-hangcheck']},
+                            ['tagname', 'value'])
+            if tags:
+                for tag in tags:
+                    if tag['tagname'] == 'serial':
+                        build_sh_spec['serial'] = tag['value']
+                    if tag['tagname'] == 'cramfs':
+                        build_sh_spec['cramfs'] = True
+                    if tag['tagname'] == 'kvariant':
+                        build_sh_spec['variant'] = tag['value']
+                    if tag['tagname'] == 'kargs':
+                        build_sh_spec['kargs'].append(tag['value'].split())
+                    if tag['tagname'] == 'no-hangcheck':
+                        build_sh_spec['kargs'].append('hcheck_reboot0')
+            # then options can override tags
             for option in options:
                 if option == "cramfs":
-                    optdict['cramfs']=True
+                    build_sh_spec['cramfs']=True
                 elif option == 'partition':
                     if type != "usb":
                         raise PLCInvalidArgument, "option 'partition' is for USB images only"
                     else:
                         type="usb_partition"
                 elif option == "serial":
-                    optdict['serial']='default'
+                    build_sh_spec['serial']='default'
                 elif option.find("serial:") == 0:
-                    optdict['serial']=option.replace("serial:","")
+                    build_sh_spec['serial']=option.replace("serial:","")
+                elif option.find("variant:") == 0:
+                    build_sh_spec['variant']=option.replace("variant:","")
+                elif option == "no-hangcheck":
+                    build_sh_spec['kargs'].append('hcheck_reboot0')
                 else:
                     raise PLCInvalidArgument, "unknown option %s"%option
 
-        ### check node if needed
+        # compute nodename according the action
         if action.find("node-") == 0:
-            nodes = Nodes(self.api, [node_id_or_hostname])
-            if not nodes:
-                raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
-            node = nodes[0]
             nodename = node['hostname']
-
         else:
             node = None
             # compute a 8 bytes random number
@@ -311,49 +441,22 @@ class GetBootMedium(Method):
             nodename = "".join(map(hexa2,tempbytes))
 
         # get nodefamily
-        (pldistro,arch) = self.get_nodefamily(node)
-        self.nodefamily="%s-%s"%(pldistro,arch)
+        (pldistro,fcdistro,arch) = self.get_nodefamily(node,auth)
+        self.nodefamily="%s-%s-%s"%(pldistro,fcdistro,arch)
+
         # apply on globals
         for attr in [ "BOOTCDDIR", "BOOTCDBUILD", "GENERICDIR" ]:
             setattr(self,attr,getattr(self,attr).replace("@NODEFAMILY@",self.nodefamily))
             
-        ### handle filename
-        # allow to set filename to None or any other empty value
-        if not filename: filename=''
-        filename = filename.replace ("%d",self.WORKDIR)
-        filename = filename.replace ("%n",nodename)
-        filename = filename.replace ("%s",suffix)
-        filename = filename.replace ("%p",self.api.config.PLC_NAME)
-        # let's be cautious
-        try: filename = filename.replace ("%f", self.nodefamily)
-        except: pass
-        try: filename = filename.replace ("%a", arch)
-        except: pass
-        try: filename = filename.replace ("%v",self.bootcd_version())
-        except: pass
-
-        ### Check filename location
-        if filename != '':
-            if 'admin' not in self.caller['roles']:
-                if ( filename.index(self.WORKDIR) != 0):
-                    raise PLCInvalidArgument, "File %s not under %s"%(filename,self.WORKDIR)
-
-            ### output should not exist (concurrent runs ..)
-            if os.path.exists(filename):
-                raise PLCInvalidArgument, "Resulting file %s already exists"%filename
-
-            ### we can now safely create the file, 
-            ### either we are admin or under a controlled location
-            filedir=os.path.dirname(filename)
-            # dirname does not return "." for a local filename like its shell counterpart
-            if filedir:
-                if not os.path.exists(filedir):
-                    try:
-                        os.makedirs (filedir,0777)
-                    except:
-                        raise PLCPermissionDenied, "Could not create dir %s"%filedir
-
+        filename = self.handle_filename(filename, nodename, suffix, arch)
         
+        # log call
+        if node:
+            self.message='GetBootMedium on node %s - action=%s'%(nodename,action)
+            self.event_objects={'Node': [ node ['node_id'] ]}
+        else:
+            self.message='GetBootMedium - generic - action=%s'%action
+
         ### generic media
         if action == 'generic-iso' or action == 'generic-usb':
             if options:
@@ -366,29 +469,35 @@ class GetBootMedium(Method):
             generic_path = "%s/%s" % (self.GENERICDIR,generic_name)
 
             if filename:
-                ret=os.system ("cp %s %s"%(generic_path,filename))
+                ret=os.system ('cp "%s" "%s"'%(generic_path,filename))
                 if ret==0:
                     return filename
                 else:
-                    raise PLCPermissionDenied, "Could not copy %s into"%(generic_path,filename)
+                    raise PLCPermissionDenied, "Could not copy %s into %s"%(generic_path,filename)
             else:
                 ### return the generic medium content as-is, just base64 encoded
                 return base64.b64encode(file(generic_path).read())
 
-       ### config file preview or regenerated
-       if action == 'node-preview' or action == 'node-floppy':
+        ### config file preview or regenerated
+        if action == 'node-preview' or action == 'node-floppy':
             renew_key = (action == 'node-floppy')
             floppy = self.floppy_contents (node,renew_key)
-           if filename:
-               try:
-                   file(filename,'w').write(floppy)
-               except:
-                   raise PLCPermissionDenied, "Could not write into %s"%filename
-               return filename
-           else:
-               return floppy
+            if filename:
+                try:
+                    file(filename,'w').write(floppy)
+                except:
+                    raise PLCPermissionDenied, "Could not write into %s"%filename
+                return filename
+            else:
+                return floppy
 
         ### we're left with node-iso and node-usb
+        # the steps involved in the image creation are:
+        # - create and test the working environment
+        # - generate the configuration file
+        # - build and invoke the build command
+        # - delivery the resulting image file
+
         if action == 'node-iso' or action == 'node-usb':
 
             ### check we've got required material
@@ -418,33 +527,26 @@ class GetBootMedium(Method):
                 self.trash.append(floppy_file)
 
                 node_image = "%s/%s%s"%(self.WORKDIR,nodename,suffix)
-
-                # make build's arguments
-                serial_arg=""
-                if "cramfs" in optdict: type += "_cramfs"
-                if "serial" in optdict: serial_arg = "-s %s"%optdict['serial']
                 log_file="%s.log"%node_image
-                # invoke build.sh
-                build_command = '%s -f "%s" -o "%s" -t "%s" %s &> %s' % (self.BOOTCDBUILD,
-                                                                         floppy_file,
-                                                                         node_image,
-                                                                         type,
-                                                                         serial_arg,
-                                                                         log_file)
-                if self.DEBUG:
-                    print 'build command:',build_command
-                ret=os.system(build_command)
+
+                command = self.build_command(node_type, build_sh_spec, node_image, type, floppy_file, log_file)
+
+                # invoke the image build script
+                if command != "":
+                    ret=os.system(command)
+
                 if ret != 0:
-                    raise PLCAPIError,"bootcd/build.sh failed\n%s\n%s"%(
-                        build_command,file(log_file).read())
+                    raise PLCAPIError, "%s failed Command line was: %s Error logs: %s" % \
+                              (self.BOOTCDBUILD,  command, file(log_file).read())
 
                 self.trash.append(log_file)
+
                 if not os.path.isfile (node_image):
                     raise PLCAPIError,"Unexpected location of build.sh output - %s"%node_image
             
                 # handle result
                 if filename:
-                    ret=os.system("mv %s %s"%(node_image,filename))
+                    ret=os.system('mv "%s" "%s"'%(node_image,filename))
                     if ret != 0:
                         self.trash.append(node_image)
                         self.cleantrash()
index 4cd31be..2e458bb 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 89d5250..0edf8b7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 02bcd68..432ae04 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 2bc989c..845eb3a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index c711e9d..9b63fe2 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index d8bb0f5..ffc75e6 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
similarity index 59%
rename from PLC/Methods/GetInterfaceSettings.py
rename to PLC/Methods/GetInterfaceTags.py
index c532d7c..6daa3f9 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -9,16 +11,16 @@ from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Auth import Auth
 
-from PLC.InterfaceSettings import InterfaceSetting, InterfaceSettings
+from PLC.InterfaceTags import InterfaceTag, InterfaceTags
 from PLC.Sites import Site, Sites
 from PLC.Interfaces import Interface, Interfaces
 
-class GetInterfaceSettings(Method):
+class GetInterfaceTags(Method):
     """
     Returns an array of structs containing details about
     interfaces and related settings.
 
-    If interface_setting_filter is specified and is an array of
+    If interface_tag_filter is specified and is an array of
     interface setting identifiers, only interface settings matching
     the filter will be returned. If return_fields is specified, only
     the specified details will be returned.
@@ -28,17 +30,17 @@ class GetInterfaceSettings(Method):
 
     accepts = [
         Auth(),
-        Mixed([InterfaceSetting.fields['interface_setting_id']],
+        Mixed([InterfaceTag.fields['interface_tag_id']],
               Parameter(int,"Interface setting id"),
-              Filter(InterfaceSetting.fields)),
+              Filter(InterfaceTag.fields)),
         Parameter([str], "List of fields to return", nullok = True)
         ]
 
-    returns = [InterfaceSetting.fields]
+    returns = [InterfaceTag.fields]
     
 
-    def call(self, auth, interface_setting_filter = None, return_fields = None):
+    def call(self, auth, interface_tag_filter = None, return_fields = None):
 
-        interface_settings = InterfaceSettings(self.api, interface_setting_filter, return_fields)
+        interface_tags = InterfaceTags(self.api, interface_tag_filter, return_fields)
 
-        return interface_settings
+        return interface_tags
index 2bd7f7c..2c6b6b4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -7,11 +9,11 @@ from PLC.Auth import Auth
 
 class GetInterfaces(Method):
     """
-    Returns an array of structs containing details about node network
-    interfacess. If interfaces_filter is specified and is an array
-    of node network identifiers, or a struct of node network
-    fields and values, only node network interfaces matching the filter
-    will be returned.
+    Returns an array of structs containing details about network
+    interfaces. If interfaces_filter is specified and is an array of
+    interface identifiers, or a struct of interface fields and
+    values, only interfaces matching the filter will be
+    returned.
 
     If return_fields is given, only the specified details will be returned.
     """
@@ -20,8 +22,10 @@ class GetInterfaces(Method):
 
     accepts = [
         Auth(),
-        Mixed([Interface.fields['interface_id']],
+        Mixed([Mixed(Interface.fields['interface_id'],
+                     Interface.fields['ip'])],
               Parameter (int, "interface id"),
+              Parameter (str, "ip address"),
               Filter(Interface.fields)),
         Parameter([str], "List of fields to return", nullok = True)
         ]
index 32bb658..c6ccb35 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 2d7550c..9895710 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index b0eb44e..cc18ca4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index cee914a..8eea15e 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index dbddd9f..c1ced07 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/GetNodeFlavour.py b/PLC/Methods/GetNodeFlavour.py
new file mode 100644 (file)
index 0000000..f1e258d
--- /dev/null
@@ -0,0 +1,78 @@
+# $Id$
+# $URL$
+from PLC.Method import Method
+from PLC.Auth import Auth
+from PLC.Faults import *
+from PLC.Parameter import *
+from PLC.Nodes import Node, Nodes
+
+from PLC.Accessors.Accessors_standard import *                 # import node accessors
+
+class GetNodeFlavour(Method):
+    """
+    Returns detailed information on a given node's flavour, i.e. its
+    base installation. 
+
+    This depends on the global PLC settings in the PLC_FLAVOUR area,
+    optionnally overridden by any of the following tags if set on that node:
+
+    'arch', 'pldistro', 'fcdistro', (xxx fcdistro not yet supported)
+    'deployment', 'extensions',
+    """
+
+    roles = ['admin', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname']),
+        ]
+
+    returns = { 
+        'nodefamily' : Parameter (str, "the nodefamily this node should be based upon"),
+        'extensions' : [ Parameter (str, "extensions to add to the base install") ],
+        'plain' : Parameter (bool, "use plain bootstrapfs image if set (for tests)" ) ,
+        }
+
+    
+    ########## nodefamily
+    def nodefamily (self, auth, node_id, fcdistro, arch):
+
+        # the deployment tag, if set, wins
+        deployment = GetNodeDeployment (self.api).call(auth,node_id)
+        if deployment: return deployment
+
+        pldistro = GetNodePldistro (self.api).call(auth, node_id)
+        if not pldistro: pldistro = self.api.config.PLC_FLAVOUR_NODE_PLDISTRO
+
+        # xxx would make sense to check the corresponding bootstrapfs is available
+        return "%s-%s-%s"%(pldistro,fcdistro,arch)
+
+    def extensions (self, auth, node_id, fcdistro, arch):
+        try:
+            return [ "%s-%s-%s"%(e,fcdistro,arch) for e in GetNodeExtensions(self.api).call(auth,node_id).split() ]
+        except:
+            return []
+
+    def plain (self, auth, node_id):
+        return not not GetNodePlainBootstrapfs(self.api).call(auth,node_id)
+
+    def call(self, auth, node_id_or_name):
+        # Get node information
+        nodes = Nodes(self.api, [node_id_or_name])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node %r"%node_id_or_name
+        node = nodes[0]
+        node_id = node['node_id']
+
+        arch = GetNodeArch (self.api).call(auth,node_id)
+        if not arch: arch = self.api.config.PLC_FLAVOUR_NODE_ARCH
+
+        fcdistro = GetNodeFcdistro (self.api).call(auth, node_id)
+        if not fcdistro: fcdistro = self.api.config.PLC_FLAVOUR_NODE_FCDISTRO
+
+        # xxx could use some sanity checking, and could provide fallbacks
+        return { 'nodefamily' : self.nodefamily(auth,node_id, fcdistro, arch),
+                 'extensions' : self.extensions(auth,node_id, fcdistro, arch),
+                 'plain' : self.plain(auth,node_id),
+                 }
index 5711217..6a59556 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -5,7 +7,7 @@ from PLC.Filter import Filter
 from PLC.Auth import Auth
 from PLC.NodeGroups import NodeGroup, NodeGroups
 
-class GetNodeGroups(Method):
+class v43GetNodeGroups(Method):
     """
     Returns an array of structs containing details about node groups.
     If nodegroup_filter is specified and is an array of node group
@@ -28,3 +30,54 @@ class GetNodeGroups(Method):
   
     def call(self, auth, nodegroup_filter = None, return_fields = None):
        return NodeGroups(self.api, nodegroup_filter, return_fields)
+
+
+nodegroup_fields = NodeGroup.fields.copy()
+nodegroup_fields['name'] = Parameter(str, "Legacy version of groupname", max = 50),
+
+class v42GetNodeGroups(v43GetNodeGroups):
+    """
+    Legacy wrapper for v42GetNodeGroups.
+    """
+
+    accepts = [
+        Auth(),
+        Mixed([Mixed(NodeGroup.fields['nodegroup_id'],
+                     NodeGroup.fields['groupname'])],
+              Filter(nodegroup_fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [nodegroup_fields]
+  
+    def call(self, auth, nodegroup_filter = None, return_fields = None):
+        # convert name -> groupname in both filters
+        if isinstance(nodegroup_filter, dict):
+            if nodegroup_filter.has_key('name'):
+                groupname = nodegroup_filter.pop('name')
+                if not nodegroup_filter.has_key('groupname'):
+                    nodegroup_filter['groupname']=groupname
+
+        if isinstance(return_fields, list):
+            if 'name' in return_fields:
+                return_fields.remove('name')
+                if 'groupname' not in return_fields:
+                    return_fields.append('groupname')
+
+        nodegroups = NodeGroups(self.api, nodegroup_filter, return_fields)
+        # if groupname is present, then create a name mapping
+        for nodegroup in nodegroups:
+            if nodegroup.has_key('groupname'):
+                nodegroup['name']=nodegroup['groupname']
+        return nodegroups
+
+class GetNodeGroups(v42GetNodeGroups):
+    """
+    Returns an array of structs containing details about node groups.
+    If nodegroup_filter is specified and is an array of node group
+    identifiers or names, or a struct of node group attributes, only
+    node groups matching the filter will be returned. If return_fields
+    is specified, only the specified details will be returned.
+    """
+
+    pass
index fd53dcd..e7710c6 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
diff --git a/PLC/Methods/GetNodeTypes.py b/PLC/Methods/GetNodeTypes.py
new file mode 100644 (file)
index 0000000..6f90469
--- /dev/null
@@ -0,0 +1,24 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeTypes import NodeType, NodeTypes
+from PLC.Auth import Auth
+
+class GetNodeTypes(Method):
+    """
+    Returns an array of all valid node node types.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+    accepts = [
+        Auth()
+        ]
+
+    returns = [NodeType.fields['node_type']]
+    
+
+    def call(self, auth):
+        return [node_type['node_type'] for node_type in NodeTypes(self.api)]
index f4b577d..90be951 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -6,13 +8,19 @@ from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
 from PLC.Auth import Auth
 
-class GetNodes(Method):
+admin_only = ['key', 'session', 'boot_nonce' ]
+
+class v43GetNodes(Method):
     """
     Returns an array of structs containing details about nodes. If
     node_filter is specified and is an array of node identifiers or
     hostnames, or a struct of node attributes, only nodes matching the
-    filter will be returned. If return_fields is specified, only the
-    specified details will be returned.
+    filter will be returned. 
+
+    If return_fields is specified, only the specified details will be
+    returned. NOTE that if return_fields is unspecified, the complete
+    set of native fields are returned, which DOES NOT include tags at
+    this time.
 
     Some fields may only be viewed by admins.
     """
@@ -70,7 +78,7 @@ class GetNodes(Method):
 
            # remove remaining admin only fields
             for node in nodes:    
-               for field in ['boot_nonce', 'key', 'session', 'root_person_ids']:
+               for field in admin_only:
                     if field in node:
                         del node[field]
        
@@ -81,3 +89,55 @@ class GetNodes(Method):
                    del node[field]     
 
         return nodes
+
+node_fields = Node.fields.copy()
+node_fields['nodenetwork_ids']=Parameter([int], "Legacy version of interface_ids")
+
+class v42GetNodes(v43GetNodes):
+    """
+    Legacy wrapper for v43GetNodes.
+    """
+
+    accepts = [
+        Auth(),
+        Mixed([Mixed(Node.fields['node_id'],
+                     Node.fields['hostname'])],
+             Parameter(str,"hostname"),
+              Parameter(int,"node_id"),
+              Filter(node_fields)),
+        Parameter([str], "List of fields to return", nullok = True),
+        ]
+    returns = [node_fields]
+
+    def call(self, auth, node_filter = None, return_fields = None):
+        # convert nodenetwork_ids -> interface_ids
+        if isinstance(node_filter, dict):
+            if node_filter.has_key('nodenetwork_ids'):
+                interface_ids = node_filter.pop('nodenetwork_ids')
+                if not node_filter.has_key('interface_ids'):
+                    node_filter['interface_ids']=interface_ids
+
+        if isinstance(return_fields, list):
+            if 'nodenetwork_ids' in return_fields:
+                return_fields.remove('nodenetwork_ids')
+                if 'interface_ids' not in return_fields:
+                    return_fields.append('interface_ids')
+        nodes = v43GetNodes.call(self,auth,node_filter,return_fields)
+        # if interface_ids are present, then create a nodenetwork_ids mapping
+        for node in nodes:
+            if node.has_key('interface_ids'):
+                node['nodenetwork_ids']=node['interface_ids']
+        return nodes
+
+class GetNodes(v42GetNodes):
+    """
+    Returns an array of structs containing details about nodes. If
+    node_filter is specified and is an array of node identifiers or
+    hostnames, or a struct of node attributes, only nodes matching the
+    filter will be returned. If return_fields is specified, only the
+    specified details will be returned.
+
+    Some fields may only be viewed by admins.
+    """
+
+    pass
index 44f9380..a75a6b0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 2a81508..7d50821 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index ee9ab4d..aee8f4a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 113fe86..43a77aa 100644 (file)
@@ -2,6 +2,7 @@
 # Thierry Parmentelat - INRIA
 # 
 # $Id$
+# $URL$
 
 import time
 
@@ -17,7 +18,7 @@ from PLC.Keys import Key, Keys
 from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
 from PLC.Slices import Slice, Slices
-from PLC.SliceAttributes import SliceAttributes
+from PLC.SliceTags import SliceTags
 
 class GetPeerData(Method):
     """
@@ -67,7 +68,7 @@ class GetPeerData(Method):
                                     'peer_id': None}, person_fields)
 
        # filter out system slices
-        system_slice_ids = SliceAttributes(self.api, {'name': 'system', 'value': '1'}).dict('slice_id')
+        system_slice_ids = SliceTags(self.api, {'name': 'system', 'value': '1'}).dict('slice_id')
        slices = Slices(self.api, {'peer_id': None,
                                   '~slice_id':system_slice_ids.keys()})
        
index 30fbd94..5b194fa 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter
 from PLC.Auth import Auth
index e93fe36..97f96d0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 # 
diff --git a/PLC/Methods/GetPersonTags.py b/PLC/Methods/GetPersonTags.py
new file mode 100644 (file)
index 0000000..516638f
--- /dev/null
@@ -0,0 +1,44 @@
+# $Id: GetPersonTags.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/GetPersonTags.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+
+from PLC.PersonTags import PersonTag, PersonTags
+
+class GetPersonTags(Method):
+    """
+    Returns an array of structs containing details about
+    persons and related settings.
+
+    If person_tag_filter is specified and is an array of
+    person setting identifiers, only person settings matching
+    the filter will be returned. If return_fields is specified, only
+    the specified details will be returned.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed([PersonTag.fields['person_tag_id']],
+              Parameter(int,"Person setting id"),
+              Filter(PersonTag.fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [PersonTag.fields]
+    
+
+    def call(self, auth, person_tag_filter = None, return_fields = None):
+
+        person_tags = PersonTags(self.api, person_tag_filter, return_fields)
+
+        return person_tags
index 5228933..7ef68b2 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -65,7 +67,7 @@ class GetPersons(Method):
         # Must query at least person_id, site_ids, and role_ids (see
         # Person.can_view() and below).
         if return_fields is not None:
-            added_fields = set(['person_id', 'site_ids', 'role_ids']).difference(return_fields)
+            added_fields = set(['person_id', 'site_ids', 'role_ids','roles']).difference(return_fields)
             return_fields += added_fields
         else:
             added_fields = []
index b7d238f..9f4ca94 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Auth import Auth
 from PLC.Faults import *
index 0456662..ae1c314 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index ae75219..70f39b4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Method import Method
index a72553c..e80fb50 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/GetSiteTags.py b/PLC/Methods/GetSiteTags.py
new file mode 100644 (file)
index 0000000..4663e8a
--- /dev/null
@@ -0,0 +1,45 @@
+# $Id: GetSiteTags.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/GetSiteTags.py $
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 14587 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+
+from PLC.SiteTags import SiteTag, SiteTags
+from PLC.Sites import Site, Sites
+
+class GetSiteTags(Method):
+    """
+    Returns an array of structs containing details about
+    sites and related settings.
+
+    If site_tag_filter is specified and is an array of
+    site setting identifiers, only site settings matching
+    the filter will be returned. If return_fields is specified, only
+    the specified details will be returned.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed([SiteTag.fields['site_tag_id']],
+              Parameter(int,"Site setting id"),
+              Filter(SiteTag.fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [SiteTag.fields]
+    
+
+    def call(self, auth, site_tag_filter = None, return_fields = None):
+
+        site_tags = SiteTags(self.api, site_tag_filter, return_fields)
+
+        return site_tags
index c0f198e..5a5d657 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
diff --git a/PLC/Methods/GetSliceFamily.py b/PLC/Methods/GetSliceFamily.py
new file mode 100644 (file)
index 0000000..49aee9b
--- /dev/null
@@ -0,0 +1,58 @@
+# $Id$
+# $URL$
+from PLC.Method import Method
+from PLC.Auth import Auth
+from PLC.Faults import *
+from PLC.Parameter import *
+from PLC.Slices import Slice, Slices
+
+from PLC.Accessors.Accessors_standard import *                 # import slice accessors
+
+class GetSliceFamily(Method):
+    """
+    Returns the slice vserver reference image that a given slice
+    should be based on. This depends on the global PLC settings in the
+    PLC_FLAVOUR area, optionnally overridden by any of the 'vref',
+    'arch', 'pldistro', 'fcdistro' tag if set on the slice.
+    """
+
+    roles = ['admin', 'user', 'node']
+
+    # don't support sliver-specific settings yet
+    accepts = [
+        Auth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        ]
+
+    returns = Parameter (str, "the slicefamily this slice should be based upon")
+
+    # 
+    ### system slices - at least planetflow - still rely on 'vref'
+    # 
+    def call(self, auth, slice_id_or_name):
+        # Get slice information
+        slices = Slices(self.api, [slice_id_or_name])
+        if not slices:
+            raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
+        slice = slices[0]
+        slice_id = slice['slice_id']
+
+        arch = GetSliceArch (self.api).call(auth,slice_id)
+        if not arch: arch = self.api.config.PLC_FLAVOUR_SLICE_ARCH
+
+        pldistro = GetSlicePldistro (self.api).call(auth, slice_id)
+        if not pldistro: pldistro = self.api.config.PLC_FLAVOUR_SLICE_PLDISTRO
+
+        fcdistro = GetSliceFcdistro (self.api).call(auth, slice_id)
+        if not fcdistro: fcdistro = self.api.config.PLC_FLAVOUR_SLICE_FCDISTRO
+
+        # the vref tag, if set, wins over pldistro
+        vref = GetSliceVref (self.api).call(auth,slice_id)
+
+        # xxx would make sense to check the corresponding vserver rpms are available
+        # in all node-families yum repos (and yumgroups, btw)
+        if vref: 
+            return "%s-%s-%s"%(vref,fcdistro,arch)
+        else:
+            return "%s-%s-%s"%(pldistro,fcdistro,arch)
index 174c209..f073d6e 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 4029c83..7306a01 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
similarity index 59%
rename from PLC/Methods/GetSliceAttributes.py
rename to PLC/Methods/GetSliceTags.py
index b8a0a11..41ed3e9 100644 (file)
@@ -1,18 +1,20 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 from PLC.Persons import Person, Persons
 from PLC.Sites import Site, Sites
 from PLC.Slices import Slice, Slices
 from PLC.Auth import Auth
 
-class GetSliceAttributes(Method):
+class GetSliceTags(Method):
     """
     Returns an array of structs containing details about slice and
     sliver attributes. An attribute is a sliver attribute if the
-    node_id field is set. If slice_attribute_filter is specified and
+    node_id field is set. If slice_tag_filter is specified and
     is an array of slice attribute identifiers, or a struct of slice
     attribute attributes, only slice attributes matching the filter
     will be returned. If return_fields is specified, only the
@@ -28,15 +30,15 @@ class GetSliceAttributes(Method):
 
     accepts = [
         Auth(),
-        Mixed([SliceAttribute.fields['slice_attribute_id']],
-              Filter(SliceAttribute.fields)),
+        Mixed([SliceTag.fields['slice_tag_id']],
+              Filter(SliceTag.fields)),
         Parameter([str], "List of fields to return", nullok = True)
         ]
 
-    returns = [SliceAttribute.fields]
+    returns = [SliceTag.fields]
     
 
-    def call(self, auth, slice_attribute_filter = None, return_fields = None):
+    def call(self, auth, slice_tag_filter = None, return_fields = None):
        # If we are not admin, make sure to only return our own slice
        # and sliver attributes.
         if isinstance(self.caller, Person) and \
@@ -52,37 +54,37 @@ class GetSliceAttributes(Method):
                 return []
 
             # Get slice attributes that we are able to view
-            valid_slice_attribute_ids = []
+            valid_slice_tag_ids = []
             slices = Slices(self.api, valid_slice_ids)
             for slice in slices:
-                valid_slice_attribute_ids += slice['slice_attribute_ids']
+                valid_slice_tag_ids += slice['slice_tag_ids']
 
-            if not valid_slice_attribute_ids:
+            if not valid_slice_tag_ids:
                 return []
 
-            if slice_attribute_filter is None:
-                slice_attribute_filter = valid_slice_attribute_ids
+            if slice_tag_filter is None:
+                slice_tag_filter = valid_slice_tag_ids
 
-        # Must query at least slice_attribute_id (see below)
-        if return_fields is not None and 'slice_attribute_id' not in return_fields:
-            return_fields.append('slice_attribute_id')
+        # Must query at least slice_tag_id (see below)
+        if return_fields is not None and 'slice_tag_id' not in return_fields:
+            return_fields.append('slice_tag_id')
             added_fields = True
         else:
             added_fields = False
 
-        slice_attributes = SliceAttributes(self.api, slice_attribute_filter, return_fields)
+        slice_tags = SliceTags(self.api, slice_tag_filter, return_fields)
 
         # Filter out slice attributes that are not viewable
         if isinstance(self.caller, Person) and \
            'admin' not in self.caller['roles']:
-            slice_attributes = filter(lambda slice_attribute: \
-                                      slice_attribute['slice_attribute_id'] in valid_slice_attribute_ids,
-                                      slice_attributes)
+            slice_tags = filter(lambda slice_tag: \
+                                      slice_tag['slice_tag_id'] in valid_slice_tag_ids,
+                                      slice_tags)
 
-        # Remove slice_attribute_id if not specified
+        # Remove slice_tag_id if not specified
         if added_fields:
-            for slice_attribute in slice_attributes:
-               if 'slice_attribute_id' in slice_attribute:
-                   del slice_attribute['slice_attribute_id']
+            for slice_tag in slice_tags:
+               if 'slice_tag_id' in slice_tag:
+                   del slice_tag['slice_tag_id']
 
-        return slice_attributes
+        return slice_tags
index f985c28..cbf4d95 100644 (file)
@@ -1,4 +1,5 @@
 # $Id$
+# $URL$
 
 import time
 
@@ -62,7 +63,7 @@ class GetSliceTicket(Method):
         data = {
             'timestamp': int(time.time()),
            'initscripts': initscripts,
-            'slivers': get_slivers(self.api, [slice['slice_id']]),
+            'slivers': get_slivers(self.api, auth, [slice['slice_id']]),
             }
 
         # Sign ticket
index 63dc0b4..0d34bbf 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
@@ -6,7 +8,7 @@ from PLC.Persons import Person, Persons
 from PLC.Sites import Site, Sites
 from PLC.Slices import Slice, Slices
 
-class GetSlices(Method):
+class v43GetSlices(Method):
     """
     Returns an array of structs containing details about slices. If
     slice_filter is specified and is an array of slice identifiers or
@@ -73,3 +75,58 @@ class GetSlices(Method):
                    del slice['slice_id']
 
         return slices
+
+slice_fields = Slice.fields.copy()
+slice_fields['slice_attribute_ids']=Parameter([int], "Legacy version of slice_tag_ids")
+
+class v42GetSlices(v43GetSlices):
+    """
+    Legacy wrapper for v43GetSlices.
+    """
+
+    accepts = [
+        Auth(),
+        Mixed([Mixed(Slice.fields['slice_id'],
+                     Slice.fields['name'])],
+              Parameter(str,"name"),
+              Parameter(int,"slice_id"),
+              Filter(slice_fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [slice_fields]
+
+    def call(self, auth, slice_filter = None, return_fields = None):
+        # convert nodenetwork_ids -> interface_ids
+        if isinstance(slice_filter, dict):
+            if slice_filter.has_key('slice_attribute_ids'):
+                slice_tag_ids = slice_filter.pop('slice_attribute_ids') 
+                if not slice_filter.has_key('slice_tag_ids'):
+                    slice_filter['slice_tag_ids']=slice_tag_ids
+        if isinstance(return_fields, list):
+            if 'slice_attribute_ids' in return_fields:
+                return_fields.remove('slice_attribute_ids')
+                if 'slice_tag_ids' not in return_fields:
+                    return_fields.append('slice_tag_ids')
+        slices = v43GetSlices.call(self,auth,slice_filter,return_fields)
+        # add in a slice_tag_ids -> slice_attribute_ids
+        for slice in slices:
+            if slice.has_key('slice_tag_ids'):
+                slice['slice_attribute_ids']=slice['slice_tag_ids']
+        return slices
+
+class GetSlices(v42GetSlices):
+    """
+    Returns an array of structs containing details about slices. If
+    slice_filter is specified and is an array of slice identifiers or
+    slice names, or a struct of slice attributes, only slices matching
+    the filter will be returned. If return_fields is specified, only the
+    specified details will be returned.
+
+    Users may only query slices of which they are members. PIs may
+    query any of the slices at their sites. Admins and nodes may query
+    any slice. If a slice that cannot be queried is specified in
+    slice_filter, details about that slice will not be returned.
+    """
+
+    pass
diff --git a/PLC/Methods/GetSlicesMD5.py b/PLC/Methods/GetSlicesMD5.py
deleted file mode 100644 (file)
index b7e4cde..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-from PLC.Faults import *
-from PLC.Method import Method
-from PLC.Parameter import Parameter, Mixed
-from PLC.Auth import Auth
-
-class GetSlicesMD5(Method):
-    """
-    Returns the current md5 hash of slices.xml file
-    (slices-0.5.xml.md5)
-    """
-
-    roles = ['admin', 'pi', 'user', 'tech', 'node']
-
-    accepts = [
-        Auth(),
-        ]
-
-    returns = Parameter(str, "MD5 hash of slices.xml")
-    
-
-    def call(self, auth):
-       try:
-           file_path = '/var/www/html/xml/slices-0.5.xml.md5'
-           slices_md5 = file(file_path).readline().strip()
-           if slices_md5 <> "":                    
-               return slices_md5
-           raise PLCInvalidArgument, "File is empty"
-       except IOError:
-           raise PLCInvalidArgument, "No such file"
-       
index 9c32fce..5a129ca 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Faults import *
@@ -11,20 +13,26 @@ from PLC.NodeGroups import NodeGroup, NodeGroups
 from PLC.ConfFiles import ConfFile, ConfFiles
 from PLC.Slices import Slice, Slices
 from PLC.Persons import Person, Persons
+from PLC.Sites import Sites
+from PLC.Roles import Roles
 from PLC.Keys import Key, Keys
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 from PLC.InitScripts import InitScript, InitScripts
+from PLC.Methods.GetSliceFamily import GetSliceFamily
 
-def get_slivers(api, slice_filter, node = None):
+# XXX used to check if slice expiration time is sane
+MAXINT =  2L**31-1
+
+def get_slivers(api, auth, slice_filter, node = None):
     # Get slice information
-    slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_attribute_ids'])
+    slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
 
     # Build up list of users and slice attributes
     person_ids = set()
-    slice_attribute_ids = set()
+    slice_tag_ids = set()
     for slice in slices:
         person_ids.update(slice['person_ids'])
-        slice_attribute_ids.update(slice['slice_attribute_ids'])
+        slice_tag_ids.update(slice['slice_tag_ids'])
 
     # Get user information
     all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
@@ -38,7 +46,7 @@ def get_slivers(api, slice_filter, node = None):
     all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
 
     # Get slice attributes
-    all_slice_attributes = SliceAttributes(api, slice_attribute_ids).dict()
+    all_slice_tags = SliceTags(api, slice_tag_ids).dict()
 
     slivers = []
     for slice in slices:
@@ -57,10 +65,10 @@ def get_slivers(api, slice_filter, node = None):
         attributes = []
 
         # All (per-node and global) attributes for this slice
-        slice_attributes = []
-        for slice_attribute_id in slice['slice_attribute_ids']:
-            if slice_attribute_id in all_slice_attributes:
-                slice_attributes.append(all_slice_attributes[slice_attribute_id])
+        slice_tags = []
+        for slice_tag_id in slice['slice_tag_ids']:
+            if slice_tag_id in all_slice_tags:
+                slice_tags.append(all_slice_tags[slice_tag_id])
 
         # Per-node sliver attributes take precedence over global
         # slice attributes, so set them first.
@@ -69,27 +77,34 @@ def get_slivers(api, slice_filter, node = None):
         sliver_attributes = []
 
         if node is not None:
-            for sliver_attribute in filter(lambda a: a['node_id'] == node['node_id'], slice_attributes):
+            for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
                 sliver_attributes.append(sliver_attribute['tagname'])
                 attributes.append({'tagname': sliver_attribute['tagname'],
                                    'value': sliver_attribute['value']})
 
            # set nodegroup slice attributes
-           for slice_attribute in filter(lambda a: a['nodegroup_id'] in node['nodegroup_ids'], slice_attributes):
+           for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
                # Do not set any nodegroup slice attributes for
                 # which there is at least one sliver attribute
                 # already set.
-               if slice_attribute['tagname'] not in slice_attributes:
-                   attributes.append({'tagname': slice_attribute['tagname'],
-                                  'value': slice_attribute['value']})
+               if slice_tag not in slice_tags:
+                   attributes.append({'tagname': slice_tag['tagname'],
+                                  'value': slice_tag['value']})
 
-        for slice_attribute in filter(lambda a: a['node_id'] is None, slice_attributes):
+        for slice_tag in [ a for a in slice_tags if a['node_id'] is None ]:
             # Do not set any global slice attributes for
             # which there is at least one sliver attribute
             # already set.
-            if slice_attribute['tagname'] not in sliver_attributes:
-                attributes.append({'tagname': slice_attribute['tagname'],
-                                   'value': slice_attribute['value']})
+            if slice_tag['tagname'] not in sliver_attributes:
+                attributes.append({'tagname': slice_tag['tagname'],
+                                   'value': slice_tag['value']})
+
+        # XXX Sanity check; though technically this should be a system invariant
+        # checked with an assertion
+        if slice['expires'] > MAXINT:  slice['expires']= MAXINT
+
+        # expose the slice vref as computed by GetSliceFamily
+        family = GetSliceFamily (api).call(auth, slice['slice_id'])
 
         slivers.append({
             'name': slice['name'],
@@ -97,12 +112,13 @@ def get_slivers(api, slice_filter, node = None):
             'instantiation': slice['instantiation'],
             'expires': slice['expires'],
             'keys': keys,
-            'attributes': attributes
+            'attributes': attributes,
+            'GetSliceFamily': family,
             })
 
     return slivers
 
-class GetSlivers(Method):
+class v43GetSlivers(Method):
     """
     Returns a struct containing information about the specified node
     (or calling node, if called by a node and node_id_or_hostname is
@@ -130,6 +146,13 @@ class GetSlivers(Method):
         'groups': [NodeGroup.fields['groupname']],
         'conf_files': [ConfFile.fields],
        'initscripts': [InitScript.fields],
+        'accounts': [{
+            'name': Parameter(str, "unix style account name", max = 254),
+            'keys': [{
+                'key_type': Key.fields['key_type'],
+                'key': Key.fields['key']
+            }],
+            }],
         'slivers': [{
             'name': Slice.fields['name'],
             'slice_id': Slice.fields['slice_id'],
@@ -140,8 +163,8 @@ class GetSlivers(Method):
                 'key': Key.fields['key']
             }],
             'attributes': [{
-                'tagname': SliceAttribute.fields['tagname'],
-                'value': SliceAttribute.fields['value']
+                'tagname': SliceTag.fields['tagname'],
+                'value': SliceTag.fields['value']
             }]
         }]
     }
@@ -203,15 +226,56 @@ class GetSlivers(Method):
        initscripts = InitScripts(self.api, {'enabled': True})  
 
         # Get system slices
-        system_slice_attributes = SliceAttributes(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
-        system_slice_ids = system_slice_attributes.keys()
+        system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
+        system_slice_ids = system_slice_tags.keys()
        
        # Get nm-controller slices
+        # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
        controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
        controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
        slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
 
-       slivers = get_slivers(self.api, slice_ids, node)
+       slivers = get_slivers(self.api, auth, slice_ids, node)
+
+        # get the special accounts and keys needed for the node
+        # root
+        # site_admin
+        accounts = []
+        if False and 'site_id' not in node:
+            nodes = Nodes(self.api, node['node_id'])
+            node = nodes[0]
+
+        # used in conjunction with reduce to flatten lists, like in
+        # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
+        def reduce_flatten_list (x,y): return x+y
+
+        # power users are pis and techs
+        def get_site_power_user_keys(api,site_id_or_name):
+            site = Sites (api,site_id_or_name,['person_ids'])[0]
+            key_ids = reduce (reduce_flatten_list, 
+                              [ p['key_ids'] for p in \
+                                    Persons(api,{ 'person_id':site['person_ids'], 
+                                                  'enabled':True, '|role_ids' : [20, 40] }, 
+                                            ['key_ids']) ],
+                              [])
+            return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
+
+        # all admins regardless of their site
+        def get_all_admin_keys(api):
+            key_ids = reduce (reduce_flatten_list, 
+                              [ p['key_ids'] for p in \
+                                    Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
+                                            ['key_ids']) ],
+                              [])
+            return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
+
+        # 'site_admin' account setup
+        personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
+        accounts.append({'name':'site_admin','keys':personsitekeys})
+
+        # 'root' account setup on nodes from all 'admin' users
+        personsitekeys=get_all_admin_keys(self.api)
+        accounts.append({'name':'root','keys':personsitekeys})
 
        node.update_last_contact()
 
@@ -223,5 +287,40 @@ class GetSlivers(Method):
             'groups': groups,
             'conf_files': conf_files.values(),
            'initscripts': initscripts,
-            'slivers': slivers
+            'slivers': slivers,
+            'accounts': accounts
             }
+
+class v42GetSlivers(v43GetSlivers):
+    """
+    Legacy wrapper for v43GetSlivers.
+    """
+
+    def call(self, auth, node_id_or_hostname = None):
+        result = v43GetSlivers.call(self,auth,node_id_or_hostname)
+        networks = result['networks']
+
+        for i in range(0,len(networks)):
+            network = networks[i]
+            if network.has_key("interface_id"):
+                network['nodenetwork_id']=network['interface_id']
+            if network.has_key("interface_tag_ids"):
+                network['nodenetwork_setting_ids']=network['interface_tag_ids']
+            networks[i]=network
+
+        result['networks']=networks
+        return result
+
+class GetSlivers(v42GetSlivers):
+    """
+    Returns a struct containing information about the specified node
+    (or calling node, if called by a node and node_id_or_hostname is
+    not specified), including the current set of slivers bound to the
+    node.
+
+    All of the information returned by this call can be gathered from
+    other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
+    function exists almost solely for the benefit of Node Manager.
+    """
+
+    pass
index ebcff1e..9008699 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index 11251f8..852a7af 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 70c273d..f266891 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
index 99ec318..bf71520 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
index 7efefd2..7f0d1b5 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import socket
 
 from PLC.Faults import *
diff --git a/PLC/Methods/RebootNodeWithPCU.py b/PLC/Methods/RebootNodeWithPCU.py
new file mode 100644 (file)
index 0000000..fac74d3
--- /dev/null
@@ -0,0 +1,77 @@
+# $Id$
+# $URL$
+import socket
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+
+try:
+       from pcucontrol import reboot
+       external_dependency = True
+except:
+       external_dependency = False
+
+class RebootNodeWithPCU(Method):
+    """
+       Uses the associated PCU to attempt to reboot the given Node.
+
+    Admins can reboot any node. Techs and PIs can only reboot nodes at
+    their site.
+
+    Returns 1 if the reboot proceeded without error (Note: this does not guarantee
+       that the reboot is successful).
+       Returns -1 if external dependencies for this call are not available.
+       Returns "error string" if the reboot failed with a specific message.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    accepts = [
+        Auth(),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, node_id_or_hostname):
+        # Get account information
+        nodes = Nodes(self.api, [node_id_or_hostname])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node"
+
+        node = nodes[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # If we are not an admin, make sure that the caller is a
+        # member of the site at which the node is located.
+        if 'admin' not in self.caller['roles']:
+            if node['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Not allowed to reboot nodes from specified site"
+
+       # Verify that the node has pcus associated with it.
+       pcus = PCUs(self.api, {'pcu_id' : node['pcu_ids']} )
+        if not pcus:
+            raise PLCInvalidArgument, "No PCUs associated with Node"
+
+       pcu = pcus[0]
+
+       if not external_dependency:
+            raise PLCNotImplemented, "Could not load external module to attempt reboot"
+
+       # model, hostname, port, 
+       # i = pcu['node_ids'].index(node['node_id'])
+       # p = pcu['ports'][i]
+       ret = reboot.reboot_api(node, pcu)
+
+        self.event_objects = {'Node': [node['node_id']]}
+        self.message = "RebootNodeWithPCU called"
+               
+        return ret
index 4568d99..fe81e39 100644 (file)
@@ -2,7 +2,11 @@
 # Thierry Parmentelat - INRIA
 # 
 # $Id$
+# $URL$
 
+import os
+import sys
+import fcntl
 import time
 
 from PLC.Debug import log
@@ -38,6 +42,40 @@ def message (to_print=None,verbose_only=False):
 def message_verbose(to_print=None):
     message(to_print,verbose_only=True)
 
+
+class FileLock:
+    """
+    Lock/Unlock file
+    """
+    def __init__(self, file_path, expire = 60 * 60 * 2):
+        self.expire = expire
+        self.fpath = file_path
+        self.fd = None
+
+    def lock(self):
+        if os.path.exists(self.fpath):
+            if (time.time() - os.stat(self.fpath).st_ctime) > self.expire:
+                try:
+                    os.unlink(self.fpath)
+                except Exception, e:
+                    message('FileLock.lock(%s) : %s' % (self.fpath, e))
+                    return False
+        try:
+            self.fd = open(self.fpath, 'w')
+            fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+        except IOError, e:
+            message('FileLock.lock(%s) : %s' % (self.fpath, e))
+            return False
+        return True
+
+    def unlock(self):
+        try:
+            fcntl.flock(self.fd, fcntl.LOCK_UN | fcntl.LOCK_NB)
+            self.fd.close()
+        except IOError, e:
+            message('FileLock.unlock(%s) : %s' % (self.fpath, e))
+
+
 class RefreshPeer(Method):
     """
     Fetches site, node, slice, person and key data from the specified peer
@@ -57,6 +95,21 @@ class RefreshPeer(Method):
     returns = Parameter(int, "1 if successful")
 
     def call(self, auth, peer_id_or_peername):
+        ret_val = None
+        peername = Peers(self.api, [peer_id_or_peername], ['peername'])[0]['peername']
+        file_lock = FileLock("/tmp/refresh-peer-%s.lock" % peername)
+        if not file_lock.lock():
+            raise Exception, "Another instance of RefreshPeer is running."
+        try:
+            ret_val = self.real_call(auth, peer_id_or_peername)
+        except Exception, e:
+            file_lock.unlock()
+            raise Exception, e
+        file_lock.unlock()
+        return ret_val
+
+        
+    def real_call(self, auth, peer_id_or_peername):
         # Get peer
        peers = Peers(self.api, [peer_id_or_peername])
         if not peers:
@@ -74,6 +127,18 @@ class RefreshPeer(Method):
        message('RefreshPeer starting up (commit_mode=%r)'%commit_mode)
        message('Issuing GetPeerData')
         peer_tables = peer.GetPeerData()
+        # for smooth federation with 4.2 - ignore fields that are useless anyway, and rewrite boot_state
+        boot_state_rewrite={'dbg':'safeboot','diag':'safeboot','disable':'disabled',
+                            'inst':'reinstall','rins':'reinstall','new':'reinstall','rcnf':'reinstall'}
+        for node in peer_tables['Nodes']:
+            for key in ['nodenetwork_ids','dummybox_id']:
+                if key in node:
+                    del node[key]
+            if node['boot_state'] in boot_state_rewrite: node['boot_state']=boot_state_rewrite[node['boot_state']]
+        for slice in peer_tables['Slices']:
+            for key in ['slice_attribute_ids']:
+                if key in slice:
+                    del slice[key]
         timers['transport'] = time.time() - start - peer_tables['db_time']
         timers['peer_db'] = peer_tables['db_time']
         message_verbose('GetPeerData returned -> db=%d transport=%d'%(timers['peer_db'],timers['transport']))
@@ -112,6 +177,10 @@ class RefreshPeer(Method):
             for peer_object_id, peer_object in peer_objects.iteritems():
                 message_verbose ('DBG %s peer_object_id=%d (%d/%d)'%(classname,peer_object_id,count,total))
                 count += 1
+                if peer_object_id in synced:
+                    message("Warning: %s Skipping already added %s: %r"%(
+                            peer['peername'], classname, peer_object))
+                    continue
                 if classname == 'Node':
                     message_verbose ('DBG>> hostname=%s'%peer_object['hostname'])
                 elif classname == "Slice":
@@ -330,7 +399,10 @@ class RefreshPeer(Method):
         if peer_tables['Nodes']:
             columns = peer_tables['Nodes'][0].keys()
         else:
-            columns = None
+            # smooth federation with a 4.2 peer - ignore these fields that are useless anyway
+            columns = Node.fields
+            if 'interface_ids' in columns: columns.remove('interface_ids')
+            if 'dummybox_id' in columns: columns.remove('dummybox_id')
 
         # Keyed on foreign node_id
         old_peer_nodes = Nodes(self.api, {'peer_id': peer_id}, columns).dict('peer_node_id')
@@ -456,7 +528,7 @@ class RefreshPeer(Method):
 
             # Nodes that are currently part of the slice
            old_slice_node_ids = [ node_transcoder[node_id] for node_id in slice['node_ids'] \
-                                  if node_transcoder[node_id] in peer_nodes]
+                                  if node_id in node_transcoder and node_transcoder[node_id] in peer_nodes]
 
             # Nodes that should be part of the slice
            slice_node_ids = [ node_id for node_id in peer_slice['node_ids'] if node_id in peer_nodes]
@@ -509,5 +581,5 @@ class RefreshPeer(Method):
 
         # Update peer itself and commit
         peer.sync(commit = True)
-
+        
         return timers
diff --git a/PLC/Methods/ReportRunlevel.py b/PLC/Methods/ReportRunlevel.py
new file mode 100644 (file)
index 0000000..e903122
--- /dev/null
@@ -0,0 +1,63 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth, BootAuth, SessionAuth
+from PLC.Nodes import Node, Nodes
+
+can_update = ['run_level']
+
+class ReportRunlevel(Method):
+    """
+        report runlevel
+    """
+    roles = ['node', 'admin']
+
+    accepts = [
+        Mixed(BootAuth(), SessionAuth(), Auth()),
+        {'run_level': Node.fields['run_level'],
+         },
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, report_fields, node_id_or_hostname=None):
+
+        if not isinstance(self.caller, Node):
+            # check admin
+            if 'admin' not in self.caller['roles']:
+                raise PLCPermissionDenied, "Not allowed to update node run_level"
+
+            nodes = Nodes(self.api, [node_id_or_hostname])
+            if not nodes:
+                raise PLCInvalidArgument, "No such node"
+        else:
+            nodes  = [self.caller]
+
+        node = nodes[0]
+        # avoid logging this even too often
+        # avoid logging occurrences where run_level does not change
+        former_level=None
+        if 'run_level' in node: former_level=node['run_level']
+
+        node.update_last_contact()
+        for field in can_update:
+            if field in report_fields:
+                node.update({field : report_fields[field]})
+
+        node.sync(commit=True)
+        
+        # skip logging in this case
+        if former_level and 'run_level' in node and node['run_level'] == former_level:
+            pass
+        else:
+            # handle the 'run_level' key
+            message="run level " + node['hostname'] + ":"
+            if 'run_level' in report_fields:
+                message += str(former_level) + "->" + report_fields['run_level']
+            message += ", ".join(  [ k + "->" + v for (k,v) in report_fields.items() if k not in ['run_level'] ] )
+
+        return 1
index 0e2d2a9..b066529 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import random
 import base64
 import time
diff --git a/PLC/Methods/ResolveSlices.py b/PLC/Methods/ResolveSlices.py
new file mode 100644 (file)
index 0000000..2a33ef2
--- /dev/null
@@ -0,0 +1,44 @@
+# $Id$
+# $URL$
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+
+class ResolveSlices(Method):
+    """
+    This method is similar to GetSlices, except that (1) the returned
+    columns are restricted to 'name' and 'slice_id', and (2) it
+    returns expired slices too. This method is designed to help
+    third-party software solve slice names from their slice_id
+    (e.g. PlanetFlow Central). For this reason it is accessible with
+    anonymous authentication (among others).
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech', 'anonymous' ]
+
+    applicable_fields = { 
+        'slice_id' : Slice.fields['slice_id'],
+        'name' : Slice.fields['name'],
+        }
+
+    accepts = [
+        Auth(),
+        Mixed([Mixed(Slice.fields['slice_id'],
+                     Slice.fields['name'])],
+              Parameter(str,"name"),
+              Parameter(int,"slice_id"),
+              Filter(applicable_fields))
+        ]
+
+    returns = [applicable_fields]
+
+    def call(self, auth, slice_filter = None):
+
+        # Must query at least slice_id (see below)
+        return_fields = self.applicable_fields.keys()
+        # pass expires=0
+        slices = Slices(self.api, slice_filter, return_fields, 0)
+        return slices
index 644826b..d0d392b 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index cc30b52..72f8c9c 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 70f6696..453b707 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import re
 
 from PLC.Faults import *
index 1211610..5469e81 100644 (file)
@@ -1,9 +1,11 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Auth import Auth
 from PLC.Slices import Slice, Slices
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 from PLC.Sites import Site, Sites
 from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
@@ -61,7 +63,7 @@ class SliceExtendedInfo(Method):
            index = slices.index(slice)
             node_ids = slices[index].pop('node_ids')
             person_ids = slices[index].pop('person_ids')
-           attribute_ids = slices[index].pop('slice_attribute_ids')
+           attribute_ids = slices[index].pop('slice_tag_ids')
             if return_users or return_users is None:
                 persons = Persons(self.api, person_ids)
                 person_info = [{'email': person['email'], 
@@ -75,7 +77,7 @@ class SliceExtendedInfo(Method):
                             for node in nodes]
                 slices[index]['nodes'] = node_info
            if return_attributes or return_attributes is None:
-               attributes = SliceAttributes(self.api, attribute_ids)
+               attributes = SliceTags(self.api, attribute_ids)
                attribute_info = [{'name': attribute['name'],
                                   'value': attribute['value']} \
                                  for attribute in attributes]
index 7cdb167..d687cf8 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import os
 import sys
 from subprocess import Popen, PIPE, call
@@ -8,7 +10,7 @@ from PLC.Faults import *
 from PLC.Slices import Slice, Slices
 from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 
 from PLC.Methods.GetSliceTicket import GetSliceTicket
 
@@ -96,7 +98,7 @@ class SliceGetTicket(GetSliceTicket):
 
         nodes = Nodes(self.api, slice['node_ids']).dict()
         persons = Persons(self.api, slice['person_ids']).dict()
-        slice_attributes = SliceAttributes(self.api, slice['slice_attribute_ids']).dict()
+        slice_tags = SliceTags(self.api, slice['slice_tag_ids']).dict()
 
         ticket = NamedTemporaryFile()
 
@@ -140,13 +142,13 @@ class SliceGetTicket(GetSliceTicket):
 
         # <rspec>
         xml.startElement('rspec', {})
-        for slice_attribute_id in slice['slice_attribute_ids']:
-            if not slice_attributes.has_key(slice_attribute_id):
+        for slice_tag_id in slice['slice_tag_ids']:
+            if not slice_tags.has_key(slice_tag_id):
                 continue
-            slice_attribute = slice_attributes[slice_attribute_id]
+            slice_tag = slice_tags[slice_tag_id]
 
-            name = slice_attribute['name']
-            value = slice_attribute['value']
+            name = slice_tag['name']
+            value = slice_tag['value']
 
             def kbps_to_bps(kbps):
                 bps = int(kbps) * 1000
@@ -178,7 +180,7 @@ class SliceGetTicket(GetSliceTicket):
 
             if name == 'initscript':
                 (attribute_name, value_name, type) = ('initscript', 'initscript_id', 'integer')
-                value = slice_attribute['slice_attribute_id']
+                value = slice_tag['slice_tag_id']
             elif name in name_type_cast:
                 (attribute_name, value_name, type, cast) = name_type_cast[name]
                 value = cast(value)
index 9645f99..15b1665 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Faults import *
index 4d94933..0f8df16 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
index 9e054ed..91936c2 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
index 35ccabe..ef3f287 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 66c0ed2..b810fe8 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 0c44f66..590181a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
index 4ac6f89..cfed340 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Faults import *
index 5b2b786..2e7380a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Methods.SliceGetTicket import SliceGetTicket
 
 class SliceTicketGet(SliceGetTicket):
index 9e82d3a..420ccc7 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Faults import *
index 560a66a..fb8ac72 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 0b41b15..0cebd66 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index e3eb1e4..2c39925 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
diff --git a/PLC/Methods/UnBindObjectFromPeer.py b/PLC/Methods/UnBindObjectFromPeer.py
new file mode 100644 (file)
index 0000000..1e5c4f5
--- /dev/null
@@ -0,0 +1,70 @@
+# $Id: UnBindObjectFromPeer.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: svn+ssh://svn.planet-lab.org/svn/PLCAPI/trunk/PLC/Methods/BindObjectToPeer.py $
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Persons import Persons
+from PLC.Sites import Sites
+from PLC.Nodes import Nodes
+from PLC.Slices import Slices
+from PLC.Keys import Keys
+from PLC.Peers import Peers
+from PLC.Faults import *
+class UnBindObjectFromPeer(Method):
+    """
+    This method is a hopefully temporary hack to let the sfa correctly
+    detach the objects it creates from a remote peer object. This is
+    needed so that the sfa federation link can work in parallel with
+    RefreshPeer, as RefreshPeer depends on remote objects being
+    correctly marked. 
+
+    UnBindObjectFromPeer is allowed to admins only.
+    """
+
+    roles = ['admin']
+
+    known_types = ['site','person','slice','node','key']
+    types_doc = ",".join(["'%s'"%type for type in known_types])
+
+    accepts = [
+        Auth(),
+        Parameter(str,"Object type, among "+types_doc),
+        Parameter(int,"object_id"),
+        Parameter(str,"peer shortname"),
+        Parameter(int,"remote object_id, set to 0 if unknown"),
+        ]
+
+    returns = Parameter (int, '1 if successful')
+
+    def locate_object (self, object_type, object_id):
+        # locate e.g. the Nodes symbol
+        class_obj = globals()[object_type.capitalize()+'s']
+        id_name=object_type+'_id'
+        # invoke e.g. Nodes ({'node_id':node_id})
+        objs=class_obj(self.api,{id_name:object_id})
+        if len(objs) != 1:
+            raise PLCInvalidArgument,"Cannot locate object, type=%s id=%d"%\
+                (type,object_id)
+        return objs[0]
+
+
+    def call(self, auth, object_type, object_id, shortname):
+
+        object_type = object_type.lower()
+        if object_type not in self.known_types:
+            raise PLCInvalidArgument, 'Unrecognized object type %s'%object_type
+
+        peers=Peers(self.api,{'shortname':shortname.upper()})
+        if len(peers) !=1:
+            raise PLCInvalidArgument, 'No such peer with shortname %s'%shortname
+
+        peer=peers[0]
+        object = self.locate_object (object_type, object_id)
+        remover_name = 'remove_'+object_type
+        remove_function = getattr(type(peer),remover_name)
+        remove_function(peer,object)
+
+        return 1
index ed2fd43..4586f64 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 922c940..6110de2 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 6fd0e2a..1148f58 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d550336..9e86831 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index bb0f1f0..32b8730 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index c13680b..61c4557 100644 (file)
@@ -1,47 +1,61 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
+from PLC.Table import Row
+from PLC.Auth import Auth
+
 from PLC.Nodes import Node, Nodes
+from PLC.TagTypes import TagTypes
+from PLC.InterfaceTags import InterfaceTags
 from PLC.Interfaces import Interface, Interfaces
-from PLC.Auth import Auth
+from PLC.Methods.AddInterfaceTag import AddInterfaceTag
+from PLC.Methods.UpdateInterfaceTag import UpdateInterfaceTag
 
-can_update = lambda (field, value): field not in \
-             ['interface_id','node_id']
+cannot_update = ['interface_id','node_id']
 
 class UpdateInterface(Method):
     """
-    Updates an existing node network. Any values specified in
+    Updates an existing interface network. Any values specified in
     interface_fields are used, otherwise defaults are
     used. Acceptable values for method are dhcp and static. If type is
     static, then ip, gateway, network, broadcast, netmask, and dns1
     must all be specified in interface_fields. If type is dhcp,
     these parameters, even if specified, are ignored.
     
-    PIs and techs may only update networks associated with their own
-    nodes. Admins may update any node network.
+    PIs and techs may only update interfaces associated with their own
+    nodes. Admins may update any interface network.
  
     Returns 1 if successful, faults otherwise.
     """
 
     roles = ['admin', 'pi', 'tech']
 
-    interface_fields = dict(filter(can_update, Interface.fields.items()))
+    accepted_fields = Row.accepted_fields(cannot_update, Interface.fields,exclude=True)
+    accepted_fields.update(Interface.tags)
 
     accepts = [
         Auth(),
        Interface.fields['interface_id'],
-       interface_fields
+       accepted_fields
         ]
 
     returns = Parameter(int, '1 if successful')
 
     def call(self, auth, interface_id, interface_fields):
-        interface_fields = dict(filter(can_update, interface_fields.items()))
 
-       # Get node network information
+        [native,tags,rejected] = Row.split_fields(interface_fields,[Interface.fields,Interface.tags])
+
+        # type checking
+        native= Row.check_fields (native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot update Interface column(s) %r"%rejected
+
+       # Get interface information
        interfaces = Interfaces(self.api, [interface_id])
        if not interfaces:
-            raise PLCInvalidArgument, "No such node network"
+            raise PLCInvalidArgument, "No such interface"
 
        interface = interfaces[0]
                
@@ -53,17 +67,29 @@ class UpdateInterface(Method):
         if 'admin' not in self.caller['roles']:
             nodes = Nodes(self.api, [interface['node_id']])
             if not nodes:
-                raise PLCPermissionDenied, "Node network is not associated with a node"
+                raise PLCPermissionDenied, "Interface is not associated with a node"
             node = nodes[0]
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to update node network"
+                raise PLCPermissionDenied, "Not allowed to update interface"
 
-       # Update node network
-       interface.update(interface_fields)
+       interface.update(native)
         interface.sync()
        
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            interface_tags=InterfaceTags(self.api,{'tagname':tagname,'interface_id':interface['interface_id']})
+            if not interface_tags:
+                AddInterfaceTag(self.api).__call__(auth,interface['interface_id'],tagname,value)
+            else:
+                UpdateInterfaceTag(self.api).__call__(auth,interface_tags[0]['interface_tag_id'],value)
+
        self.event_objects = {'Interface': [interface['interface_id']]}
-       self.message = "Node network %d updated: %s " % \
-           (interface['interface_id'], ", ".join(interface_fields.keys()))
+        if 'ip' in interface:
+            self.message = "Interface %s updated"%interface['ip']
+        else:
+            self.message = "Interface %d updated"%interface['interface_id']
+        self.message += "[%s]." % ", ".join(interface_fields.keys())
 
         return 1
similarity index 64%
rename from PLC/Methods/UpdateInterfaceSetting.py
rename to PLC/Methods/UpdateInterfaceTag.py
index cdc4dc5..6d7e424 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -9,13 +11,13 @@ from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 
-from PLC.InterfaceSettings import InterfaceSetting, InterfaceSettings
+from PLC.InterfaceTags import InterfaceTag, InterfaceTags
 from PLC.Interfaces import Interface, Interfaces
 
 from PLC.Nodes import Nodes
 from PLC.Sites import Sites
 
-class UpdateInterfaceSetting(Method):
+class UpdateInterfaceTag(Method):
     """
     Updates the value of an existing interface setting
 
@@ -28,27 +30,27 @@ class UpdateInterfaceSetting(Method):
 
     accepts = [
         Auth(),
-        InterfaceSetting.fields['interface_setting_id'],
-        InterfaceSetting.fields['value']
+        InterfaceTag.fields['interface_tag_id'],
+        InterfaceTag.fields['value']
         ]
 
     returns = Parameter(int, '1 if successful')
 
     object_type = 'Interface'
 
-    def call(self, auth, interface_setting_id, value):
-        interface_settings = InterfaceSettings(self.api, [interface_setting_id])
-        if not interface_settings:
-            raise PLCInvalidArgument, "No such interface setting %r"%interface_setting_id
-        interface_setting = interface_settings[0]
+    def call(self, auth, interface_tag_id, value):
+        interface_tags = InterfaceTags(self.api, [interface_tag_id])
+        if not interface_tags:
+            raise PLCInvalidArgument, "No such interface setting %r"%interface_tag_id
+        interface_tag = interface_tags[0]
 
-        ### reproducing a check from UpdateSliceAttribute, looks dumb though
-        interfaces = Interfaces(self.api, [interface_setting['interface_id']])
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        interfaces = Interfaces(self.api, [interface_tag['interface_id']])
         if not interfaces:
-            raise PLCInvalidArgument, "No such interface %r"%interface_setting['interface_id']
+            raise PLCInvalidArgument, "No such interface %r"%interface_tag['interface_id']
         interface = interfaces[0]
 
-        assert interface_setting['interface_setting_id'] in interface['interface_setting_ids']
+        assert interface_tag['interface_tag_id'] in interface['interface_tag_ids']
 
        # check permission : it not admin, is the user affiliated with the right site
        if 'admin' not in self.caller['roles']:
@@ -65,8 +67,8 @@ class UpdateInterfaceSetting(Method):
                    min(self.caller['role_ids']) > required_min_role:
                raise PLCPermissionDenied, "Not allowed to modify the specified interface setting, requires role %d",required_min_role
 
-        interface_setting['value'] = value
-        interface_setting.sync()
+        interface_tag['value'] = value
+        interface_tag.sync()
 
-       self.object_ids = [interface_setting['interface_setting_id']]
+       self.object_ids = [interface_tag['interface_tag_id']]
         return 1
index 0fb560b..1294fc0 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index e44dca4..0848f65 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index d5cdb0d..04d1036 100644 (file)
@@ -1,14 +1,19 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.Nodes import Node, Nodes
+from PLC.Table import Row
 from PLC.Auth import Auth
 
-related_fields = Node.related_fields.keys()
-can_update = lambda (field, value): field in \
-             ['hostname', 'boot_state', 'model', 'version',
-              'key', 'session', 'boot_nonce'] + \
-            related_fields
+from PLC.Nodes import Node, Nodes
+from PLC.TagTypes import TagTypes
+from PLC.NodeTags import NodeTags
+from PLC.Methods.AddNodeTag import AddNodeTag
+from PLC.Methods.UpdateNodeTag import UpdateNodeTag
+
+admin_only = [ 'key', 'session', 'boot_nonce', 'site_id']
+can_update = ['hostname', 'node_type', 'boot_state', 'model', 'version'] + admin_only 
 
 class UpdateNode(Method):
     """
@@ -23,37 +28,47 @@ class UpdateNode(Method):
 
     roles = ['admin', 'pi', 'tech']
 
-    node_fields = dict(filter(can_update, Node.fields.items() + Node.related_fields.items()))
+    accepted_fields = Row.accepted_fields(can_update,Node.fields)
+    # xxx check the related_fields feature
+    accepted_fields.update(Node.related_fields)
+    accepted_fields.update(Node.tags)
 
     accepts = [
         Auth(),
         Mixed(Node.fields['node_id'],
               Node.fields['hostname']),
-        node_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, '1 if successful')
 
     def call(self, auth, node_id_or_hostname, node_fields):
-        node_fields = dict(filter(can_update, node_fields.items()))
+        
+        # split provided fields 
+        [native,related,tags,rejected] = Row.split_fields(node_fields,[Node.fields,Node.related_fields,Node.tags])
+
+        # type checking
+        native = Row.check_fields (native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot update Node column(s) %r"%rejected
+
+        # Authenticated function
+        assert self.caller is not None
 
        # Remove admin only fields
        if 'admin' not in self.caller['roles']:
-            for key in 'key', 'session', 'boot_nonce':
-                if node_fields.has_key(key):
-                    del node_fields[key]
+            for key in admin_only:
+                if native.has_key(key):
+                    del native[key]
 
         # Get account information
         nodes = Nodes(self.api, [node_id_or_hostname])
         if not nodes:
-            raise PLCInvalidArgument, "No such node"
+            raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
         node = nodes[0]
 
         if node['peer_id'] is not None:
-            raise PLCInvalidArgument, "Not a local node"
-
-        # Authenticated function
-        assert self.caller is not None
+            raise PLCInvalidArgument, "Not a local node %r"%node_id_or_hostname
 
         # If we are not an admin, make sure that the caller is a
         # member of the site at which the node is located.
@@ -62,20 +77,31 @@ class UpdateNode(Method):
                 raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
 
         # Make requested associations
-        for field in related_fields:
-            if field in node_fields:
-                node.associate(auth, field, node_fields[field])
-                node_fields.pop(field)
-
-       node.update(node_fields)
-       node.update_last_updated(False)
-        node.sync()
+        for (k,v) in related.iteritems():
+            node.associate(auth, k,v)
+
+       node.update(native)
+       node.update_last_updated(commit=False)
+        node.sync(commit=True)
        
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
+            if not node_tags:
+                AddNodeTag(self.api).__call__(auth,node['node_id'],tagname,value)
+            else:
+                UpdateNodeTag(self.api).__call__(auth,node_tags[0]['node_tag_id'],value)
+
        # Logging variables
        self.event_objects = {'Node': [node['node_id']]}
-       self.message = 'Node %d updated: %s.' % \
-               (node['node_id'], ", ".join(node_fields.keys()))
+        if 'hostname' in node:
+            self.message = 'Node %s updated'%node['hostname']
+        else:
+            self.message = 'Node %d updated'%node['node_id']
+        self.message += " [%s]." % (", ".join(node_fields.keys()),)
        if 'boot_state' in node_fields.keys():
-               self.message += ' boot_state updated to %s' %  node_fields['boot_state']
+               self.message += ' boot_state updated to %s' % node_fields['boot_state']
 
         return 1
index 75b683f..b968766 100644 (file)
@@ -1,10 +1,12 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.NodeGroups import NodeGroup, NodeGroups
 from PLC.Auth import Auth
 
-can_update = lambda (field, value): field in ['groupname','tagvalue'] 
+can_update = lambda (field, value): field in ['groupname','value'] 
 
 class UpdateNodeGroup(Method):
     """
index 2d39e19..4750556 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
@@ -9,11 +11,9 @@ from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 
-from PLC.NodeTags import NodeTag, NodeTags
-from PLC.Nodes import Node, Nodes
-
-from PLC.Nodes import Nodes
 from PLC.Sites import Sites
+from PLC.Nodes import Node, Nodes
+from PLC.NodeTags import NodeTag, NodeTags
 
 class UpdateNodeTag(Method):
     """
@@ -29,7 +29,7 @@ class UpdateNodeTag(Method):
     accepts = [
         Auth(),
         NodeTag.fields['node_tag_id'],
-        NodeTag.fields['tagvalue']
+        NodeTag.fields['value']
         ]
 
     returns = Parameter(int, '1 if successful')
@@ -42,13 +42,13 @@ class UpdateNodeTag(Method):
             raise PLCInvalidArgument, "No such node tag %r"%node_tag_id
         node_tag = node_tags[0]
 
-        ### reproducing a check from UpdateSliceAttribute, looks dumb though
+        ### reproducing a check from UpdateSliceTag, looks dumb though
         nodes = Nodes(self.api, [node_tag['node_id']])
         if not nodes:
             raise PLCInvalidArgument, "No such node %r"%node_tag['node_id']
         node = nodes[0]
 
-        assert node_tag['node_tag_id'] in node['tag_ids']
+        assert node_tag['node_tag_id'] in node['node_tag_ids']
 
        # check permission : it not admin, is the user affiliated with the right site
        if 'admin' not in self.caller['roles']:
@@ -65,7 +65,7 @@ class UpdateNodeTag(Method):
                    min(self.caller['role_ids']) > required_min_role:
                raise PLCPermissionDenied, "Not allowed to modify the specified node tag, requires role %d",required_min_role
 
-        node_tag['tagvalue'] = value
+        node_tag['value'] = value
         node_tag.sync()
 
        self.object_ids = [node_tag['node_tag_id']]
index 8916684..b8a1b63 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index b1a30bc..70b62d1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index fc4e886..47a2346 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
index 8586a48..e0ac792 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
@@ -5,7 +7,7 @@ from PLC.Auth import Auth
 from PLC.Peers import Peer, Peers
 
 can_update = lambda (field, value): field in \
-             ['peername', 'peer_url', 'key', 'cacert']
+             ['peername', 'peer_url', 'key', 'cacert', 'shortname', 'hrn_root']
 
 class UpdatePeer(Method):
     """
index 8f74fc0..9ca8c58 100644 (file)
@@ -1,8 +1,11 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Persons import Person, Persons
 from PLC.Auth import Auth
+from PLC.sendmail import sendmail
 
 related_fields = Person.related_fields.keys()
 can_update = lambda (field, value): field in \
@@ -62,7 +65,19 @@ class UpdatePerson(Method):
         person.update(person_fields)
        person.update_last_updated(False)
         person.sync()
-       
+
+       if 'enabled' in person_fields:
+           To = [("%s %s" % (person['first_name'], person['last_name']), person['email'])]
+           Cc = []     
+           if person['enabled']:
+               Subject = "%s account enabled" % (self.api.config.PLC_NAME)
+               Body = "Your %s account has been enabled. Please visit %s to access your account." % (self.api.config.PLC_NAME, self.api.config.PLC_WWW_HOST) 
+           else:
+               Subject = "%s account disabled" % (self.api.config.PLC_NAME)
+               Body = "Your %s account has been disabled. Please contact your PI or PlanetLab support for more information" % (self.api.config.PLC_NAME)
+           sendmail(self.api, To = To, Cc = Cc, Subject = Subject, Body = Body)                
+
+                               
        # Logging variables
        self.event_objects = {'Person': [person['person_id']]}
 
diff --git a/PLC/Methods/UpdatePersonTag.py b/PLC/Methods/UpdatePersonTag.py
new file mode 100644 (file)
index 0000000..928b54f
--- /dev/null
@@ -0,0 +1,68 @@
+# $Id: UpdatePersonTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/UpdatePersonTag.py $
+#
+# $Revision: 14587 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.PersonTags import PersonTag, PersonTags
+from PLC.Persons import Person, Persons
+
+from PLC.Nodes import Nodes
+from PLC.Persons import Persons
+
+class UpdatePersonTag(Method):
+    """
+    Updates the value of an existing person setting
+
+    Access rights depend on the tag type.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        PersonTag.fields['person_tag_id'],
+        PersonTag.fields['value']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Person'
+
+    def call(self, auth, person_tag_id, value):
+        person_tags = PersonTags(self.api, [person_tag_id])
+        if not person_tags:
+            raise PLCInvalidArgument, "No such person setting %r"%person_tag_id
+        person_tag = person_tags[0]
+
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        persons = Persons(self.api, [person_tag['person_id']])
+        if not persons:
+            raise PLCInvalidArgument, "No such person %r"%person_tag['person_id']
+        person = persons[0]
+
+        assert person_tag['person_tag_id'] in person['person_tag_ids']
+
+       # check permission : it not admin, is the user affiliated with the right person
+       if 'admin' not in self.caller['roles']:
+           # check caller is affiliated with this person's person
+           if len(set(person['person_ids']) & set(self.caller['person_ids'])) == 0:
+               raise PLCPermissionDenied, "Not a member of the person's persons: %s"%person['person_ids']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified person setting, requires role %d",required_min_role
+
+        person_tag['value'] = value
+        person_tag.sync()
+
+       self.object_ids = [person_tag['person_tag_id']]
+        return 1
index 6a33c5e..b59894f 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
diff --git a/PLC/Methods/UpdateSiteTag.py b/PLC/Methods/UpdateSiteTag.py
new file mode 100644 (file)
index 0000000..c7f63a3
--- /dev/null
@@ -0,0 +1,68 @@
+# $Id: UpdateSiteTag.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/Methods/UpdateSiteTag.py $
+#
+# $Revision: 14587 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.SiteTags import SiteTag, SiteTags
+from PLC.Sites import Site, Sites
+
+from PLC.Nodes import Nodes
+from PLC.Sites import Sites
+
+class UpdateSiteTag(Method):
+    """
+    Updates the value of an existing site setting
+
+    Access rights depend on the tag type.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        SiteTag.fields['site_tag_id'],
+        SiteTag.fields['value']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Site'
+
+    def call(self, auth, site_tag_id, value):
+        site_tags = SiteTags(self.api, [site_tag_id])
+        if not site_tags:
+            raise PLCInvalidArgument, "No such site setting %r"%site_tag_id
+        site_tag = site_tags[0]
+
+        ### reproducing a check from UpdateSliceTag, looks dumb though
+        sites = Sites(self.api, [site_tag['site_id']])
+        if not sites:
+            raise PLCInvalidArgument, "No such site %r"%site_tag['site_id']
+        site = sites[0]
+
+        assert site_tag['site_tag_id'] in site['site_tag_ids']
+
+       # check permission : it not admin, is the user affiliated with the right site
+       if 'admin' not in self.caller['roles']:
+           # check caller is affiliated with this site
+           if self.caller['person_id'] not in site['person_ids']:
+               raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = tag_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified site setting, requires role %d",required_min_role
+
+        site_tag['value'] = value
+        site_tag.sync()
+
+       self.object_ids = [site_tag['site_tag_id']]
+        return 1
index 901ecbc..39b052b 100644 (file)
@@ -1,17 +1,21 @@
+# $Id$
+# $URL$
 import time
 
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.Slices import Slice, Slices
+from PLC.Table import Row
 from PLC.Auth import Auth
-from PLC.Sites import Site, Sites
 
-related_fields = Slice.related_fields.keys() 
-can_update = lambda (field, value): field in \
-             ['instantiation', 'url', 'description', 'max_nodes', 'expires'] + \
-            related_fields
+from PLC.Slices import Slice, Slices
+from PLC.Sites import Site, Sites
+from PLC.TagTypes import TagTypes
+from PLC.SliceTags import SliceTags
+from PLC.Methods.AddSliceTag import AddSliceTag
+from PLC.Methods.UpdateSliceTag import UpdateSliceTag
 
+can_update = ['instantiation', 'url', 'description', 'max_nodes', 'expires'] 
 
 class UpdateSlice(Method):
     """
@@ -31,28 +35,41 @@ class UpdateSlice(Method):
 
     roles = ['admin', 'pi', 'user']
 
-    slice_fields = dict(filter(can_update, Slice.fields.items() + Slice.related_fields.items()))
+    accepted_fields = Row.accepted_fields(can_update, Slice.fields)
+    # xxx check the related_fields feature
+    accepted_fields.update(Slice.related_fields)
+    accepted_fields.update(Slice.tags)
 
     accepts = [
         Auth(),
         Mixed(Slice.fields['slice_id'],
               Slice.fields['name']),
-        slice_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, '1 if successful')
 
     def call(self, auth, slice_id_or_name, slice_fields):
-        slice_fields = dict(filter(can_update, slice_fields.items()))
+
+        # split provided fields 
+        [native,related,tags,rejected] = Row.split_fields(slice_fields,[Slice.fields,Slice.related_fields,Slice.tags])
         
+        # type checking
+        native = Row.check_fields (native, self.accepted_fields)
+        if rejected:
+            raise PLCInvalidArgument, "Cannot update Slice column(s) %r"%rejected
+
        slices = Slices(self.api, [slice_id_or_name])
         if not slices:
-            raise PLCInvalidArgument, "No such slice"
+            raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
         slice = slices[0]
 
         if slice['peer_id'] is not None:
             raise PLCInvalidArgument, "Not a local slice"
 
+        # Authenticated function
+        assert self.caller is not None
+
         if 'admin' not in self.caller['roles']:
             if self.caller['person_id'] in slice['person_ids']:
                 pass
@@ -62,12 +79,13 @@ class UpdateSlice(Method):
                 raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
 
         # Renewing
+        renewing=False
         if 'expires' in slice_fields and slice_fields['expires'] > slice['expires']:
             sites = Sites(self.api, [slice['site_id']])
             assert sites
             site = sites[0]
 
-            if site['max_slices'] < 0:
+            if site['max_slices'] <= 0:
                 raise PLCInvalidArgument, "Slice creation and renewal have been disabled for the site"
 
             # Maximum expiration date is 8 weeks from now
@@ -87,6 +105,7 @@ class UpdateSlice(Method):
                if 'url' not in slice_fields or slice_fields['url'] is None or \
                   not slice_fields['url'].strip():
                     raise PLCInvalidArgument, "Cannot renew a slice with an empty description or URL"
+            renewing=True
            
         if 'max_nodes' in slice_fields and slice_fields['max_nodes'] != slice['max_nodes']:
             if 'admin' not in self.caller['roles'] and \
@@ -94,14 +113,33 @@ class UpdateSlice(Method):
                 raise PLCInvalidArgument, "Only admins and PIs may update max_nodes"
 
        # Make requested associations
-       for field in related_fields:
-           if field in slice_fields:
-               slice.associate(auth, field, slice_fields[field])
-               slice_fields.pop(field)
+        for (k,v) in related.iteritems():
+            slice.associate(auth,k,v)
 
        slice.update(slice_fields)
-        slice.sync()
+        slice.sync(commit=True)
+
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            slice_tags=SliceTags(self.api,{'tagname':tagname,'slice_id':slice['slice_id']})
+            if not slice_tags:
+                AddSliceTag(self.api).__call__(auth,slice['slice_id'],tagname,value)
+            else:
+                UpdateSliceTag(self.api).__call__(auth,slice_tags[0]['slice_tag_id'],value)
 
        self.event_objects = {'Slice': [slice['slice_id']]}
+        if 'name' in slice:
+            self.message='Slice %s updated'%slice['name']
+        else:
+            self.message='Slice %d updated'%slice['slice_id']
+        if renewing:
+            # it appears that slice['expires'] may be either an int, or a formatted string
+            try:
+                expire_date=time.strftime('%Y-%m-%d:%H:%M',time.localtime(float(slice['expires'])))
+            except:
+                expire_date=slice['expires']
+            self.message += ' renewed until %s'%expire_date
 
         return 1
diff --git a/PLC/Methods/UpdateSliceAttribute.py b/PLC/Methods/UpdateSliceAttribute.py
deleted file mode 100644 (file)
index 3c21403..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-from PLC.Faults import *
-from PLC.Method import Method
-from PLC.Parameter import Parameter, Mixed
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
-from PLC.Slices import Slice, Slices
-from PLC.InitScripts import InitScript, InitScripts
-from PLC.Auth import Auth
-
-class UpdateSliceAttribute(Method):
-    """
-    Updates the value of an existing slice or sliver attribute.
-
-    Users may only update attributes of slices or slivers of which
-    they are members. PIs may only update attributes of slices or
-    slivers at their sites, or of which they are members. Admins may
-    update attributes of any slice or sliver.
-
-    Returns 1 if successful, faults otherwise.
-    """
-
-    roles = ['admin', 'pi', 'user']
-
-    accepts = [
-        Auth(),
-        SliceAttribute.fields['slice_attribute_id'],
-       Mixed(SliceAttribute.fields['value'],
-              InitScript.fields['name'])
-        ]
-
-    returns = Parameter(int, '1 if successful')
-
-    def call(self, auth, slice_attribute_id, value):
-        slice_attributes = SliceAttributes(self.api, [slice_attribute_id])
-        if not slice_attributes:
-            raise PLCInvalidArgument, "No such slice attribute"
-        slice_attribute = slice_attributes[0]
-
-        slices = Slices(self.api, [slice_attribute['slice_id']])
-        if not slices:
-            raise PLCInvalidArgument, "No such slice"
-        slice = slices[0]
-
-        assert slice_attribute['slice_attribute_id'] in slice['slice_attribute_ids']
-
-        if 'admin' not in self.caller['roles']:
-            if self.caller['person_id'] in slice['person_ids']:
-                pass
-            elif 'pi' not in self.caller['roles']:
-                raise PLCPermissionDenied, "Not a member of the specified slice"
-            elif slice['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
-
-            if slice_attribute['min_role_id'] is not None and \
-               min(self.caller['role_ids']) > slice_attribute['min_role_id']:
-                raise PLCPermissionDenied, "Not allowed to update the specified attribute"
-       
-       if slice_attribute['tagname'] in ['initscript']:
-            initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
-            if not initscripts:
-                raise PLCInvalidArgument, "No such plc initscript"     
-
-        slice_attribute['value'] = unicode(value)
-        slice_attribute.sync()
-       self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
-        return 1
diff --git a/PLC/Methods/UpdateSliceTag.py b/PLC/Methods/UpdateSliceTag.py
new file mode 100644 (file)
index 0000000..7b8aed2
--- /dev/null
@@ -0,0 +1,74 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceTags import SliceTag, SliceTags
+from PLC.Nodes import Node
+from PLC.Slices import Slice, Slices
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class UpdateSliceTag(Method):
+    """
+    Updates the value of an existing slice or sliver attribute.
+
+    Users may only update attributes of slices or slivers of which
+    they are members. PIs may only update attributes of slices or
+    slivers at their sites, or of which they are members. Admins may
+    update attributes of any slice or sliver.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        SliceTag.fields['slice_tag_id'],
+       Mixed(SliceTag.fields['value'],
+              InitScript.fields['name'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, slice_tag_id, value):
+        slice_tags = SliceTags(self.api, [slice_tag_id])
+        if not slice_tags:
+            raise PLCInvalidArgument, "No such slice attribute"
+        slice_tag = slice_tags[0]
+
+        slices = Slices(self.api, [slice_tag['slice_id']])
+        if not slices:
+            raise PLCInvalidArgument, "No such slice"
+        slice = slices[0]
+
+        assert slice_tag['slice_tag_id'] in slice['slice_tag_ids']
+
+        if not isinstance(self.caller, Node):
+            if 'admin' not in self.caller['roles']:
+                if self.caller['person_id'] in slice['person_ids']:
+                    pass
+                elif 'pi' not in self.caller['roles']:
+                    raise PLCPermissionDenied, "Not a member of the specified slice"
+                elif slice['site_id'] not in self.caller['site_ids']:
+                    raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+                if slice_tag['min_role_id'] is not None and \
+                       min(self.caller['role_ids']) > slice_tag['min_role_id']:
+                    raise PLCPermissionDenied, "Not allowed to update the specified attribute"
+        else:
+            ### make node's min_role_id == PI min_role_id
+            node_role_id = 20
+            if slice_tag['min_role_id'] is not None and node_role_id > slice_tag['min_role_id']:
+                raise PLCPermissionDenied, "Not allowed to update the specified slice attribute"
+       
+       if slice_tag['tagname'] in ['initscript']:
+            initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
+            if not initscripts:
+                raise PLCInvalidArgument, "No such plc initscript"     
+
+        slice_tag['value'] = unicode(value)
+        slice_tag.sync()
+       self.event_objects = {'SliceTag': [slice_tag['slice_tag_id']]}
+        return 1
index 7067917..83655c4 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
index 9dd784b..9aae067 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import random
 import base64
 import time
index aa324be..f885d10 100644 (file)
@@ -1,3 +1,4 @@
+## Please use make index to update this file
 native_methods = """
 AddAddressType
 AddAddressTypeToAddress
@@ -8,7 +9,7 @@ AddConfFileToNodeGroup
 AddIlink
 AddInitScript
 AddInterface
-AddInterfaceSetting
+AddInterfaceTag
 AddKeyType
 AddMessage
 AddNetworkMethod
@@ -17,12 +18,14 @@ AddNode
 AddNodeGroup
 AddNodeTag
 AddNodeToPCU
+AddNodeType
 AddPCU
 AddPCUProtocolType
 AddPCUType
 AddPeer
 AddPerson
 AddPersonKey
+AddPersonTag
 AddPersonToSite
 AddPersonToSlice
 AddRole
@@ -30,13 +33,15 @@ AddRoleToPerson
 AddSession
 AddSite
 AddSiteAddress
+AddSiteTag
 AddSlice
-AddSliceAttribute
 AddSliceInstantiation
+AddSliceTag
 AddSliceToNodes
 AddSliceToNodesWhitelist
 AddTagType
 AuthCheck
+BindObjectToPeer
 BlacklistKey
 BootCheckAuthentication
 BootGetNodeDetails
@@ -52,7 +57,7 @@ DeleteConfFileFromNodeGroup
 DeleteIlink
 DeleteInitScript
 DeleteInterface
-DeleteInterfaceSetting
+DeleteInterfaceTag
 DeleteKey
 DeleteKeyType
 DeleteMessage
@@ -62,6 +67,7 @@ DeleteNode
 DeleteNodeFromPCU
 DeleteNodeGroup
 DeleteNodeTag
+DeleteNodeType
 DeletePCU
 DeletePCUProtocolType
 DeletePCUType
@@ -69,15 +75,17 @@ DeletePeer
 DeletePerson
 DeletePersonFromSite
 DeletePersonFromSlice
+DeletePersonTag
 DeleteRole
 DeleteRoleFromPerson
 DeleteSession
 DeleteSite
+DeleteSiteTag
 DeleteSlice
-DeleteSliceAttribute
 DeleteSliceFromNodes
 DeleteSliceFromNodesWhitelist
 DeleteSliceInstantiation
+DeleteSliceTag
 DeleteTagType
 GenerateNodeConfFile
 GetAddressTypes
@@ -89,15 +97,17 @@ GetEventObjects
 GetEvents
 GetIlinks
 GetInitScripts
-GetInterfaceSettings
+GetInterfaceTags
 GetInterfaces
 GetKeyTypes
 GetKeys
 GetMessages
 GetNetworkMethods
 GetNetworkTypes
+GetNodeFlavour
 GetNodeGroups
 GetNodeTags
+GetNodeTypes
 GetNodes
 GetPCUProtocolTypes
 GetPCUTypes
@@ -105,26 +115,31 @@ GetPCUs
 GetPeerData
 GetPeerName
 GetPeers
+GetPersonTags
 GetPersons
 GetPlcRelease
 GetRoles
 GetSession
 GetSessions
+GetSiteTags
 GetSites
-GetSliceAttributes
+GetSliceFamily
 GetSliceInstantiations
 GetSliceKeys
+GetSliceTags
 GetSliceTicket
 GetSlices
-GetSlicesMD5
 GetSlivers
 GetTagTypes
 GetWhitelist
 NotifyPersons
 NotifySupport
 RebootNode
+RebootNodeWithPCU
 RefreshPeer
+ReportRunlevel
 ResetPassword
+ResolveSlices
 SetPersonPrimarySite
 SliceCreate
 SliceDelete
@@ -142,13 +157,14 @@ SliceUpdate
 SliceUserAdd
 SliceUserDel
 SliceUsersList
+UnBindObjectFromPeer
 UpdateAddress
 UpdateAddressType
 UpdateConfFile
 UpdateIlink
 UpdateInitScript
 UpdateInterface
-UpdateInterfaceSetting
+UpdateInterfaceTag
 UpdateKey
 UpdateMessage
 UpdateNode
@@ -159,9 +175,11 @@ UpdatePCUProtocolType
 UpdatePCUType
 UpdatePeer
 UpdatePerson
+UpdatePersonTag
 UpdateSite
+UpdateSiteTag
 UpdateSlice
-UpdateSliceAttribute
+UpdateSliceTag
 UpdateTagType
 VerifyPerson
 system.listMethods
index c8cfa37..fe5a9a3 100644 (file)
@@ -17,4 +17,4 @@ class listMethods(Method):
         self.name = "system.listMethods"
 
     def call(self):
-        return self.api.methods
+        return self.api.all_methods
index 7133fa4..c6f5864 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index c4c8367..2b5ff4d 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 9292467..fc3e940 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
@@ -31,7 +32,7 @@ class NodeGroup(Row):
         'nodegroup_id': Parameter(int, "Node group identifier"),
         'groupname': Parameter(str, "Node group name", max = 50),
         'tag_type_id': Parameter (int, "Node tag type id"),
-        'tagvalue' : Parameter(str, "value that the nodegroup definition is based upon"),
+        'value' : Parameter(str, "value that the nodegroup definition is based upon"),
         'tagname' : Parameter(str, "Tag name that the nodegroup definition is based upon"),
         'conf_file_ids': Parameter([int], "List of configuration files specific to this node group"),
         'node_ids' : Parameter([int], "List of node_ids that belong to this nodegroup"),
index 4949e8c..b177e49 100644 (file)
@@ -1,8 +1,8 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
-# $Revision: 9423 $
-#
 from PLC.Faults import *
 from PLC.Parameter import Parameter
 from PLC.Filter import Filter
@@ -23,7 +23,7 @@ class NodeTag(Row):
         'node_id': Node.fields['node_id'],
         'hostname' : Node.fields['hostname'],
         'tag_type_id': TagType.fields['tag_type_id'],
-        'tagvalue': Parameter(str, "Node tag value"),
+        'value': Parameter(str, "Node tag value"),
         'tagname': TagType.fields['tagname'],
         'description': TagType.fields['description'],
         'category': TagType.fields['category'],
diff --git a/PLC/NodeTypes.py b/PLC/NodeTypes.py
new file mode 100644 (file)
index 0000000..5b20697
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# Functions for interacting with the node_types table in the database
+#
+# $Id$
+# $URL$
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class NodeType(Row):
+    """
+    Representation of a row in the node_types table. To use,
+    instantiate with a dict of values.
+    """
+
+    table_name = 'node_types'
+    primary_key = 'node_type'
+    join_tables = ['nodes']
+    fields = {
+        'node_type': Parameter(str, "Node type", max = 20),
+        }
+
+    def validate_node_type(self, name):
+       # Make sure name is not blank
+        if not len(name):
+            raise PLCInvalidArgument, "Node type must be specified"
+       
+       # Make sure node type does not alredy exist
+       conflicts = NodeTypes(self.api, [name])
+        if conflicts:
+            raise PLCInvalidArgument, "Node type name already in use"
+
+       return name
+
+class NodeTypes(Table):
+    """
+    Representation of the node_types table in the database.
+    """
+
+    def __init__(self, api, node_types = None):
+        Table.__init__(self, api, NodeType)
+
+        sql = "SELECT %s FROM node_types" % \
+              ", ".join(NodeType.fields)
+        
+        if node_types:
+            sql += " WHERE node_type IN (%s)" % ", ".join(map(api.db.quote, node_types))
+
+        self.selectall(sql)
index cc7bbcc..46fb99a 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
@@ -15,8 +16,9 @@ from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Debug import profile
 from PLC.Table import Row, Table
-from PLC.Interfaces import Interface, Interfaces
+from PLC.NodeTypes import NodeTypes
 from PLC.BootStates import BootStates
+from PLC.Interfaces import Interface, Interfaces
 
 def valid_hostname(hostname):
     # 1. Each part begins and ends with a letter or number.
@@ -38,14 +40,16 @@ class Node(Row):
 
     table_name = 'nodes'
     primary_key = 'node_id'
-    join_tables = [ 'slice_node', 'peer_node', 'slice_attribute', 
+    join_tables = [ 'slice_node', 'peer_node', 'slice_tag', 
                     'node_session', 'node_slice_whitelist', 
                     'node_tag', 'conf_file_node', 'pcu_node', ]
     fields = {
         'node_id': Parameter(int, "Node identifier"),
+        'node_type': Parameter(str,"Node type",max=20),
         'hostname': Parameter(str, "Fully qualified hostname", max = 255),
         'site_id': Parameter(int, "Site at which this node is located"),
         'boot_state': Parameter(str, "Boot state", max = 20),
+        'run_level': Parameter(str, "Run level", max = 20),
         'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
         'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
         'version': Parameter(str, "Apparent Boot CD version", max = 64),
@@ -53,6 +57,7 @@ class Node(Row):
         'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
         'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
        'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True), 
+        'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False),
         'key': Parameter(str, "(Admin only) Node key", max = 256),
         'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
         'interface_ids': Parameter([int], "List of network interfaces that this node has"),
@@ -64,14 +69,12 @@ class Node(Row):
         'ports': Parameter([int], "List of PCU ports that this node is connected to"),
         'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
         'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
-        'tag_ids' : Parameter ([int], "List of tags attached to this node"),
+        'node_tag_ids' : Parameter ([int], "List of tags attached to this node"),
         'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
         }
     related_fields = {
        'interfaces': [Mixed(Parameter(int, "Interface identifier"),
-                                      Filter(Interface.fields))],
-       'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
-                             Parameter(str, "NodeGroup name"))],
+                             Filter(Interface.fields))],
        'conf_files': [Parameter(int, "ConfFile identifier")],
        'slices': [Mixed(Parameter(int, "Slice identifier"),
                          Parameter(str, "Slice name"))],
@@ -79,6 +82,11 @@ class Node(Row):
                                    Parameter(str, "Slice name"))]
        }
 
+    view_tags_name = "view_node_tags"
+    # tags are used by the Add/Get/Update methods to expose tags
+    # this is initialized here and updated by the accessors factory
+    tags = { }
+
     def validate_hostname(self, hostname):
         if not valid_hostname(hostname):
             raise PLCInvalidArgument, "Invalid hostname"
@@ -90,11 +98,16 @@ class Node(Row):
 
         return hostname
 
+    def validate_node_type(self, node_type):
+        node_types = [row['node_type'] for row in NodeTypes(self.api)]
+        if node_type not in node_types:
+            raise PLCInvalidArgument, "Invalid node type %r"%node_type
+        return node_type
+
     def validate_boot_state(self, boot_state):
         boot_states = [row['boot_state'] for row in BootStates(self.api)]
         if boot_state not in boot_states:
-            raise PLCInvalidArgument, "Invalid boot state"
-
+            raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
         return boot_state
 
     validate_date_created = Row.validate_timestamp
@@ -235,11 +248,15 @@ class Node(Row):
         """
 
         assert 'node_id' in self
-       assert 'interface_ids' in self
 
-       # we need to clean up InterfaceSettings, so handling interfaces as part of join_tables does not work
-       for interface in Interfaces(self.api,self['interface_ids']):
-           interface.delete()
+       # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
+        # federated nodes don't have interfaces though so for smooth transition from 4.2 to 4.3
+        if 'peer_id' in self and self['peer_id']:
+            pass
+        else:
+            assert 'interface_ids' in self
+            for interface in Interfaces(self.api,self['interface_ids']):
+                interface.delete()
 
         # Clean up miscellaneous join tables
         for table in self.join_tables:
@@ -260,8 +277,15 @@ class Nodes(Table):
     def __init__(self, api, node_filter = None, columns = None):
         Table.__init__(self, api, Node, columns)
 
-        sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
-              ", ".join(self.columns)
+        # the view that we're selecting upon: start with view_nodes
+        view = "view_nodes"
+        # as many left joins as requested tags
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),
+                                                Node.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE deleted IS False" % \
+              (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if node_filter is not None:
             if isinstance(node_filter, (list, tuple, set)):
@@ -271,7 +295,8 @@ class Nodes(Table):
                 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
                 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
             elif isinstance(node_filter, dict):
-                node_filter = Filter(Node.fields, node_filter)
+                allowed_fields=dict(Node.fields.items()+Node.tags.items())
+                node_filter = Filter(allowed_fields, node_filter)
                 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
             elif isinstance (node_filter, StringTypes):
                 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
index 9475d31..8fea231 100644 (file)
@@ -4,7 +4,8 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: 
+# $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index f0e7376..3268b79 100644 (file)
@@ -4,7 +4,8 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: 
+# $Id$
+# $URL$
 #
 from types import StringTypes
 
index 27a94ae..0bcaa67 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
index 01a7ddd..ed71236 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 # Marc E. Fiuczynski <mef@cs.princeton.edu>
 # Copyright (C) 2004 The Trustees of Princeton University
 #
index fc25b93..ad72106 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import *
@@ -17,14 +18,14 @@ class Parameter:
     sub-parameters (i.e., dict fields).
     """
 
-    def __init__(self, type, doc = "",
+    def __init__(self, typeval, doc = "",
                  min = None, max = None,
                  optional = None,
                  ro = False,
                  nullok = False):
         # Basic type of the parameter. Must be a builtin type
         # that can be marshalled by XML-RPC.
-        self.type = type
+        self.type = typeval
 
         # Documentation string for the parameter
         self.doc = doc
index 582cdb0..b2cb738 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 # 
@@ -17,7 +19,7 @@ from PLC.Persons import Person, Persons
 from PLC.Keys import Key, Keys
 from PLC.Nodes import Node, Nodes
 from PLC.TagTypes import TagType, TagTypes
-from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.SliceTags import SliceTag, SliceTags
 from PLC.Slices import Slice, Slices
 
 class Peer(Row):
@@ -30,11 +32,13 @@ class Peer(Row):
     primary_key = 'peer_id'
     join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node', 'peer_slice']
     fields = {
-       'peer_id': Parameter (int, "Peer identifier"),
-       'peername': Parameter (str, "Peer name"),
-       'peer_url': Parameter (str, "Peer API URL"),
-       'key': Parameter(str, "Peer GPG public key"),
-       'cacert': Parameter(str, "Peer SSL public certificate"),
+        'peer_id': Parameter (int, "Peer identifier"),
+        'peername': Parameter (str, "Peer name"),
+        'peer_url': Parameter (str, "Peer API URL"),
+        'key': Parameter(str, "Peer GPG public key"),
+        'cacert': Parameter(str, "Peer SSL public certificate"),
+        'shortname' : Parameter(str, "Peer short name"),
+        'hrn_root' : Parameter(str, "Root of this peer in a hierarchical naming space"),
         ### cross refs
         'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
         'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
@@ -55,22 +59,24 @@ class Peer(Row):
         return peername
 
     def validate_peer_url(self, url):
-       """
-       Validate URL. Must be HTTPS.
-       """
+        """
+        Validate URL. Must be HTTPS.
+        """
 
         (scheme, netloc, path, params, query, fragment) = urlparse(url)
         if scheme != "https":
             raise PLCInvalidArgument, "Peer URL scheme must be https"
-
+        if path[-1] != '/':
+            raise PLCInvalidArgument, "Peer URL should end with /"
+        
        return url
 
     def delete(self, commit = True):
-       """
-       Deletes this peer and all related entities.
-       """
+        """
+        Deletes this peer and all related entities.
+        """
 
-       assert 'peer_id' in self
+        assert 'peer_id' in self
 
         # Remove all related entities
         for obj in \
@@ -83,8 +89,8 @@ class Peer(Row):
             obj.delete(commit = False)
 
         # Mark as deleted
-       self['deleted'] = True
-       self.sync(commit)
+        self['deleted'] = True
+        self.sync(commit)
 
     def add_site(self, site, peer_site_id, commit = True):
         """
@@ -98,6 +104,14 @@ class Peer(Row):
              'peer_site_id': peer_site_id},
             commit = commit)
 
+    def remove_site(self, site, commit = True):
+        """
+        Unassociate a site with this peer.
+        """
+        
+        remove = Row.remove_object(Site, 'peer_site')
+        remove(self, site, commit)
+
     def add_person(self, person, peer_person_id, commit = True):
         """
         Associate a local user entry with this peer.
@@ -109,6 +123,14 @@ class Peer(Row):
              'person_id': person['person_id'],
              'peer_person_id': peer_person_id},
             commit = commit)
+    
+    def remove_person(self, person, commit = True):
+        """
+        Unassociate a site with this peer.
+        """
+    
+        remove = Row.remove_object(Person, 'peer_person')
+        remove(self, person, commit)
 
     def add_key(self, key, peer_key_id, commit = True):
         """
@@ -122,6 +144,14 @@ class Peer(Row):
              'peer_key_id': peer_key_id},
             commit = commit)
 
+    def remove_key(self, key, commit = True):
+        """
+        Unassociate a key with this peer.
+        """
+    
+        remove = Row.remove_object(Key, 'peer_key')
+        remove(self, key, commit)
+
     def add_node(self, node, peer_node_id, commit = True):
         """
         Associate a local node entry with this peer.
@@ -134,6 +164,14 @@ class Peer(Row):
              'peer_node_id': peer_node_id},
             commit = commit)
 
+    def remove_node(self, node, commit = True):
+        """
+        Unassociate a node with this peer.
+        """
+    
+        remove = Row.remove_object(Node, 'peer_node')
+        remove(self, node, commit)
+
     def add_slice(self, slice, peer_slice_id, commit = True):
         """
         Associate a local slice entry with this peer.
@@ -146,6 +184,14 @@ class Peer(Row):
              'peer_slice_id': peer_slice_id},
             commit = commit)
 
+    def remove_slice(self, slice, commit = True):
+        """
+        Unassociate a slice with this peer.
+        """
+
+        remove = Row.remove_object(Slice, 'peer_slice')
+        remove(self, slice, commit)
+
     def connect(self, **kwds):
         """
         Connect to this peer via XML-RPC.
diff --git a/PLC/PersonTags.py b/PLC/PersonTags.py
new file mode 100644 (file)
index 0000000..52e50cc
--- /dev/null
@@ -0,0 +1,59 @@
+# $Id: PersonTags.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/PersonTags.py $
+#
+# Thierry Parmentelat - INRIA
+#
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.TagTypes import TagType, TagTypes
+from PLC.Persons import Person
+
+class PersonTag(Row):
+    """
+    Representation of a row in the person_tag.
+    To use, instantiate with a dict of values.
+    """
+
+    table_name = 'person_tag'
+    primary_key = 'person_tag_id'
+    fields = {
+        'person_tag_id': Parameter(int, "Person setting identifier"),
+        'person_id': Person.fields['person_id'],
+        'email': Person.fields['email'],
+        'tag_type_id': TagType.fields['tag_type_id'],
+        'tagname': TagType.fields['tagname'],
+        'description': TagType.fields['description'],
+        'category': TagType.fields['category'],
+        'min_role_id': TagType.fields['min_role_id'],
+        'value': Parameter(str, "Person setting value"),
+       ### relations
+       
+        }
+
+class PersonTags(Table):
+    """
+    Representation of row(s) from the person_tag table in the
+    database.
+    """
+
+    def __init__(self, api, person_tag_filter = None, columns = None):
+        Table.__init__(self, api, PersonTag, columns)
+
+        sql = "SELECT %s FROM view_person_tags WHERE True" % \
+              ", ".join(self.columns)
+
+        if person_tag_filter is not None:
+            if isinstance(person_tag_filter, (list, tuple, set)):
+                person_tag_filter = Filter(PersonTag.fields, {'person_tag_id': person_tag_filter})
+            elif isinstance(person_tag_filter, dict):
+                person_tag_filter = Filter(PersonTag.fields, person_tag_filter)
+            elif isinstance(person_tag_filter, int):
+                person_tag_filter = Filter(PersonTag.fields, {'person_tag_id': [person_tag_filter]})
+            else:
+                raise PLCInvalidArgument, "Wrong person setting filter %r"%person_tag_filter
+            sql += " AND (%s) %s" % person_tag_filter.sql(api)
+
+
+        self.selectall(sql)
index ee01550..1d3059f 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
@@ -56,6 +57,7 @@ class Person(Row):
         'slice_ids': Parameter([int], "List of slice identifiers"),
         'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
         'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
+        'person_tag_ids' : Parameter ([int], "List of tags attached to this person"),
         }
     related_fields = {
        'roles': [Mixed(Parameter(int, "Role identifier"),
@@ -67,6 +69,10 @@ class Person(Row):
        'slices': [Mixed(Parameter(int, "Slice identifier"),
                         Parameter(str, "Slice name"))]
        }       
+    view_tags_name = "view_person_tags"
+    # tags are used by the Add/Get/Update methods to expose tags
+    # this is initialized here and updated by the accessors factory
+    tags = { }
 
     def validate_email(self, email):
         """
@@ -74,26 +80,12 @@ class Person(Row):
         """
 
         invalid_email = PLCInvalidArgument("Invalid e-mail address")
-        email_badchars = r'[][()<>|;^,\200-\377]'
 
-        # Pretty minimal, cheesy check.  We could do better...
-        if not email or email.count(' ') > 0:
-            raise invalid_email
-        if re.search(email_badchars, email) or email[0] == '-':
+        if not email:
             raise invalid_email
 
-        email = email.lower()
-        at_sign = email.find('@')
-        if at_sign < 1:
-            raise invalid_email
-        user = email[:at_sign]
-        rest = email[at_sign+1:]
-        domain = rest.split('.')
-
-        # This means local, unqualified addresses, are not allowed
-        if not domain:
-            raise invalid_email
-        if len(domain) < 2:
+       email_re = re.compile('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z')
+        if not email_re.match(email):
             raise invalid_email
 
                # check only against users on the same peer  
@@ -152,8 +144,8 @@ class Person(Row):
 
         if 'pi' in self['roles']:
             if set(self['site_ids']).intersection(person['site_ids']):
-                # Can update people with higher role IDs
-                return min(self['role_ids']) < min(person['role_ids'])
+                # Can update person is neither a PI or ADMIN
+                return (not (('pi' in person['roles']) or ('admin' in person['roles'])))
 
         return False
 
@@ -175,7 +167,7 @@ class Person(Row):
         if 'pi' in self['roles']:
             if set(self['site_ids']).intersection(person['site_ids']):
                 # Can view people with equal or higher role IDs
-                return min(self['role_ids']) <= min(person['role_ids'])
+                return 'admin' not in person['roles']
 
         return False
 
@@ -239,7 +231,7 @@ class Person(Row):
        
        # Translate roles into role_ids
        if role_names:
-           roles = Roles(self.api, role_names, ['role_id']).dict('role_id')
+           roles = Roles(self.api, role_names).dict('role_id')
            role_ids += roles.keys()
        
        # Add new ids, remove stale ids
@@ -379,64 +371,14 @@ class Persons(Table):
     def __init__(self, api, person_filter = None, columns = None):
         Table.__init__(self, api, Person, columns)
 
-       foreign_fields = {'role_ids': ('role_id', 'person_role'),
-                         'roles': ('name', 'roles'),
-                          'site_ids': ('site_id', 'person_site'),
-                          'key_ids': ('key_id', 'person_key'),
-                          'slice_ids': ('slice_id', 'slice_person')
-                          }
-       foreign_keys = {}
-       db_fields = filter(lambda field: field not in foreign_fields.keys(), Person.fields.keys())
-       all_fields = db_fields + [value[0] for value in foreign_fields.values()]
-       fields = []
-       _select = "SELECT "
-       _from = " FROM persons "
-       _join = " LEFT JOIN peer_person USING (person_id) "  
-       _where = " WHERE deleted IS False "
-
-       if not columns:
-           # include all columns       
-           fields = all_fields
-           tables = [value[1] for value in foreign_fields.values()]
-           tables.sort()
-           for key in foreign_fields.keys():
-               foreign_keys[foreign_fields[key][0]] = key  
-           for table in tables:
-               if table in ['roles']:
-                   _join += " LEFT JOIN roles USING(role_id) "
-               else:   
-                   _join += " LEFT JOIN %s USING (person_id) " % (table)
-       else: 
-           tables = set()
-           columns = filter(lambda column: column in db_fields+foreign_fields.keys(), columns)
-           columns.sort()
-           for column in columns: 
-               if column in foreign_fields.keys():
-                   (field, table) = foreign_fields[column]
-                   foreign_keys[field] = column
-                   fields += [field]
-                   tables.add(table)
-                   if column in ['roles']:
-                       _join += " LEFT JOIN roles USING(role_id) "
-                   else:
-                       _join += " LEFT JOIN %s USING (person_id)" % \
-                               (foreign_fields[column][1])
-               
-               else:
-                   fields += [column]  
-       
-       # postgres will return timestamps as datetime objects. 
-       # XMLPRC cannot marshal datetime so convert to int
-       timestamps = ['date_created', 'last_updated', 'verification_expires']
-       for field in fields:
-           if field in timestamps:
-               fields[fields.index(field)] = \
-                "CAST(date_part('epoch', %s) AS bigint) AS %s" % (field, field)
-
-       _select += ", ".join(fields)
-       sql = _select + _from + _join + _where
-
-       # deal with filter                      
+        view = "view_persons"
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Person.tagvalue_view_name(tagname),
+                                                Person.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE deleted IS False" % \
+            (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
+
         if person_filter is not None:
             if isinstance(person_filter, (list, tuple, set)):
                 # Separate the list into integers and strings
@@ -456,29 +398,4 @@ class Persons(Table):
             else:
                 raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
 
-       # aggregate data
-       all_persons = {}
-       for row in self.api.db.selectall(sql):
-           person_id = row['person_id']
-
-           if all_persons.has_key(person_id):
-               for (key, key_list) in foreign_keys.items():
-                   data = row.pop(key)
-                   row[key_list] = [data]
-                   if data and data not in all_persons[person_id][key_list]:
-                       all_persons[person_id][key_list].append(data)
-            else:
-               for key in foreign_keys.keys():
-                    value = row.pop(key)
-                   if value:   
-                       row[foreign_keys[key]] = [value]
-                   else:
-                       row[foreign_keys[key]] = []
-               if row: 
-                   all_persons[person_id] = row
-               
-       # populate self
-       for row in all_persons.values():
-           obj = self.classobj(self.api, row)
-            self.append(obj)
-
+        self.selectall(sql)
index 98ef727..076a864 100644 (file)
@@ -6,6 +6,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import psycopg2
@@ -52,6 +53,7 @@ class PostgreSQL:
     def __init__(self, api):
         self.api = api
         self.debug = False
+#        self.debug = True
         self.connection = None
 
     def cursor(self):
@@ -87,6 +89,12 @@ class PostgreSQL:
             self.connection.close()
             self.connection = None
 
+    # join insists on getting strings
+    @classmethod
+    def quote_string(self, value):
+        return str(PostgreSQL.quote(value))
+
+    @classmethod
     def quote(self, value):
         """
         Returns quoted version of the specified value.
@@ -95,12 +103,11 @@ class PostgreSQL:
         # The pgdb._quote function is good enough for general SQL
         # quoting, except for array types.
         if isinstance(value, (list, tuple, set)):
-            return "ARRAY[%s]" % ", ".join(map, self.quote, value)
+            return "ARRAY[%s]" % ", ".join(map (PostgreSQL.quote_string, value))
         else:
             return pgdb._quote(value)
 
-    quote = classmethod(quote)
-
+    @classmethod
     def param(self, name, value):
         # None is converted to the unquoted string NULL
         if isinstance(value, NoneType):
@@ -117,8 +124,6 @@ class PostgreSQL:
 
         return '%(' + name + ')' + conversion
 
-    param = classmethod(param)
-
     def begin_work(self):
         # Implicit in pgdb.connect()
         pass
@@ -165,8 +170,13 @@ class PostgreSQL:
 
             # psycopg2 requires %()s format for all parameters,
             # regardless of type.
+            # this needs to be done carefully though as with pattern-based filters
+            # we might have percents embedded in the query
+            # so e.g. GetPersons({'email':'*fake*'}) was resulting in .. LIKE '%sake%'
             if psycopg2:
                 query = re.sub(r'(%\([^)]*\)|%)[df]', r'\1s', query)
+            # rewrite wildcards set by Filter.py as '***' into '%'
+            query = query.replace ('***','%')
 
             if not params:
                 if self.debug:
@@ -221,7 +231,7 @@ class PostgreSQL:
         cursor = self.execute(query, params)
         rows = cursor.fetchall()
         cursor.close()
-
+        self.commit()
         if hashref or key_field is not None:
             # Return each row as a dictionary keyed on field name
             # (like DBI selectrow_hashref()).
index 32051b4..f688bc0 100644 (file)
@@ -6,6 +6,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os
@@ -15,7 +16,8 @@ from tempfile import NamedTemporaryFile
 
 class PyCurlTransport(xmlrpclib.Transport):
     def __init__(self, uri, cert = None, timeout = 300):
-        xmlrpclib.Transport.__init__(self)
+        if hasattr(xmlrpclib.Transport,'__init__'):
+            xmlrpclib.Transport.__init__(self)
         self.curl = pycurl.Curl()
 
         # Suppress signals
index ddb33af..d85444f 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from types import StringTypes
index e0a57b3..33fd3b1 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from types import StringTypes
 import random
 import base64
index 7807c51..9069a09 100644 (file)
@@ -6,6 +6,7 @@
 # Copyright (C) 2005 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os
diff --git a/PLC/SiteTags.py b/PLC/SiteTags.py
new file mode 100644 (file)
index 0000000..53634ab
--- /dev/null
@@ -0,0 +1,59 @@
+# $Id: SiteTags.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: http://svn.planet-lab.org/svn/PLCAPI/tags/PLCAPI-4.3-27/PLC/SiteTags.py $
+#
+# Thierry Parmentelat - INRIA
+#
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.TagTypes import TagType, TagTypes
+from PLC.Sites import Site
+
+class SiteTag(Row):
+    """
+    Representation of a row in the site_tag.
+    To use, instantiate with a dict of values.
+    """
+
+    table_name = 'site_tag'
+    primary_key = 'site_tag_id'
+    fields = {
+        'site_tag_id': Parameter(int, "Site setting identifier"),
+        'site_id': Site.fields['site_id'],
+        'login_base': Site.fields['login_base'],
+        'tag_type_id': TagType.fields['tag_type_id'],
+        'tagname': TagType.fields['tagname'],
+        'description': TagType.fields['description'],
+        'category': TagType.fields['category'],
+        'min_role_id': TagType.fields['min_role_id'],
+        'value': Parameter(str, "Site setting value"),
+       ### relations
+       
+        }
+
+class SiteTags(Table):
+    """
+    Representation of row(s) from the site_tag table in the
+    database.
+    """
+
+    def __init__(self, api, site_tag_filter = None, columns = None):
+        Table.__init__(self, api, SiteTag, columns)
+
+        sql = "SELECT %s FROM view_site_tags WHERE True" % \
+              ", ".join(self.columns)
+
+        if site_tag_filter is not None:
+            if isinstance(site_tag_filter, (list, tuple, set)):
+                site_tag_filter = Filter(SiteTag.fields, {'site_tag_id': site_tag_filter})
+            elif isinstance(site_tag_filter, dict):
+                site_tag_filter = Filter(SiteTag.fields, site_tag_filter)
+            elif isinstance(site_tag_filter, int):
+                site_tag_filter = Filter(SiteTag.fields, {'site_tag_id': [site_tag_filter]})
+            else:
+                raise PLCInvalidArgument, "Wrong site setting filter %r"%site_tag_filter
+            sql += " AND (%s) %s" % site_tag_filter.sql(api)
+
+
+        self.selectall(sql)
index ab8854d..ef9f02a 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from types import StringTypes
 import string
 
@@ -43,6 +45,7 @@ class Site(Row):
         'node_ids': Parameter([int], "List of site node identifiers"),
         'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
+        'site_tag_ids' : Parameter ([int], "List of tags attached to this site"),
        'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
         }
     related_fields = {
@@ -51,6 +54,10 @@ class Site(Row):
        'addresses': [Mixed(Parameter(int, "Address identifer"),
                             Filter(Address.fields))]
        }
+    view_tags_name = "view_site_tags"
+    # tags are used by the Add/Get/Update methods to expose tags
+    # this is initialized here and updated by the accessors factory
+    tags = { }
 
     def validate_name(self, name):
         if not len(name):
@@ -236,9 +243,14 @@ class Sites(Table):
 
     def __init__(self, api, site_filter = None, columns = None):
         Table.__init__(self, api, Site, columns)
-
-        sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
-              ", ".join(self.columns)
+       
+        view = "view_sites"
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Site.tagvalue_view_name(tagname),
+                                                Site.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE deleted IS False" % \
+            (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if site_filter is not None:
             if isinstance(site_filter, (list, tuple, set)):
diff --git a/PLC/SliceAttributes.py b/PLC/SliceAttributes.py
deleted file mode 100644 (file)
index eafebc4..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-from PLC.Faults import *
-from PLC.Parameter import Parameter
-from PLC.Filter import Filter
-from PLC.Table import Row, Table
-from PLC.TagTypes import TagType, TagTypes
-
-class SliceAttribute(Row):
-    """
-    Representation of a row in the slice_attribute table. To use,
-    instantiate with a dict of values.
-    """
-
-    table_name = 'slice_attribute'
-    primary_key = 'slice_attribute_id'
-    fields = {
-        'slice_attribute_id': Parameter(int, "Slice attribute identifier"),
-        'slice_id': Parameter(int, "Slice identifier"),
-        'node_id': Parameter(int, "Node identifier, if a sliver attribute"),
-       'nodegroup_id': Parameter(int, "Nodegroup identifier, if a sliver attribute"),
-        'tag_type_id': TagType.fields['tag_type_id'],
-        'tagname': TagType.fields['tagname'],
-        'description': TagType.fields['description'],
-        'category': TagType.fields['category'],
-        'min_role_id': TagType.fields['min_role_id'],
-        'value': Parameter(str, "Slice attribute value"),
-        }
-
-class SliceAttributes(Table):
-    """
-    Representation of row(s) from the slice_attribute table in the
-    database.
-    """
-
-    def __init__(self, api, slice_attribute_filter = None, columns = None):
-        Table.__init__(self, api, SliceAttribute, columns)
-
-        sql = "SELECT %s FROM view_slice_attributes WHERE True" % \
-              ", ".join(self.columns)
-
-        if slice_attribute_filter is not None:
-            if isinstance(slice_attribute_filter, (list, tuple, set)):
-                slice_attribute_filter = Filter(SliceAttribute.fields, {'slice_attribute_id': slice_attribute_filter})
-            elif isinstance(slice_attribute_filter, dict):
-                slice_attribute_filter = Filter(SliceAttribute.fields, slice_attribute_filter)
-            sql += " AND (%s) %s" % slice_attribute_filter.sql(api)
-
-        self.selectall(sql)
index 4996a1e..1087e32 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from PLC.Faults import *
diff --git a/PLC/SliceTags.py b/PLC/SliceTags.py
new file mode 100644 (file)
index 0000000..4b412b6
--- /dev/null
@@ -0,0 +1,54 @@
+# $Id$
+# $URL$
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+# seems to cause import loops
+#from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.TagTypes import TagType, TagTypes
+
+class SliceTag(Row):
+    """
+    Representation of a row in the slice_tag table. To use,
+    instantiate with a dict of values.
+    """
+
+    table_name = 'slice_tag'
+    primary_key = 'slice_tag_id'
+    fields = {
+        'slice_tag_id': Parameter(int, "Slice tag identifier"),
+        'slice_id': Parameter(int, "Slice identifier"),
+        'name': Parameter(str, "Slice name"),
+        'node_id': Node.fields['node_id'],
+       'nodegroup_id': NodeGroup.fields['nodegroup_id'],
+        'tag_type_id': TagType.fields['tag_type_id'],
+        'tagname': TagType.fields['tagname'],
+        'description': TagType.fields['description'],
+        'category': TagType.fields['category'],
+        'min_role_id': TagType.fields['min_role_id'],
+        'value': Parameter(str, "Slice attribute value"),
+        }
+
+class SliceTags(Table):
+    """
+    Representation of row(s) from the slice_tag table in the
+    database.
+    """
+
+    def __init__(self, api, slice_tag_filter = None, columns = None):
+        Table.__init__(self, api, SliceTag, columns)
+
+        sql = "SELECT %s FROM view_slice_tags WHERE True" % \
+              ", ".join(self.columns)
+
+        if slice_tag_filter is not None:
+            if isinstance(slice_tag_filter, (list, tuple, set)):
+                slice_tag_filter = Filter(SliceTag.fields, {'slice_tag_id': slice_tag_filter})
+            elif isinstance(slice_tag_filter, dict):
+                slice_tag_filter = Filter(SliceTag.fields, slice_tag_filter)
+            sql += " AND (%s) %s" % slice_tag_filter.sql(api)
+
+        self.selectall(sql)
index 5dcde46..aeb9f09 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from types import StringTypes
 import time
 import re
@@ -10,7 +12,7 @@ from PLC.Table import Row, Table
 from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
 from PLC.Nodes import Node
 from PLC.Persons import Person, Persons
-from PLC.SliceAttributes import SliceAttribute
+from PLC.SliceTags import SliceTag
 
 class Slice(Row):
     """
@@ -22,7 +24,7 @@ class Slice(Row):
 
     table_name = 'slices'
     primary_key = 'slice_id'
-    join_tables = ['slice_node', 'slice_person', 'slice_attribute', 'peer_slice', 'node_slice_whitelist']
+    join_tables = ['slice_node', 'slice_person', 'slice_tag', 'peer_slice', 'node_slice_whitelist']
     fields = {
         'slice_id': Parameter(int, "Slice identifier"),
         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
@@ -36,7 +38,7 @@ class Slice(Row):
         'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"),
         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
-        'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
+        'slice_tag_ids': Parameter([int], "List of slice attributes", ro = True),
         'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
         'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
         }
@@ -47,6 +49,9 @@ class Slice(Row):
                        Parameter(str, "Fully qualified hostname"))]
        }
 
+    view_tags_name="view_slice_tags"
+    tags = {}
+
     def validate_name(self, name):
         # N.B.: Responsibility of the caller to ensure that login_base
         # portion of the slice name corresponds to a valid site, if
@@ -151,14 +156,14 @@ class Slice(Row):
                AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, self['slice_id'], list(new_nodes))
            if stale_nodes:
                DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, self['slice_id'], list(stale_nodes))                        
-    def associate_slice_attributes(self, auth, fields, value):
+    def associate_slice_tags(self, auth, fields, value):
        """
-       Deletes slice_attribute_ids not found in value list (using DeleteSliceAttribute). 
-       Adds slice_attributes if slice_fields w/o slice_id is found (using AddSliceAttribute).
-       Updates slice_attribute if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).  
+       Deletes slice_tag_ids not found in value list (using DeleteSliceTag). 
+       Adds slice_tags if slice_fields w/o slice_id is found (using AddSliceTag).
+       Updates slice_tag if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).  
        """
        
-       assert 'slice_attribute_ids' in self
+       assert 'slice_tag_ids' in self
        assert isinstance(value, list)
 
        (attribute_ids, blank, attributes) = self.separate_types(value)
@@ -166,21 +171,21 @@ class Slice(Row):
        # There is no way to add attributes by id. They are
        # associated with a slice when they are created.
        # So we are only looking to delete here 
-       if self['slice_attribute_ids'] != attribute_ids:
-           from PLC.Methods.DeleteSliceAttribute import DeleteSliceAttribute
-           stale_attributes = set(self['slice_attribute_ids']).difference(attribute_ids)
+       if self['slice_tag_ids'] != attribute_ids:
+           from PLC.Methods.DeleteSliceTag import DeleteSliceTag
+           stale_attributes = set(self['slice_tag_ids']).difference(attribute_ids)
        
            for stale_attribute in stale_attributes:
-               DeleteSliceAttribute.__call__(DeleteSliceAttribute(self.api), auth, stale_attribute['slice_attribute_id'])              
+               DeleteSliceTag.__call__(DeleteSliceTag(self.api), auth, stale_attribute['slice_tag_id'])                
        
        # If dictionary exists, we are either adding new
         # attributes or updating existing ones.
         if attributes:
-            from PLC.Methods.AddSliceAttribute import AddSliceAttribute
-            from PLC.Methods.UpdateSliceAttribute import UpdateSliceAttribute
+            from PLC.Methods.AddSliceTag import AddSliceTag
+            from PLC.Methods.UpdateSliceTag import UpdateSliceTag
        
-           added_attributes = filter(lambda x: 'slice_attribute_id' not in x, attributes)
-           updated_attributes = filter(lambda x: 'slice_attribute_id' in x, attributes)
+           added_attributes = filter(lambda x: 'slice_tag_id' not in x, attributes)
+           updated_attributes = filter(lambda x: 'slice_tag_id' in x, attributes)
 
            for added_attribute in added_attributes:
                if 'tag_type' in added_attribute:
@@ -205,13 +210,13 @@ class Slice(Row):
                else:
                    nodegroup_id = None 
  
-               AddSliceAttribute.__call__(AddSliceAttribute(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
+               AddSliceTag.__call__(AddSliceTag(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
            for updated_attribute in updated_attributes:
-               attribute_id = updated_attribute.pop('slice_attribute_id')
-               if attribute_id not in self['slice_attribute_ids']:
+               attribute_id = updated_attribute.pop('slice_tag_id')
+               if attribute_id not in self['slice_tag_ids']:
                    raise PLCInvalidArgument, "Attribute doesnt belong to this slice" 
                else:
-                   UpdateSliceAttribute.__call__(UpdateSliceAttribute(self.api), auth, attribute_id, updated_attribute)                 
+                   UpdateSliceTag.__call__(UpdateSliceTag(self.api), auth, attribute_id, updated_attribute)             
        
     def sync(self, commit = True):
         """
@@ -252,8 +257,15 @@ class Slices(Table):
     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
         Table.__init__(self, api, Slice, columns)
 
-        sql = "SELECT %s FROM view_slices WHERE is_deleted IS False" % \
-              ", ".join(self.columns)
+        # the view that we're selecting upon: start with view_slices
+        view = "view_slices"
+        # as many left joins as requested tags
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Slice.tagvalue_view_name(tagname),
+                                                Slice.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE is_deleted IS False" % \
+              (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if expires is not None:
             if expires >= 0:
index 74da3e9..434db2e 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 from types import StringTypes, IntType, LongType
 import time
 import calendar
@@ -13,11 +15,13 @@ class Row(dict):
     """
 
     # Set this to the name of the table that stores the row.
+    # e.g. table_name = "nodes"
     table_name = None
 
     # Set this to the name of the primary key of the table. It is
     # assumed that the this key is a sequence if it is not set when
     # sync() is called.
+    # e.g. primary_key="node_id"
     primary_key = None
 
     # Set this to the names of tables that reference this table's
@@ -29,9 +33,21 @@ class Row(dict):
     # sync().
     fields = {}
 
+    # The name of the view that extends objects with tags
+    # e.g. view_tags_name = "view_node_tags"
+    view_tags_name = None
+
+    # Set this to the set of tags that can be returned by the Get function
+    tags = {}
+
     def __init__(self, api, fields = {}):
         dict.__init__(self, fields)
         self.api = api
+        # run the class_init initializer once
+        cls=self.__class__
+        if not hasattr(cls,'class_inited'):
+            cls.class_init (api)
+            cls.class_inited=True # actual value does not matter
 
     def validate(self):
         """
@@ -68,8 +84,8 @@ class Row(dict):
 
     def associate(self, *args):
        """
-       Provides a means for high lvl api calls to associate objects
-        using low lvl calls.
+       Provides a means for high level api calls to associate objects
+        using low level calls.
        """
 
        if len(args) < 3:
@@ -179,6 +195,17 @@ class Row(dict):
 
     remove_object = classmethod(remove_object)
 
+    # convenience: check in dict (self.fields or self.tags) that a key is writable
+    @staticmethod
+    def is_writable (key,value,dict):
+        # if not mentioned, assume it's writable (e.g. deleted ...)
+        if key not in dict: return True
+        # if mentioned but not linked to a Parameter object, idem
+        if not isinstance(dict[key], Parameter): return True
+        # if not marked ro, it's writable
+        if not dict[key].ro: return True
+        return False
+
     def db_fields(self, obj = None):
         """
         Return only those fields that can be set or updated directly
@@ -186,16 +213,113 @@ class Row(dict):
         for this object, and are not marked as a read-only Parameter.
         """
 
-        if obj is None:
-            obj = self
+        if obj is None: obj = self
 
         db_fields = self.api.db.fields(self.table_name)
-        return dict(filter(lambda (key, value): \
-                           key in db_fields and \
-                           (key not in self.fields or \
-                            not isinstance(self.fields[key], Parameter) or \
-                            not self.fields[key].ro),
-                           obj.items()))
+        return dict ( [ (key,value) for (key,value) in obj.items()
+                        if key in db_fields and
+                        Row.is_writable(key,value,self.fields) ] )
+
+    def tag_fields (self, obj=None):
+        """
+        Return the fields of obj that are mentioned in tags
+        """
+        if obj is None: obj=self
+        
+        return dict ( [ (key,value) for (key,value) in obj.iteritems() 
+                        if key in self.tags and Row.is_writable(key,value,self.tags) ] )
+    
+    # takes as input a list of columns, sort native fields from tags
+    # returns 2 dicts and one list : fields, tags, rejected
+    @classmethod
+    def parse_columns (cls, columns):
+        (fields,tags,rejected)=({},{},[])
+        for column in columns:
+            if column in cls.fields: fields[column]=cls.fields[column]
+            elif column in cls.tags: tags[column]=cls.tags[column]
+            else: rejected.append(column)
+        return (fields,tags,rejected)
+
+    # compute the 'accepts' part of a method, from a list of column names, and a fields dict
+    # use exclude=True to exclude the column names instead
+    # typically accepted_fields (Node.fields,['hostname','model',...])
+    @staticmethod
+    def accepted_fields (update_columns, fields_dict, exclude=False):
+        result={}
+        for (k,v) in fields_dict.iteritems():
+            if (not exclude and k in update_columns) or (exclude and k not in update_columns):
+                result[k]=v
+        return result
+
+    # filter out user-provided fields that are not part of the declared acceptance list
+    # keep it separate from split_fields for simplicity
+    # typically check_fields (<user_provided_dict>,{'hostname':Parameter(str,...),'model':Parameter(..)...})
+    @staticmethod
+    def check_fields (user_dict, accepted_fields):
+# avoid the simple, but silent, version
+#        return dict ([ (k,v) for (k,v) in user_dict.items() if k in accepted_fields ])
+        result={}
+        for (k,v) in user_dict.items():
+            if k in accepted_fields: result[k]=v
+            else: raise PLCInvalidArgument ('Trying to set/change unaccepted key %s'%k)
+        return result
+
+    # given a dict (typically passed to an Update method), we check and sort
+    # them against a list of dicts, e.g. [Node.fields, Node.related_fields]
+    # return is a list that contains n+1 dicts, last one has the rejected fields
+    @staticmethod
+    def split_fields (fields, dicts):
+        result=[]
+        for x in dicts: result.append({})
+        rejected={}
+        for (field,value) in fields.iteritems():
+            found=False
+            for i in range(len(dicts)):
+                candidate_dict=dicts[i]
+                if field in candidate_dict.keys():
+                    result[i][field]=value
+                    found=True
+                    break 
+            if not found: rejected[field]=value
+        result.append(rejected)
+        return result
+
+    ### class initialization : create tag-dependent cross view if needed
+    @classmethod
+    def tagvalue_view_name (cls, tagname):
+        return "tagvalue_view_%s_%s"%(cls.primary_key,tagname)
+
+    @classmethod
+    def tagvalue_view_create_sql (cls,tagname):
+        """
+        returns a SQL sentence that creates a view named after the primary_key and tagname, 
+        with 2 columns
+        (*) column 1: primary_key 
+        (*) column 2: actual tag value, renamed into tagname
+        """
+
+        if not cls.view_tags_name: 
+            raise Exception, 'WARNING: class %s needs to set view_tags_name'%cls.__name__
+
+        table_name=cls.table_name
+        primary_key=cls.primary_key
+        view_tags_name=cls.view_tags_name
+        tagvalue_view_name=cls.tagvalue_view_name(tagname)
+        return 'CREATE OR REPLACE VIEW %(tagvalue_view_name)s ' \
+            'as SELECT %(table_name)s.%(primary_key)s,%(view_tags_name)s.value as "%(tagname)s" ' \
+            'from %(table_name)s right join %(view_tags_name)s using (%(primary_key)s) ' \
+            'WHERE tagname = \'%(tagname)s\';'%locals()
+
+    @classmethod
+    def class_init (cls,api):
+        cls.tagvalue_views_create (api)
+
+    @classmethod
+    def tagvalue_views_create (cls,api):
+        if not cls.tags: return
+        for tagname in cls.tags.keys():
+            api.db.do(cls.tagvalue_view_create_sql (tagname))
+        api.db.commit()
 
     def __eq__(self, y):
         """
@@ -231,8 +355,9 @@ class Row(dict):
            keys == [self.primary_key] or \
            insert is True:
            
-           # If primary key id is a serial int, get next id
-           if self.fields[self.primary_key].type in (IntType, LongType):
+           # If primary key id is a serial int and it isnt included, get next id
+           if self.fields[self.primary_key].type in (IntType, LongType) and \
+              self.primary_key not in self:
                pk_id = self.api.db.next_id(self.table_name, self.primary_key)
                self[self.primary_key] = pk_id
                db_fields[self.primary_key] = pk_id
@@ -291,12 +416,16 @@ class Table(list):
 
         if columns is None:
             columns = classobj.fields
+            tag_columns={}
         else:
-            columns = filter(lambda x: x in classobj.fields, columns)
-            if not columns:
-                raise PLCInvalidArgument, "No valid return fields specified"
+            (columns,tag_columns,rejected) = classobj.parse_columns(columns)
+            if not columns and not tag_columns:
+                raise PLCInvalidArgument, "No valid return fields specified for class %s"%classobj.__name__
+            if rejected:
+                raise PLCInvalidArgument, "unknown column(s) specified %r in %s"%(rejected,classobj.__name__)
 
         self.columns = columns
+        self.tag_columns = tag_columns
 
     def sync(self, commit = True):
         """
index c94be0b..a7a3f4e 100644 (file)
@@ -1,8 +1,8 @@
+# $Id$
+# $URL$
 #
 # Thierry Parmentelat - INRIA
 #
-# $Revision: 9423 $
-#
 from types import StringTypes
 
 from PLC.Faults import *
@@ -21,7 +21,7 @@ class TagType (Row):
 
     table_name = 'tag_types'
     primary_key = 'tag_type_id'
-    join_tables = ['node_tag', 'interface_setting', 'slice_attribute' ]
+    join_tables = ['node_tag', 'interface_tag', 'slice_tag', 'site_tag', 'person_tag' ]
     fields = {
         'tag_type_id': Parameter(int, "Node tag type identifier"),
         'tagname': Parameter(str, "Node tag type name", max = 100),
index 22f8b24..0be5ac4 100644 (file)
@@ -1,3 +1,4 @@
+## Please use make index to update this file
 all = """
 API
 AddressTypes
@@ -15,7 +16,7 @@ Filter
 GPG
 Ilinks
 InitScripts
-InterfaceSettings
+InterfaceTags
 Interfaces
 KeyTypes
 Keys
@@ -25,6 +26,7 @@ NetworkMethods
 NetworkTypes
 NodeGroups
 NodeTags
+NodeTypes
 Nodes
 PCUProtocolTypes
 PCUTypes
@@ -32,17 +34,25 @@ PCUs
 POD
 Parameter
 Peers
+PersonTags
 Persons
 PostgreSQL
 PyCurl
 Roles
 Sessions
 Shell
+SiteTags
 Sites
-SliceAttributes
 SliceInstantiations
+SliceTags
 Slices
 Table
 TagTypes
 sendmail
+v42Legacy
+v42LegacyNodeNetworkSettingTypes
+v42LegacyNodeNetworkSettings
+v42LegacyNodeNetworks
+v42LegacySliceAttributeTypes
+v42LegacySliceAttributes
 """.split()
index cab0ae6..fe4b7b9 100644 (file)
@@ -1,3 +1,5 @@
+# $Id$
+# $URL$
 import os
 import sys
 import pprint
diff --git a/PLC/v42Legacy.py b/PLC/v42Legacy.py
new file mode 100644 (file)
index 0000000..a6a063d
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+
+# apply rename on list (columns) or dict (filter) args
+def patch (arg,rename):
+    if isinstance(arg,list):
+        for i in range(0,len(arg)):
+            arg[i] = patch(arg[i],rename)
+        return arg
+    if isinstance(arg,dict):
+        return dict ( [ (rename(k),v) for (k,v) in arg.iteritems() ] )
+    return rename(arg)
+
diff --git a/PLC/v42LegacyNodeNetworkSettingTypes.py b/PLC/v42LegacyNodeNetworkSettingTypes.py
new file mode 100644 (file)
index 0000000..c7b937e
--- /dev/null
@@ -0,0 +1,11 @@
+# $Id$
+# $URL$
+
+# mapping of argument/return names for *NodeNetworkSettingType*
+v42_to_v43_argmap = { "name":"tagname",
+                      "nodenetwork_setting_type_id": "tag_type_id",
+                      }
+v43_to_v42_argmap = dict([ (v,k) for k,v in v42_to_v43_argmap.iteritems()])
+
+def v42rename (x): return v42_to_v43_argmap.get(x,x)
+def v43rename (x): return v43_to_v42_argmap.get(x,x)
diff --git a/PLC/v42LegacyNodeNetworkSettings.py b/PLC/v42LegacyNodeNetworkSettings.py
new file mode 100644 (file)
index 0000000..1171f9e
--- /dev/null
@@ -0,0 +1,16 @@
+# $Id$
+# $URL$
+
+# mapping of argument/return names for *NodeNetworkSettings* and *InterfaceTags* calls
+v42_to_v43_argmap = {
+    "nodenetwork_id":"interface_id",
+    "nodenetwork_ids":"interface_ids",
+    "nodenetwork_setting_id":"interface_tag_id",
+    "nodenetwork_setting_ids":"interface_tag_ids",
+    "nodenetwork_setting_type_id":"tag_type_id",
+    "name":"tagname"
+    }
+v43_to_v42_argmap = dict([ (v,k) for k,v in v42_to_v43_argmap.iteritems()])
+
+def v42rename (x): return v42_to_v43_argmap.get(x,x)
+def v43rename (x): return v43_to_v42_argmap.get(x,x)
diff --git a/PLC/v42LegacyNodeNetworks.py b/PLC/v42LegacyNodeNetworks.py
new file mode 100644 (file)
index 0000000..21a4a7d
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+
+# mapping of argument/return names for *NodeNetwork* and *Interface* calls
+v42_to_v43_argmap = {
+    "nodenetwork_id":"interface_id",
+    "nodenetwork_ids":"interface_ids",
+    "nodenetwork_setting_ids":"interface_tag_ids",
+}
+v43_to_v42_argmap = dict([ (v,k) for k,v in v42_to_v43_argmap.iteritems()])
+
+def v42rename (x): return v42_to_v43_argmap.get(x,x)
+def v43rename (x): return v43_to_v42_argmap.get(x,x)
diff --git a/PLC/v42LegacySliceAttributeTypes.py b/PLC/v42LegacySliceAttributeTypes.py
new file mode 100644 (file)
index 0000000..0636984
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id$
+# $URL$
+
+# mapping of argument/return names for *SliceAttributeType* calls
+
+v42_to_v43_argmap = { "name":"tagname",
+                      "slice_attribute_id":"slice_tag_id",
+                      "attribute_type_id":"tag_type_id",
+                      }
+v43_to_v42_argmap = dict([ (v,k) for k,v in v42_to_v43_argmap.iteritems()])
+
+def v42rename (x): return v42_to_v43_argmap.get(x,x)
+def v43rename (x): return v43_to_v42_argmap.get(x,x)
diff --git a/PLC/v42LegacySliceAttributes.py b/PLC/v42LegacySliceAttributes.py
new file mode 100644 (file)
index 0000000..09d906c
--- /dev/null
@@ -0,0 +1,12 @@
+# $Id$
+# $URL$
+
+# mapping of argument/return names for *{SliceAttribute,NetworkSetting}Type* and *TagType* calls
+
+v42_to_v43_argmap = { "name":"tagname",
+                      "slice_attribute_id":"slice_tag_id",
+                      }
+v43_to_v42_argmap = dict([ (v,k) for k,v in v42_to_v43_argmap.iteritems()])
+
+def v42rename (x): return v42_to_v43_argmap.get(x,x)
+def v43rename (x): return v43_to_v42_argmap.get(x,x)
index 5b7aa79..c364e18 100644 (file)
@@ -28,6 +28,20 @@ Obsoletes: plcapilib
 
 # We use set everywhere
 Requires: python >= 2.4
+Requires: postgresql >= 8.2, postgresql-server >= 8.2
+Requires: postgresql-python
+Requires: python-psycopg2
+Requires: python-pycurl
+Requires: httpd
+Requires: mod_python
+Requires: mod_ssl
+Requires: SOAPpy
+# for the RebootNodeWithPCU method
+Requires: pcucontrol
+
+### avoid having yum complain about updates, as stuff is moving around
+# plc.d/api
+Conflicts: MyPLC <= 4.3
 
 # We use psycopg2
 BuildRequires: postgresql-devel
@@ -56,10 +70,11 @@ through Apache mod_python.
 %build
 # python-pycurl and python-psycopg2 avail. from fedora 5
 # we used to ship our own version of psycopg2 and pycurl, for fedora4
-# starting with 5.0, support for these two modules is taken out
+# starting with 4.3, support for these two modules is taken out
 # 
 # Build __init__.py metafiles and PHP API. 
 %{__make} %{?_smp_mflags}
+%{__make} -C wsdl
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -75,6 +90,21 @@ cat > $RPM_BUILD_ROOT/%{_sysconfdir}/php.d/xmlrpc.ini <<EOF
 extension=xmlrpc.so
 EOF
 
+# Install initscripts
+echo "* Installing initscripts"
+find plc.d | cpio -p -d -u ${RPM_BUILD_ROOT}/etc/
+chmod 755 ${RPM_BUILD_ROOT}/etc/plc.d/*
+
+# Install db-config.d files
+echo "* Installing db-config.d files"
+mkdir -p ${RPM_BUILD_ROOT}/etc/planetlab/db-config.d
+cp db-config.d/* ${RPM_BUILD_ROOT}/etc/planetlab/db-config.d
+chmod 444 ${RPM_BUILD_ROOT}/etc/planetlab/db-config.d/*
+
+# Install wsdl
+echo "* Installing wsdl"
+install -D -m 644 wsdl/plcapi.wsdl $RPM_BUILD_ROOT/var/www/html/wsdl/plcapi.wsdl
+
 %clean
 rm -rf $RPM_BUILD_ROOT
 
@@ -88,9 +118,152 @@ rm -rf $RPM_BUILD_ROOT
 %{php_extension_dir}/xmlrpc.so
 %{_sysconfdir}/php.d/xmlrpc.ini
 %config (noreplace) %{_datadir}/plc_api/PLC/Accessors/Accessors_site.py
+/etc/plc.d
+/etc/planetlab/db-config.d
+/var/www/html/wsdl/plcapi.wsdl
 
 %changelog
-* Wed Sep 10 2008 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-5.0-1
+* Fri Jan 29 2010 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-5.0-1
+- first working version of 5.0:
+- pld.c/, db-config.d/ and nodeconfig/ scripts should now sit in the module they belong to
+- nodefamily is 3-fold with pldistro-fcdistro-arch
+- site and person tags
+- new methods GetSliceFamily and GetNodeFlavour
+- deprecated the dummynet stuff that were for the external dummyboxes
+- tags definition : more consistency between db-config scripts and accessors
+- (get accessor to create the tag type too if absent)
+- logging an event for AddSliceToNodes
+
+* Sat Jan 09 2010 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-32
+- support for fedora 12
+- fix subtle bug in filtering with ] and quotes
+
+* Fri Dec 18 2009 Baris Metin <Talip-Baris.Metin@sophia.inria.fr> - PLCAPI-4.3-31
+- * patch for php-5.3 (the one in f12)
+- * validate email addresses with regex
+- * add PersonTags and SiteTags
+- * add additional accessors for node tags (kvariant, serial, ..)
+
+* Tue Nov 03 2009 Marc Fiuczynski <mef@cs.princeton.edu> - PLCAPI-4.3-30
+- Redacting password, session, and authstring values from the event log.
+
+* Mon Oct 19 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-29
+- let AddSite set ext_consortium_id - required for the poorman registration pages
+- drop version constraint on Requires: postgresql-python
+- don't log system calls nor ReportRunLevel
+
+* Thu Oct 15 2009 Daniel Hokka Zakrisson <daniel@hozac.com> - PLCAPI-4.3-28
+- Fix requires for CentOS.
+
+* Fri Oct 09 2009 Baris Metin <Talip-Baris.Metin@sophia.inria.fr> - PLCAPI-4.3-27
+- Require postgresql 8.2 (for array operators && and @>)
+
+* Thu Oct 08 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-26
+- Filter now supports the | and & features to match in sequence values
+- bugfix in the postgresql wrapper for sequence filter values
+- reviewed GetSlivers to export admin keys more efficiently
+- fix checking roles in UpdateSliceTag
+
+* Sat Sep 26 2009 Marc Fiuczynski <mef@cs.princeton.edu> - PLCAPI-4.3-25
+- - Some typos in the documentation were fixed.
+- - UpdateSliceTag check if a node's min_role_id is >= (rather than >)
+- to the tag's min_role_id.
+
+* Fri Sep 18 2009 anil vengalil <avengali@sophia.inria.fr> - PLCAPI-4.3-24
+
+* Mon Sep 07 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-23
+- Ongoing work to add upcalls, using new SFA class
+- new methods BindObjectToPeer, UnBindObjectFromPeer, still for SFA
+- reviewed type-checking for the 3 taggable classes node-interface-slice
+- cleanup ald dummynet stuff
+- expose the 'extensions' accessors to the API
+- tweaked checks in AddSliceTag
+- GetPersons exposes roles by default
+- bugfix in ReportRunLevel for non-string levels
+- tweaks in GetSlivers ( seems that it now exposes the keys for the root context )
+
+* Fri Jul 10 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-22
+- new BindObjectToPeer method for sfa
+- AddSliceTag and UpdateSliceTag open to the 'node' auth method with restrictions
+
+* Wed Jul 01 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-21
+- getbootmedium supports options as tags (serial, cramfs, kvariant, kargs, no-hangcheck )
+- reportrunlevel logs its calls only when run_level changes
+- pycurl more robust wrt to xmlrpclib.Transport
+
+* Tue Jun 16 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-20
+- produce a wsdl interface
+- bugfix in getbootmedium for nodes with interface tags
+
+* Sun Jun 07 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-19
+- bugfix for some rare pattern-based filters
+
+* Wed Jun 03 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-18
+- improvements in the 4.2 legacy layer
+
+* Sat May 30 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-17
+- bugfix required for slice tags set on nodegroups
+
+* Thu May 28 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-16
+- more complete compatibility layer - second iteration, with legacy code isolated in Legacy/
+
+* Tue May 26 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-15
+- more powerful legacy layer with 4.2
+
+* Fri May 15 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-14
+- RefreshPeer sets lock per-peer to avoid multiple concurent instances
+- migration script has an option for running interactively
+
+* Wed May 06 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-13
+- skips already added entries
+
+* Tue Apr 28 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-12
+- yet another set of fixes for external dummynet boxes
+
+* Wed Apr 22 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-11
+- GetDummyBoxMedium returns a base64-encoded boot image, doc is updated
+- and tmp file is cleaned up
+
+* Wed Apr 22 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-10
+- restore missing ResolveSlices
+
+* Mon Apr 20 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-9
+- new method GetDummyBoxMedium
+
+* Fri Apr 17 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-8
+- remove duplicate in Methods/__init__ that was breaking build of myplc-docs
+
+* Fri Apr 17 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-7
+- support for external dummynet boxes back in 4.3 - first draft
+
+* Thu Apr 09 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-6
+- fixes for smooth federation between 4.2 and 4.3
+- peername is not UNIQUE in schema anymore, was preventing delete/recreate
+
+* Tue Apr 07 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-5
+- support for BootCD variants (GetBootMedium ['variant:centos5'])
+- fix corner case with filters like {'~slice_id':[]}
+- fix transaction leak that caused the db connections pool to exhaust
+- properly expose all methods, including Legacy/, and not only Methods/
+
+* Tue Mar 24 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-4
+- renumbered as 4.3
+- nodes have new fields run_level (in addition to boot_state) and verified
+- tweaked migration from 4.2
+- tuned rpm dependencies
+- doc generation more explicit about errors like missing python modules
+- removed obsolete method GetSlicesMD5
+
+* Wed Jan 28 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-3
+- unified all tags
+- renamed interface settings into interface tags and slice attributes into slice tags
+- nodes have a node_type
+- various changes on the way to 4.3
+
+* Thu Nov 27 2008 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-2
+- Checkpointing : this version still has interface settings and slice attributes
+
+* Wed Sep 10 2008 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - PLCAPI-4.3-1
 - first iteration with taggable nodes/interfaces/slices
 - embryo for ilinks
 - cleaned up boot states
@@ -134,4 +307,4 @@ rm -rf $RPM_BUILD_ROOT
 * Fri Oct 27 2006 Mark Huang <mlhuang@CS.Princeton.EDU> - 
 - Initial build.
 
-%define module_current_branch 4.2
+%define module_current_branch 4.3
index 40a68ab..37dee6d 100755 (executable)
--- a/Server.py
+++ b/Server.py
@@ -6,6 +6,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os
diff --git a/db-config.d/000-functions b/db-config.d/000-functions
new file mode 100644 (file)
index 0000000..70fa078
--- /dev/null
@@ -0,0 +1,144 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### 
+import sys, os
+import resource
+
+g_url = ""
+def GetMyPLCURL(): return g_url
+def SetMyPLCURL(url):
+    global g_url
+    g_url = url
+
+# Get all currently registered roles
+g_role_names = [ role['name'] for role in GetRoles()]
+g_role_names.sort()
+
+def SetRole(level, role):
+    global g_role_names
+    if role not in g_role_names:
+        AddRole(level, role)
+        g_role_names.append(role)
+        g_role_names.sort()
+
+# Get list of existing tag types
+g_known_tag_types = [tag_type['tagname'] for tag_type in GetTagTypes()]
+g_known_tag_types.sort()
+
+def SetTagType(tag_type):
+    global g_known_tag_types
+    # Create/update default slice tag types
+    if tag_type['tagname'] not in g_known_tag_types:
+        AddTagType(tag_type)
+        g_known_tag_types.append(tag_type['tagname'])
+        g_known_tag_types.sort()
+    else:
+        UpdateTagType(tag_type['tagname'], tag_type)
+
+# Get list of existing (enabled, global) files
+g_conf_files = GetConfFiles()
+g_conf_files = filter(lambda conf_file: conf_file['enabled'] and \
+                    not conf_file['node_ids'] and \
+                    not conf_file['nodegroup_ids'],
+                    g_conf_files)
+g_dests = [conf_file['dest'] for conf_file in g_conf_files]
+g_conf_files = dict(zip(g_dests, g_conf_files))
+
+# Get list of existing initscripts
+g_oldinitscripts = GetInitScripts()
+g_oldinitscript_names = [script['name'] for script in g_oldinitscripts]
+g_oldinitscripts = dict(zip(g_oldinitscript_names, g_oldinitscripts))
+
+def SetInitScript(initscript):
+    global g_oldinitscripts, g_oldinitscript_names
+    if initscript['name'] not in g_oldinitscript_names:
+        initscript_id = AddInitScript(initscript)
+        g_oldinitscript_names.append(initscript['name'])
+        initscript['initscript_id']=initscript_id
+        g_oldinitscripts[initscript['name']]=initscript
+    else:
+        orig_initscript = g_oldinitscripts[initscript['name']]
+        initscript_id = orig_initscript['initscript_id']
+        UpdateInitScript(initscript_id, initscript)
+        
+def SetConfFile(conf_file):
+    global g_conf_files, g_dests
+    if conf_file['dest'] not in g_dests:
+        AddConfFile(conf_file)
+    else:
+        orig_conf_file = g_conf_files[conf_file['dest']]
+        conf_file_id = orig_conf_file['conf_file_id']
+        UpdateConfFile(conf_file_id, conf_file)
+
+def SetSlice(slice, tags):
+    # Create or Update slice
+    slice_name = slice['name']
+    slices = GetSlices([slice_name])
+    if len(slices)==1:
+        slice_id = slices[0]['slice_id']
+        if slice.has_key('name'):
+            del slice['name']
+        UpdateSlice(slice_id, slice)
+        slice['name']=slice_name
+    else:
+        expires = None
+        if slice.has_key('expires'):
+            expires = slice['expires']
+            del slice['expires']
+        slice_id = AddSlice(slice)
+        if expires <> None:
+            UpdateSlice(slice_id, {'expires':expires})
+
+    # Get slice structure with all fields
+    slice = GetSlices([slice_name])[0]
+
+    # Create/delete all tags
+    # NOTE: update is not needed, since unspecified tags are deleted, 
+    #       and new tags are added
+    slice_tags = []
+    if slice['slice_tag_ids']:
+        # Delete unknown attributes
+        for slice_tag in GetSliceTags(slice['slice_tag_ids']):
+            # ignore sliver tags, as those are custom/run-time values
+            if slice_tag['node_id'] <> None: continue
+            if (slice_tag['tagname'], slice_tag['value']) not in tags:
+                DeleteSliceTag(slice_tag['slice_tag_id'])
+            else:
+                slice_tags.append((slice_tag['tagname'],slice_tag['value']))
+
+    # only add slice tags that are new
+    for (name, value) in tags:
+        if (name,value) not in slice_tags:
+            AddSliceTag(slice_name, name, value)            
+        else:
+            # NOTE: this confirms that the user-specified tag is 
+            #       returned by GetSliceTags
+            pass
+
+def SetMessage(message):
+    messages = GetMessages([message['message_id']])
+    if len(messages)==0:
+        AddMessage(message)
+    else:
+        UpdateMessage(message['message_id'],message)
+
+# Get all model names
+g_pcu_models = [type['model'] for type in GetPCUTypes()]
+
+def SetPCUType(pcu_type):
+    global g_pcu_models
+    if 'pcu_protocol_types' in pcu_type:
+        protocol_types = pcu_type['pcu_protocol_types']
+        # Take this value out of the struct.
+        del pcu_type['pcu_protocol_types']
+    else:
+        protocol_types = []
+
+    if pcu_type['model'] not in g_pcu_models:
+        # Add the name/model info into DB
+        id = AddPCUType(pcu_type)
+        # for each protocol, also add this.
+        for ptype in protocol_types:
+            AddPCUProtocolType(id, ptype)
+
diff --git a/db-config.d/001-admin_user b/db-config.d/001-admin_user
new file mode 100644 (file)
index 0000000..8797164
--- /dev/null
@@ -0,0 +1,24 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### 
+# Create/update the default administrator account (should be person_id 2).
+
+admin = { 'person_id': 2,
+          'first_name': "Default",
+          'last_name': "Administrator",
+          'email': plc['root_user'],
+          'password': plc['root_password'] }
+persons = GetPersons([admin['person_id']])
+if not persons:
+    person_id = AddPerson(admin)
+    if person_id != admin['person_id']:
+        # Huh? Someone deleted the account manually from the database.
+        DeletePerson(person_id)
+        raise Exception, "Someone deleted the \"%s %s\" account from the database!" % \
+              (admin['first_name'], admin['last_name'])
+    UpdatePerson(person_id, { 'enabled': True })
+else:
+    person_id = persons[0]['person_id']
+    UpdatePerson(person_id, admin)
+
diff --git a/db-config.d/002-system_site b/db-config.d/002-system_site
new file mode 100644 (file)
index 0000000..ced75a7
--- /dev/null
@@ -0,0 +1,69 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### 
+# Create/update and populate the default site (should be site_id 1)
+
+### plc_www holds the contents of the PLC_WWW configuration category
+if plc_www['port'] == '80':
+    url = "http://" + plc_www['host'] + "/"
+elif plc_www['port'] == '443':
+    url = "https://" + plc_www['host'] + "/"
+else:
+    url = "http://" + plc_www['host'] + ":" + plc_www['port'] + "/"
+
+SetMyPLCURL(url)
+
+site = { 'site_id': 1,
+         'name': plc['name'] + " Central",
+         'abbreviated_name': plc['name'],
+         'login_base': plc['slice_prefix'],
+         'is_public': False,
+         'url': url,
+         'max_slices': 100 }
+
+sites = GetSites([site['site_id']])
+if not sites:
+    site_id = AddSite(site['name'], site['abbreviated_name'], site['login_base'], site)
+    if site_id != site['site_id']:
+        DeleteSite(site_id)
+        raise Exception, "Someone deleted the \"%s\" site from the database!" % \
+              site['name']
+    sites = [site]
+
+# Must call UpdateSite() even after AddSite() to update max_slices
+site_id = sites[0]['site_id']
+UpdateSite(site_id, site)
+
+# The default administrator account must be associated with a site
+# in order to login.
+AddPersonToSite(admin['person_id'], site['site_id'])
+SetPersonPrimarySite(admin['person_id'], site['site_id'])
+
+# Grant admin and PI roles to the default administrator account
+AddRoleToPerson(10, admin['person_id'])
+AddRoleToPerson(20, admin['person_id'])
+
+# Associate root ssh key with the default administrator
+keyfile=plc['root_ssh_key_pub']
+person = GetPersons(admin['person_id'])[0]
+keys = GetKeys(person['key_ids'])
+if os.path.exists(keyfile):
+    sshkeyfp = file(keyfile,"r")
+    sshkey = sshkeyfp.read()
+    sshkeyfp.close()
+
+    found=False
+    for key in keys:
+        if key['key_type']=='ssh':
+            if key['key'] == sshkey:
+                found=True
+            else:
+                # should we delete other keys?
+                pass
+    if not found:
+        key_id = AddPersonKey(admin['person_id'],{'key_type':'ssh','key':sshkey})
+else:
+    if len(keys)==0:
+        print "WARNING: default administrator does not have an ssh key"
+        print "and the default ssh root pub key (%s) file does not exist." % keyfile
diff --git a/db-config.d/010-slice_tags b/db-config.d/010-slice_tags
new file mode 100644 (file)
index 0000000..282bc0c
--- /dev/null
@@ -0,0 +1,167 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### slice tag types
+# xxx this should move to PLC/Accessors
+
+# Setup default slice tag types
+slicetag_types = \
+[
+    # Slice type (only vserver is supported)
+    {'tagname': "type",
+     'description': "Type of slice (e.g. vserver)",
+     'category' : 'slice/general',
+     'min_role_id': 20},
+    
+    # System slice
+    {'tagname': "system",
+     'description': "Is a default system slice (1) or not (0 or unset)",
+     'category' : 'slice/general',
+     'min_role_id': 10},
+    
+    # Slice enabled (1) or suspended (0)
+    {'tagname': "enabled",
+     'description': "Slice enabled (1 or unset) or suspended (0)",
+     'category' : 'slice/general',
+     'min_role_id': 10},
+    
+    # Slice reference image
+    {'tagname': "vref",
+     'description': "Reference image",
+     'category' : 'slice/config',
+     'min_role_id': 30},
+    
+    # Slice initialization script
+    {'tagname': "initscript",
+     'description': "Slice initialization script",
+     'category' : 'slice/config',
+     'min_role_id': 30},
+    
+    # IP Addresses for a Slice
+    {'tagname': "ip_addresses",
+     'description': "Add an ip address to a slice/sliver.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # CPU share
+    {'tagname': "cpu_pct",
+     'description': "Reserved CPU percent",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "cpu_share",
+     'description': "Number of CPU shares",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # Bandwidth limits
+    {'tagname': "net_min_rate",
+     'description': "Minimum bandwidth (kbps)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_max_rate",
+     'description': "Maximum bandwidth (kbps)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_i2_min_rate",
+     'description': "Minimum bandwidth over I2 routes (kbps)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_i2_max_rate",
+     'description': "Maximum bandwidth over I2 routes (kbps)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_max_kbyte",
+     'description': "Maximum daily network Tx KByte limit.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_thresh_kbyte",
+     'description': "KByte limit before warning and throttling.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_i2_max_kbyte",
+     'description': "Maximum daily network Tx KByte limit to I2 hosts.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_i2_thresh_kbyte",
+     'description': "KByte limit to I2 hosts before warning and throttling.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_share",
+     'description': "Number of bandwidth shares",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    {'tagname': "net_i2_share",
+     'description': "Number of bandwidth shares over I2 routes",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # Disk quota
+    {'tagname': "disk_max",
+     'description': "Disk quota (1k disk blocks)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # Proper operations
+    {'tagname': "proper_op",
+     'description': "Proper operation (e.g. bind_socket)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # VServer capabilities 
+    {'tagname': "capabilities",
+     'description': "VServer bcapabilities (separate by commas)",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # Vsys
+    {'tagname': "vsys",
+     'description': "Bind vsys script fd's to a slice's vsys directory.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # CoDemux
+    {'tagname': "codemux",
+     'description': "Demux HTTP between slices using localhost ports. Value in the form 'host, localhost port'.",
+     'category' : 'slice/rspec',
+     'min_role_id': 10},
+    
+    # Delegation
+    {'tagname': "delegations",
+     'description': "Coma seperated list of slices to give delegation authority to.",
+     'category' : 'slice/rspec',
+     'min_role_id': 30},
+
+    # Security capability to empower a slice to make an authenticated API call, set by silverauth NM plugin.
+    {'tagname': "hmac",
+     'description': "Sliver authorization key.",
+     'category' : 'slice/auth',
+     'min_role_id': 20},
+
+    # Capability to give a sliver access to unused raw disk
+    {'tagname': "rawdisk",
+     'description': "map unused raw disk devices into the slice",
+     'category' : 'slice/access', # we should get rid of this category thing
+     'min_role_id': 20},
+
+    { 'tagname' : 'exempt_slice_until',
+      'description' : 'Exclude this slice from MyOps until given date (YYYYMMDD)',
+      'category' : 'slice/myops', 
+      'min_role_id' : 10} ,
+]
+
+# add in the platform supported rlimits to the default_attribute_types
+for entry in resource.__dict__.keys() + ["VLIMIT_OPENFD"]:
+    if entry.find("LIMIT_")==1:
+        rlim = entry[len("RLIMIT_"):]
+        rlim = rlim.lower()
+        for ty in ("min","soft","hard"):
+            attribute = {
+                'tagname': "%s_%s"%(rlim,ty),
+                'description': "Per sliver RLIMIT %s_%s."%(rlim,ty),
+                'category': 'slice/limit',
+                'min_role_id': 10 #admin
+                }
+            slicetag_types.append(attribute)
+
+for slicetag_type in slicetag_types:
+    SetTagType(slicetag_type)
diff --git a/db-config.d/011-standard_tags b/db-config.d/011-standard_tags
new file mode 100644 (file)
index 0000000..64d0ddf
--- /dev/null
@@ -0,0 +1,35 @@
+# -*-python-*-
+# $Id$
+# $URL$
+
+# force creation of tags defined as accessors
+
+#################### slice tags
+GetSliceVref(0)
+GetSliceArch(0)
+GetSlicePldistro(0)
+GetSliceFcdistro(0)
+
+#################### node tags
+# 
+GetNodeArch(0)
+GetNodePldistro(0)
+GetNodeFcdistro(0)
+GetNodeDeployment(0)
+GetNodeExtensions(0)
+GetNodePlainBootstrapfs(0)
+GetNodeSerial(0)
+GetNodeCramfs(0)
+GetNodeKvariant(0)
+GetNodeKargs(0)
+GetNodeNoHangcheck(0)
+
+#################### interface settings
+
+# force creation of tags defined as accessors
+
+GetInterfaceIfname(0)
+GetInterfaceDriver(0)
+GetInterfaceAlias(0)
+GetInterfaceBackdoor(0)
+
diff --git a/db-config.d/012-wireless_tags b/db-config.d/012-wireless_tags
new file mode 100644 (file)
index 0000000..9bf1f96
--- /dev/null
@@ -0,0 +1,21 @@
+# -*-python-*-
+# $Id: 012-interface_tags -1   $
+# $URL: svn+ssh://thierry@svn.planet-lab.org/svn/MyPLC/trunk/db-config.d/012-interface_tags $
+#################### interface settings
+
+# force creation of tags defined as accessors
+GetInterfaceMode(0)
+GetInterfaceEssid(0)
+GetInterfaceNw(0)
+GetInterfaceFreq(0)
+GetInterfaceChannel(0)
+GetInterfaceSens(0)
+GetInterfaceRate(0)
+GetInterfaceKey(0)
+GetInterfaceKey1(0)
+GetInterfaceKey2(0)
+GetInterfaceKey3(0)
+GetInterfaceKey4(0)
+GetInterfaceSecurityMode(0)
+GetInterfaceIwconfig(0)
+GetInterfaceIwpriv(0)
diff --git a/db-config.d/020-boot_states b/db-config.d/020-boot_states
new file mode 100644 (file)
index 0000000..b7a8a5b
--- /dev/null
@@ -0,0 +1,23 @@
+# -*-python-*-
+# $Id: 012-slice_tags -1   $
+# $URL: svn+ssh://thierry@svn.planet-lab.org/svn/MyPLC/trunk/db-config.d/012-slice_tags $
+#################### slice tag types
+default_boot_states = [
+    'boot',
+    'failboot',
+    'safeboot',
+    'install',
+    'reinstall',
+    'disabled',
+]
+current_boot_states = GetBootStates()
+for state in default_boot_states:
+    if state not in current_boot_states:
+        AddBootState(state)
+
+# TODO: Delete old boot states. 
+if False:# NOTE: Only set to true if all federating peers have the new default boot states above.
+    for state in current_boot_states:
+        if state not in default_boot_states:
+            DeleteBootState(state)
+    
diff --git a/db-config.d/050-pcu_types b/db-config.d/050-pcu_types
new file mode 100644 (file)
index 0000000..74dce72
--- /dev/null
@@ -0,0 +1,64 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### PCUs
+### Setup Initial PCU information
+       
+pcu_types = [
+    {'model': 'HPiLO',
+     'name': 'HP iLO v1 or v2 (Integrated Lights-Out)', },
+    
+    {'model': 'IntelAMT',
+     'name': 'Intel AMT v2.5 or v3.0 (Active Management Technology)', },
+    
+    {'model': 'DRAC',
+     'name': 'DRAC - Dell Remote Access Control (all versions)', },
+    
+    {'model': 'OpenIPMI',
+     'name': 'OpenIPMI - Intelligent Platform Management Interface', },
+    
+    {'model': 'APCControl12p3',
+     'name': 'APC AP79xx or Masterswitch (sequence 1-2-port-3)', },
+    {'model': 'APCControl1p4',
+     'name': 'APC AP79xx or Masterswitch (sequence 1-port-4)', },
+    {'model': 'APCControl121p3',
+     'name': 'APC AP79xx or Masterswitch (sequence 1-2-1-port-3)', },
+    {'model': 'APCControl121p1',
+     'name': 'APC AP79xx or Masterswitch (sequence 1-2-1-port-1)', },
+    {'model': 'APCControl13p13',
+     'name': 'APC AP79xx or Masterswitch (sequence 1-3-port-1-3)', },
+    
+    {'model': 'BayTechRPC3NC', 
+     'name': 'BayTech with prompt RPC3-NC>', },
+    {'model': 'BayTechRPC16', 
+     'name': 'BayTech with prompt RPC-16>', },
+    {'model': 'BayTech',
+     'name': 'BayTech with prompt DS-RPC>', },
+    {'model': 'BayTechCtrlC', 
+     'name': 'BayTech Ctrl-C, 5, then with prompt DS-RPC>', },
+    {'model': 'BayTechCtrlCUnibe', 
+     'name': 'BayTech Ctrl-C, 3, then with prompt DS-RPC>', },
+    
+    {'model': 'BlackBoxPSMaverick',
+     'name': 'BlackBoxPSMaverick Web based controller'},
+    
+    {'model': 'IPAL', 
+     'name': 'IPAL - Dataprobe IP-41x & IP-81x', },
+    {'model': 'ePowerSwitchNew',
+     'name': 'ePowerSwitch Newer Models 1/4/8x', },
+    {'model': 'ePowerSwitchOld',
+     'name': 'ePowerSwitch Older Models 1/4/8x', },
+    
+    {'model': 'PM211MIP',
+     'name': 'Infratec PM221-MIP', },
+    
+    {'model': 'WTIIPS4',
+     'name': 'Western Telematic (WTI IPS-4)', },
+    
+    {'model': 'ManualPCU',
+     'name': 'Manual Administrator Operation (choose if model unknown)', },
+    ]
+
+for pcu_type in pcu_types:
+    SetPCUType(pcu_type)
+
diff --git a/db-config.d/060-messages b/db-config.d/060-messages
new file mode 100644 (file)
index 0000000..78eaa71
--- /dev/null
@@ -0,0 +1,293 @@
+# -*-python-*-
+# $Id$
+# $URL$
+#################### body for messages
+
+installfailed = """Once the node meets these requirements, please reinitiate the install
+by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+Update the BootState to 'Reinstall', then reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we may investigate the problem.
+"""
+
+# Load default message templates
+message_templates = [
+    {'message_id': 'Verify account',
+     'subject': "Verify account registration",
+     'template': """
+Please verify that you registered for a %(PLC_NAME)s account with the
+username %(email)s by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/persons/register.php?id=%(person_id)d&key=%(verification_key)s
+
+You must wait for this account to be approved before you can begin using it, please be patient.
+
+If you did not register for a %(PLC_NAME)s account, please ignore this
+message, or contact %(PLC_NAME)s Support <%(PLC_MAIL_SUPPORT_ADDRESS)s>.
+"""
+     },
+
+    {'message_id': 'New PI account',
+     'subject': "New PI account registration from %(first_name)s %(last_name)s <%(email)s> at %(site_name)s",
+     'template': """
+%(first_name)s %(last_name)s <%(email)s> has signed up for a new
+%(PLC_NAME)s account at %(site_name)s and has requested a PI role. PIs
+are responsible for enabling user accounts, creating slices, and
+ensuring that all users abide by the %(PLC_NAME)s Acceptable Use
+Policy.
+
+Only %(PLC_NAME)s administrators may enable new PI accounts. If you
+are a PI at %(site_name)s, please respond and indicate whether this
+registration is acceptable.
+
+To view the request, visit:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/persons/index.php?id=%(person_id)d
+"""
+     },
+
+    {'message_id': 'New account',
+     'subject': "New account registration from %(first_name)s %(last_name)s <%(email)s> at %(site_name)s",
+     'template': """
+%(first_name)s %(last_name)s <%(email)s> has signed up for a new
+%(PLC_NAME)s account at %(site_name)s and has requested the following
+roles: %(roles)s.
+
+To deny the request or enable the account, visit:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/persons/index.php?id=%(person_id)d
+"""
+     },
+
+    {'message_id': 'Password reset requested',
+     'subject': "Password reset requested",
+     'template': """
+Someone has requested that the password of your %(PLC_NAME)s account
+%(email)s be reset. If this person was you, you may continue with the
+reset by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/persons/reset_password.php?id=%(person_id)d&key=%(verification_key)s
+
+If you did not request that your password be reset, please contact
+%(PLC_NAME)s Support <%(PLC_MAIL_SUPPORT_ADDRESS)s>. Do not quote or
+otherwise include any of this text in any correspondence.
+"""
+     },
+
+    {'message_id': 'Password reset',
+     'subject': "Password reset",
+     'template': """
+The password of your %(PLC_NAME)s account %(email)s has been
+temporarily reset to:
+
+%(password)s
+
+Please change it at as soon as possible by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/persons/index.php?id=%(person_id)d
+
+If you did not request that your password be reset, please contact
+%(PLC_NAME)s Support <%(PLC_MAIL_SUPPORT_ADDRESS)s>. Do not quote or
+otherwise include any of this text in any correspondence.
+"""
+     },
+
+    # Boot Manager messages
+    {'message_id': "installfinished",
+     'subject': "%(hostname)s completed installation",
+     'template': """
+%(hostname)s just completed installation.
+
+The node should be usable in a couple of minutes if installation was
+successful.
+"""
+     },
+
+    {'message_id': "insufficientdisk",
+     'subject': "%(hostname)s does not have sufficient disk space",
+     'template': """
+%(hostname)s failed to boot because it does not have sufficent disk
+space, or because its disk controller was not recognized.
+
+Please replace the current disk or disk controller or install
+additional disks to meet the current hardware requirements.
+""" + installfailed
+     },
+
+    {'message_id': "insufficientmemory",
+     'subject': "%(hostname)s does not have sufficient memory",
+     'template': """
+%(hostname)s failed to boot because it does not have sufficent
+memory.
+
+Please install additional memory to meet the current hardware
+requirements.
+""" + installfailed
+     },
+
+    {'message_id': "authfail",
+     'subject': "%(hostname)s failed to authenticate",
+     'template':
+"""
+%(hostname)s failed to authenticate for the following reason:
+
+%(fault)s
+
+The most common reason for authentication failure is that the
+authentication key stored in the node configuration file, does not
+match the key on record. 
+
+There are two possible steps to resolve the problem.
+
+1. If you have used an All-in-one BootCD that includes the plnode.txt file,
+    then please check your machine for any old boot media, either in the
+    floppy drive, or on a USB stick.  It is likely that an old configuration
+    is being used instead of the new configuration stored on the BootCD.
+Or, 
+2. If you are using Generic BootCD image, then regenerate the node 
+    configuration file by visiting:
+
+    https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+    Under 'Download', follow the 'Download plnode.txt file for %(hostname)s'
+    option, and save the downloaded file as plnode.txt on either a floppy 
+    disk or a USB flash drive.  Be sure the 'Boot State' is set to 'Boot', 
+    and, then reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we can help investigate the problem.
+"""
+     },
+
+    {'message_id': "notinstalled",
+     'subject': "%(hostname)s is not installed",
+     'template':
+"""
+%(hostname)s failed to boot because it has either never been
+installed, or the installation is corrupt.
+
+Please check if the hard drive has failed, and replace it if so. After
+doing so, visit:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+Change the 'Boot State' to 'Reinstall', and then reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we may investigate the problem.
+"""
+     },
+
+    {'message_id': "missingkernel",
+     'subject': "%(hostname)s is missing its production kernel",
+     'template':
+"""
+%(hostname)s failed to boot because the filesystem is missing its production
+kernel.
+
+No action is needed from you at this time; this message is merely
+informational.
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+We will investigate the problem shortly.
+"""
+     },
+
+    {'message_id': "filesystemcorrupted",
+     'subject': "%(hostname)s may have corrupt filesystem",
+     'template':
+"""
+%(hostname)s failed to boot because the filesystem appears to be corrupted. 
+
+No action is needed from you at this time; this message is merely
+informational.
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+We will investigate the problem shortly.
+"""
+     },
+
+    {'message_id': "mountfailed",
+     'subject': "%(hostname)s could not mount filesystem",
+     'template':
+"""
+%(hostname)s failed to boot because the boot scripts could not mount the 
+filesystem.
+
+This could be for a number of reasons.  No action is needed from you at this
+time; this message is merely informational.  
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+We will investigate the problem shortly.
+"""
+     },
+
+    {'message_id': "hostnamenotresolve",
+     'subject': "%(hostname)s does not resolve",
+     'template':
+"""
+%(hostname)s failed to boot because its hostname does not resolve, or
+does resolve but does not match its configured IP address.
+
+Please check the network settings for the node, especially its
+hostname, IP address, and DNS servers, by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+Correct any errors, and change the 'Boot State' to 'Reinstall', and then
+reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we may investigate the problem.
+"""
+     },
+
+    # XXX N.B. I don't think these are necessary, since there's no
+    # way that the Boot Manager would even be able to contact the
+    # API to send these messages.
+
+    {'message_id': "noconfig",
+     'subject': "%(hostname)s does not have a configuration file",
+     'template': """
+%(hostname)s failed to boot because it could not find a PlanetLab
+configuration file. To create this file, visit:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+Click the Configuration File link, and save the downloaded file as
+plnode.txt on either a floppy disk or a USB flash drive.  Change the 
+'Boot State' to 'Reinstall', and then reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we may investigate the problem.
+"""
+     },
+
+    {'message_id': "nodetectednetwork",
+     'subject': "%(hostname)s has unsupported network hardware",
+     'template':
+"""
+
+%(hostname)s failed to boot because it has network hardware that is
+unsupported by the current production kernel. If it has booted
+successfully in the past, please try re-installing it by visiting:
+
+https://%(PLC_WWW_HOST)s:%(PLC_WWW_SSL_PORT)d/db/nodes/?id=%(node_id)d
+
+Change the 'Boot State' to 'Reinstall', and then reboot the node.
+
+If you have already performed this step and are still receiving this
+message, please reply so that we may investigate the problem.
+"""
+     },
+]
+
+for message in message_templates:
+    SetMessage(message)
index 9b6fcfa..a91975d 100755 (executable)
@@ -7,6 +7,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import xml.dom.minidom
index 1eef1a2..1290610 100755 (executable)
@@ -1,11 +1,25 @@
 #!/usr/bin/env python
 
 from PLC.API import PLCAPI
+from PLC.Faults import PLCInvalidAPIMethod
 from DocBook import DocBook
+import sys
 
-def api_methods():
-    api = PLCAPI(None)
-    methods = api.all_methods
-    return [api.callable(method) for method in methods]
+api = PLCAPI(None)
+methods = api.all_methods
+good_apis = []
+bad_apis = []
+for method in methods:
+    try:
+        good_api = api.callable(method)
+        good_apis.append(good_api)
+    except PLCInvalidAPIMethod, e:
+        bad_apis.append((method,e))
 
-DocBook(api_methods ()).Process()
+DocBook(good_apis).Process()
+
+if len(bad_apis):
+    sys.stderr.write("UNEXPECTED: There are %d non-callable methods:\n")
+    for bad_api,e in bad_apis:
+        sys.stderr.write("\t%s:%s\n" % (bad_api,e))
+    sys.exit(-1)
index 9cdec71..f3a8cb7 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 all: PLCAPI.html
@@ -23,7 +24,7 @@ PLCAPI.xml: PLCAPI.xml.in
        $(if $(local-docbook-43), \
        echo Using locally installed DTD $(local-docbook-43), \
        echo WARNING - could not locate local DTD - using remote $(remote-docbook-43))
-       sed -e s:@DOCBOOK-43@:$(docbook-43): $< > $@
+       sed -e "s:@DOCBOOK-43@:$(docbook-43):" $< > $@
 
 .PLCAPI.xml.valid: Methods.xml
 
index f52353e..183a059 100644 (file)
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- -*-xml-*- -->
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
 "@DOCBOOK-43@" [
 <!ENTITY Methods SYSTEM "Methods.xml">
     <section id="Filters">
       <title>Filters</title>
 
-      <para>Most of the <function>Get</function> functions take a
+      <para>Most of the <function>Get</function> methods take a
       filter argument. Filters may be arrays of integer (and sometimes
       string) identifiers, or a struct representing a filter on the
-      attributes of the entities being queried. For example,</para>
+      attributes of the entities being queried. For example,
 
 <programlisting>
-# plcsh code fragment (see below)
-GetNodes([1,2,3])
-GetNodes({'node_id': [1,2,3]})
+>>> GetNodes([1,2,3])
+>>> GetNodes({'node_id': [1,2,3]})
 </programlisting>
+</para>
 
       <para>Would be equivalent queries. Attributes that are
       themselves arrays (such as <literal>interface_ids</literal>
@@ -139,28 +140,206 @@ GetNodes({'node_id': [1,2,3]})
       filters.</para>
 
       <para> Filters support a few extra features illustrated in the following examples.</para>
+      
+      <section id="pattern-matching">
+       <title> Pattern Matching</title>
+       <para> <literal>*</literal> can be used in a text value and have the usual meaning, so all nodes in the <emphasis>fr</emphasis> can be obtained with:
+         <programlisting>GetNodes ( { 'hostname' : '*.fr' } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="negation">
+       <title> Negation </title>
+       <para> Fields starting with a <literal>~</literal> are negated, so non-local nodes can be fetched with:
+       <programlisting>GetNodes( { '~peer_id' : None } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="numeric">
+       <title> Numeric comparisons </title>
+       <para> Strictly greater/smaller operations are achieved by prepending the field name like in:
+       <programlisting>GetEvents( { '>time' : 1178531418 } ) </programlisting>
+       </para>
+       <para> Greater/smaller or equal: 
+       <programlisting>GetEvents( { ']event_id' : 2305 } ) </programlisting>
+       </para>
+      </section>
+
+      <section id="sort-clip">
+       <title> Sorting and Clipping </title> 
+       <para> The following 3 special fields can be used to extract only a subset of the results for pagination:
+         <programlisting> GetNodes( { '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 }</programlisting>
+       </para>
+      </section>
+    </section>
+
+    <section id="tags">
+      <title>Tags</title>
+
+      <para> The PLC API comes with a feature called
+      <emphasis>tags</emphasis>, that basically aims at supporting an
+      extensible data model. A few classes (as of this writing, Nodes,
+      Interfaces and Slices) are eligible for being dynamically
+      extended beyond the basic set of fields that are built into the
+      database schema.</para>
+
+      <para> Historically, this is a generalization of the concept of
+      <emphasis> SliceAttribute </emphasis>, and the more recent
+      concept of <emphasis> InterfaceSetting </emphasis>, that with
+      release 5.0 have been renamed into <emphasis> SliceTag
+      </emphasis> and <emphasis> InterfaceTag </emphasis>,
+      respectively. </para>
+
+      <section id="tags-low-level">
+       <title> Low level </title>
+       <para> The low level interface to tags relies on the following items:
       <itemizedlist>
        <listitem>
-         <para> <emphasis> Pattern Matching </emphasis> </para>
-         <programlisting>GetNodes ( { 'hostname' : '*.fr' } ) </programlisting>
+         <para> 
+           A <emphasis> TagType </emphasis> object basically models a
+           new column that needs to be added to other objects. In
+           much the same way as nodes are named through a <emphasis>
+           hostname </emphasis>, tagtypes are named with a
+           <emphasis>tagname</emphasis>, plus additional information
+           (category, description) that is mostly informative. The
+           convention is to use a category that depicts the type of
+           objects that the tag type, like e.g. <emphasis>
+           node/config </emphasis> 
+         </para>
        </listitem>
        <listitem>
-         <para> <emphasis> Negation </emphasis> </para>
-         <programlisting>GetNodes( { '~peer_id' : None } ) </programlisting>
+         <para> You would then be allowed to attach a value to, say,
+         a Node, by calling <emphasis> AddNodeTag </emphasis>, and
+         then as usual change this value with <emphasis>
+         UpdateNodeTag </emphasis>, or delete it with <emphasis>
+         DeleteNodeTag </emphasis>. </para>
        </listitem>
+      </itemizedlist>
+    </para>
+      </section>
+
+      <section id="accessors">
+       <title> Accessors </title>
+      <para> A rather more convenient way to use tags is through
+      Accessors. This convenience is located in <emphasis>
+      PLC/Accessors </emphasis>, and allows you to easily define Get
+      or Set methods dedicated to a given tag. This is for instance
+      how the <emphasis> GetNodeArch </emphasis> and <emphasis>
+      SetNodeArch </emphasis> methods are implemented. These methods
+      greatly simplify tags manipulation as they take care of
+      <itemizedlist>
        <listitem>
-         <para> <emphasis> Numeric comparisons </emphasis> </para>
-         <programlisting>GetEvents( { '>time' : 1178531418 } ) </programlisting>
-         <programlisting>GetEvents( { ']event_id' : 2305 } ) </programlisting>
+         <para> Lazily create <emphasis> TagTypes </emphasis> when
+         needed,</para>
        </listitem>
        <listitem>
-         <para> <emphasis> Sorting and Clipping </emphasis> </para>
-         <programlisting> GetNodes( { '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 }</programlisting>
+         <para> Create or update the, say, <emphasis> NodeTag
+         </emphasis> object, as needed.</para>
        </listitem>
       </itemizedlist>
+      </para>
+      <para> <emphasis> Site-specific </emphasis> accessors can be
+      defined in <emphasis>
+      /usr/share/plc_api/PLC/Accessors/Accessors_site.py </emphasis>
+      that will be preserved across updates of the PLCAPI rpm. 
+      </para>
+      <para> 
+       This mechanism does not currently support setting slice
+       tags that apply only on a given node or nodegroup. 
+      </para>
+      </section>
+
+      <section id="expose-in-api">
+       <title> Through regular Add/Get/Update methods </title>
+      <para> 
+       Finally, tags may also get manipulated through the
+       <emphasis>AddNode</emphasis>, <emphasis>GetNodes</emphasis>,
+       and <emphasis>UpdateNode</emphasis> methods:
+
+      <itemizedlist>
+       <listitem> <para> 
+         The <literal>define_accessors</literal> function in the
+         Accessors factory has an optional argument named <literal>
+         expose_in_api </literal>. When this is set, the
+         corresponding tag becomes visible from the Add/Get/Update
+         methods almost as if it was a native tag.
+       </para> </listitem>
+
+       <listitem><para>
+         So for instance the following code would be legal and do as expected:
+<programlisting>
+# create a x86_64 node
+>>> AddNode({'hostname':'pl1.foo.com','arch':'x86_64'})
+# get details for pl1.foo.com including tag 'arch' tag
+>>> GetNodes(['pl1.foo.com'],['boot_state','node_type','arch'])
+# set the 'deployment' tag
+>>> UpdateNode('pl1.foo.com',{'deployment':'beta'})
+# get all alpha and beta nodes
+>>> GetNodes({'deployment':'*a'},['hostname','deployment'])
+</programlisting>
+       </para></listitem>
+
+       <listitem><para> 
+         The current limitation about tags as opposed to native
+         fields is that, for performance, tags won't get returned
+         when using the implicit set of columns. So for instance:
+<programlisting>
+# get all details for 'pl1.foo.com' 
+>>> node=GetNodes(['pl1.foo.com'])[0]
+# this did not return the 'arch' tag
+>>> 'arch' in node
+False
+</programlisting>
+       </para></listitem>
+
+      </itemizedlist>
+    </para>
+      </section>
+    </section>
+
+    <section id="nodegroups">
+    <title>Nodegroups</title>
+
+    <para> In earlier versions up to v4.2, <emphasis> NodeGroups
+    </emphasis> used to be defined extensively. So you would,
+    basically, create an empty nodegroup instance, and then use
+    <emphasis> AddNodeToNodeGroup </emphasis> or <emphasis>
+    DeleteNodeFromNodeGroup </emphasis> to manage the nodegroup's
+    contents. </para>
+
+    <para> The new model has been redefined as follows. You now define
+    a nodegroup as the set of nodes for which a given <emphasis> Tag
+    </emphasis> has a given value, which are defined once and for good
+    when creating the <emphasis> NodeGroup </emphasis> object. </para>
+
+    <para> So for instance for managing the set of nodes that are
+    running various levels of software code, PLC has defined two
+    <emphasis> NodeGroups </emphasis> named <literal> alpha </literal>
+    and <literal> beta </literal>. With the new model, we would now do
+    something like the following, using the built-in <literal>
+    deployment </literal> tag that is created for that purpose:
+<programlisting>
+>>> AddNodeGroup('alphanodes','deployment','alpha')
+21
+>>> AddNodeGroup('betanodes','deployment','beta')
+21
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': []}
+{'groupname': u'betanodes', 'node_ids': []}
+>>> SetNodeDeployment('vnode01.inria.fr','alpha')
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': [1]}
+{'groupname': u'betanodes', 'node_ids': []}
+>>> SetNodeDeployment('vnode01.inria.fr','beta')
+>>> for ng in GetNodeGroups(['alphanodes','betanodes'],['groupname','node_ids']): print ng
+{'groupname': u'alphanodes', 'node_ids': []}
+{'groupname': u'betanodes', 'node_ids': [1]}
+</programlisting>
+</para>  
+
     </section>
 
-    <section>
+    <section id="plcsh">
       <title>PlanetLab shell</title>
 
       <para>A command-line program called <command>plcsh</command>
@@ -240,6 +419,50 @@ nodes = GetNodes([121], ['node_id', 'hostname'])
 nodes = plc.GetNodes([121], ['node_id', 'hostname'])
       </programlisting>
     </section>
+
+  <section id='standalone'>
+    <title>Using regular python</title>
+
+    <para>It is also possible to write simple regular-python scripts,
+    as illustrated in the example below. The only difference with the
+    examples above is that all API calls need to be passed a first
+    argument for authentication. This example would write in a file
+    the name of all the hosts attached to a given slice.</para>
+
+<programlisting>
+#!/usr/bin/env python
+
+import xmlrpclib
+
+plc_host='www.planet-lab.eu'
+login='thierry.parmentelat@sophia.inria.fr'
+password='xxxxxxxx'
+
+slice_name='inria_heartbeat'
+
+auth = { 'AuthMethod' : 'password',
+           'Username' : login,
+           'AuthString' : password,
+}
+
+api_url="https://%s:443/PLCAPI/"%plc_host
+
+plc_api = xmlrpclib.ServerProxy(api_url,allow_none=True)
+
+# the slice's node ids
+node_ids = plc_api.GetSlices(auth,slice_name,['node_ids'])[0]['node_ids']
+
+# get hostname for these nodes
+slice_nodes = plc_api.GetNodes(auth,node_ids,['hostname'])
+
+# store in a file
+f=open('mynodes.txt','w')
+for node in slice_nodes:
+    print >>f,node['hostname']
+f.close()
+</programlisting>
+  </section>
+
   </chapter>
 
   <chapter id="Methods">
diff --git a/migrations/011-down-site-and-person-tags.sql b/migrations/011-down-site-and-person-tags.sql
new file mode 100644 (file)
index 0000000..c70424d
--- /dev/null
@@ -0,0 +1,16 @@
+-- 
+-- purpose: provide a way to delete the additions added by the up script.
+-- NOTE: this does not leave the DB in a usable state, since it drops the view_sites and view_persons;
+-- 
+
+DROP VIEW view_site_tags;
+DROP VIEW view_sites;
+DROP VIEW site_tags;
+DROP TABLE site_tag;
+
+DROP VIEW view_person_tags;
+DROP VIEW view_persons;
+DROP VIEW person_tags;
+DROP TABLE person_tag;
+
+UPDATE plc_db_version SET subversion = 0;
diff --git a/migrations/011-up-site-and-person-tags.sql b/migrations/011-up-site-and-person-tags.sql
new file mode 100644 (file)
index 0000000..3dee8d0
--- /dev/null
@@ -0,0 +1,129 @@
+--
+-- PlanetLab
+--
+-- migration 001
+--
+-- purpose: provide tags on site and person objects in db
+--
+--
+
+-- SITES
+
+CREATE TABLE site_tag (
+    site_tag_id serial PRIMARY KEY,                    -- ID
+    site_id integer REFERENCES sites NOT NULL,         -- site id
+    tag_type_id integer REFERENCES tag_types,          -- tag type id
+    value text                                         -- value attached
+) WITH OIDS;
+
+CREATE OR REPLACE VIEW site_tags AS
+SELECT site_id,
+array_accum(site_tag_id) AS site_tag_ids
+FROM site_tag
+GROUP BY site_id;
+
+CREATE OR REPLACE VIEW view_site_tags AS
+SELECT
+site_tag.site_tag_id,
+site_tag.site_id,
+sites.login_base,
+tag_types.tag_type_id,
+tag_types.tagname,
+tag_types.description,
+tag_types.category,
+tag_types.min_role_id,
+site_tag.value
+FROM site_tag 
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN sites USING (site_id);
+
+DROP VIEW view_sites;
+CREATE OR REPLACE VIEW view_sites AS
+SELECT
+sites.site_id,
+sites.login_base,
+sites.name,
+sites.abbreviated_name,
+sites.deleted,
+sites.enabled,
+sites.is_public,
+sites.max_slices,
+sites.max_slivers,
+sites.latitude,
+sites.longitude,
+sites.url,
+sites.ext_consortium_id,
+CAST(date_part('epoch', sites.date_created) AS bigint) AS date_created,
+CAST(date_part('epoch', sites.last_updated) AS bigint) AS last_updated,
+peer_site.peer_id,
+peer_site.peer_site_id,
+COALESCE((SELECT person_ids FROM site_persons WHERE site_persons.site_id = sites.site_id), '{}') AS person_ids,
+COALESCE((SELECT node_ids FROM site_nodes WHERE site_nodes.site_id = sites.site_id), '{}') AS node_ids,
+COALESCE((SELECT address_ids FROM site_addresses WHERE site_addresses.site_id = sites.site_id), '{}') AS address_ids,
+COALESCE((SELECT slice_ids FROM site_slices WHERE site_slices.site_id = sites.site_id), '{}') AS slice_ids,
+COALESCE((SELECT pcu_ids FROM site_pcus WHERE site_pcus.site_id = sites.site_id), '{}') AS pcu_ids,
+COALESCE((SELECT site_tag_ids FROM site_tags WHERE site_tags.site_id = sites.site_id), '{}') AS site_tag_ids
+FROM sites
+LEFT JOIN peer_site USING (site_id);
+
+-- PERSONS
+
+CREATE TABLE person_tag (
+    person_tag_id serial PRIMARY KEY,                  -- ID
+    person_id integer REFERENCES persons NOT NULL,             -- person id
+    tag_type_id integer REFERENCES tag_types,          -- tag type id
+    value text                                         -- value attached
+) WITH OIDS;
+
+CREATE OR REPLACE VIEW person_tags AS
+SELECT person_id,
+array_accum(person_tag_id) AS person_tag_ids
+FROM person_tag
+GROUP BY person_id;
+
+CREATE OR REPLACE VIEW view_person_tags AS
+SELECT
+person_tag.person_tag_id,
+person_tag.person_id,
+persons.email,
+tag_types.tag_type_id,
+tag_types.tagname,
+tag_types.description,
+tag_types.category,
+tag_types.min_role_id,
+person_tag.value
+FROM person_tag 
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN persons USING (person_id);
+
+DROP VIEW view_persons;
+CREATE OR REPLACE VIEW view_persons AS
+SELECT
+persons.person_id,
+persons.email,
+persons.first_name,
+persons.last_name,
+persons.deleted,
+persons.enabled,
+persons.password,
+persons.verification_key,
+CAST(date_part('epoch', persons.verification_expires) AS bigint) AS verification_expires,
+persons.title,
+persons.phone,
+persons.url,
+persons.bio,
+CAST(date_part('epoch', persons.date_created) AS bigint) AS date_created,
+CAST(date_part('epoch', persons.last_updated) AS bigint) AS last_updated,
+peer_person.peer_id,
+peer_person.peer_person_id,
+COALESCE((SELECT role_ids FROM person_roles WHERE person_roles.person_id = persons.person_id), '{}') AS role_ids,
+COALESCE((SELECT roles FROM person_roles WHERE person_roles.person_id = persons.person_id), '{}') AS roles,
+COALESCE((SELECT site_ids FROM person_sites WHERE person_sites.person_id = persons.person_id), '{}') AS site_ids,
+COALESCE((SELECT key_ids FROM person_keys WHERE person_keys.person_id = persons.person_id), '{}') AS key_ids,
+COALESCE((SELECT slice_ids FROM person_slices WHERE person_slices.person_id = persons.person_id), '{}') AS slice_ids,
+COALESCE((SELECT person_tag_ids FROM person_tags WHERE person_tags.person_id = persons.person_id), '{}') AS person_tag_ids
+FROM persons
+LEFT JOIN peer_person USING (person_id);
+
+
+UPDATE plc_db_version SET subversion = 11;
similarity index 58%
rename from migrations/v4-to-v5/migrate.sed
rename to migrations/v42-to-v43/migrate.sed
index 76a4263..c432d9d 100644 (file)
@@ -1,3 +1,5 @@
-s,nodenetwork,interface,g
 s,slice_attribute_type,tag_type,g
 s,attribute_type_id,tag_type_id,g
+s,nodenetwork_setting,interface_tag,g
+s,slice_attribute,slice_tag,g
+s,nodenetwork,interface,g
similarity index 75%
rename from migrations/v4-to-v5/migrate.sh
rename to migrations/v42-to-v43/migrate.sh
index cfe76f2..ccb9914 100755 (executable)
@@ -16,7 +16,7 @@ SCHEMA_SQL=$UPUP/planetlab5.sql
 DATE=$(date +%Y-%m-%d-%H-%M)
 DATE_=$(date +%Y_%m_%d_%H_%M)
 LOG=${DIRNAME}/${DATE}.log
-DUMP=${DIRNAME}/${DATE}-pl4.sql
+DUMP=${DIRNAME}/pl4.sql
 RESTORE=${DIRNAME}/${DATE}-pl5.sql
 FAKE=${DIRNAME}/input-pl4.sql
 VIEWS_SQL=$DIRNAME/${DATE}-views5.sql
@@ -26,9 +26,27 @@ NODEGROUPS_SQL=$DIRNAME/${DATE}-nodegroups.sql
 PGM_VIEWS=$UP/extract-views.py
 PGM_NODEGROUPS=$DIRNAME/parse-site-nodegroups.py
 
+INTERACTIVE_MODE="true"
+
 # load config
 . /etc/planetlab/plc_config
 
+function run () {
+    message=$1; shift
+
+    if [ -n "$DEBUG" ] ; then 
+       echo -n "Type enter to run next step XXX $message XXX ... "
+       read _answer_
+    fi
+
+    echo -n "$message "
+    echo "==================================================" >> $LOG
+    echo $message >> $LOG
+    echo "$@" >> $LOG
+    "$@" >> $LOG 2>&1
+    echo Done
+}
+
 # return 0 (yes) or 1 (no) whether the database exists
 function check_for_database () {
     dbname=$1; shift
@@ -39,64 +57,75 @@ function check_for_database () {
 # when 'service plc start' gets run, the planetlab5 DB gets created 
 # so this script will drop the planetlab5 DB and re-create it from scratch 
 # with the contents of the planetlab4 DB that is epxected to exist
-function warning () {
+function confirm_nodegroups () {
     echo "========================================"
     echo "$COMMAND"
-    echo "This script is designed to ease the migration from myplc 4.2 to 5.0"
+    echo "This script is designed to ease the migration from myplc 4.2 to 4.3"
     echo "It attempts to (re)create the planetlab5 database from planetlab4"
     echo ""
-    echo "You might wish to edit/review $NODEGROUPS_DEF to finetune your migration"
+    echo "You might wish to edit/review"
+    echo "    $NODEGROUPS_DEF"
+    echo "    to finetune your migration"
     echo ""
     echo "Please refer to http://svn.planet-lab.org/wiki/Migration4to5"
     echo "========================================"
     echo -n "Are you sure you want to proceed y/[n] ? "
-    read answer
-    case $answer in
-       y|Y) echo See log in $LOG ;;
-       *) echo "Bye" ; exit 1 ;;
-    esac
+    if [ "$INTERACTIVE_MODE" = "true" ] ; then
+       read answer
+       case $answer in
+           y|Y) echo See log in $LOG ;;
+           *) echo "Bye" ; exit 1 ;;
+       esac
+    fi
 }
 
-function check () {
+function check_env () {
     [ -f $MIGRATION_SED ] || { echo $MIGRATION_SED not found - exiting ; exit 1; }
     [ -f $MIGRATION_SQL ] || { echo $MIGRATION_SQL not found - exiting ; exit 1; }
     [ -f $SCHEMA_SQL ] || { echo $SCHEMA_SQL not found - exiting ; exit 1; }
     [ -f $NODEGROUPS_DEF ] || { echo $NODEGROUPS_DEF not found - exiting ; exit 1; }
 }
 
-function run () {
-    message=$1; shift
+# connect to the former myplc, performs a local dump of planetlab4 and creates is locally
+function get_planetlab4 () {
 
-    if [ -n "$DEBUG" ] ; then 
-       echo -n "Type enter to run next step XXX $message XXX ... "
-       read _answer_
-    fi
+    # for faster tests ..
+    if [ -f $FAKE ] ; then
+       echo ''
+       echo 'xxxx     WARNING     WARNING     WARNING     WARNING     WARNING     xxx'
+       echo ''
+       echo Using fake input for tests $FAKE
+       echo ''
+       echo 'xxxx     WARNING     WARNING     WARNING     WARNING     WARNING     xxx'
+       echo ''
+       DUMP=$FAKE
+    elif [ -f $DUMP ] ; then
+       echo "Using planetlab4 from $DUMP"
+    else
 
-    echo -n "$message "
-    echo "==================================================" >> $LOG
-    echo $message >> $LOG
-    echo "$@" >> $LOG
-    "$@" >> $LOG 2>&1
-    echo Done
+       echo -n "Enter the hostname for the former DB service : "
+       if [ "$INTERACTIVE_MODE" = "true" ] ; then 
+               read hostname
+               echo "Running pg_dump on $hostname.."
+               pg_dump --ignore-version --host=$hostname --user=$PLC_DB_USER planetlab4 -f ${DUMP}
+       else
+               pg_dump --ignore-version --user=$PLC_DB_USER planetlab4 -f ${DUMP}
+       fi
+       DUMP=$DUMP
+    fi
 }
 
-function migrate () {
-    set -e
-    cd $DIRNAME
-
-    # check that planetlab4 exists
-    if check_for_database planetlab4 ; then
-       echo "OK : FOUND db planetlab4"
-    else
-       echo "ERROR : planetlab4 NOT FOUND - bye"
-       exit 1
-    fi
+function prepare_planetlab5 () {
 
     # check if planetlab5 exists
     if check_for_database planetlab5 ; then
        rename=planetlab5_${DATE_}
        echo -n "There is an existing DB named planetlab5, drop or rename into $rename d/[r] ? "
-       read _answer_
+       if [ "$INTERACTIVE_MODE" = "true" ] ; then
+               read _answer_
+       else
+               _answer_='r'
+       fi
        case $_answer_ in
            d|D)
                run "Dropping    planetlab5" psql --user=postgres template1 -c "DROP DATABASE planetlab5" || true
@@ -117,25 +146,17 @@ function migrate () {
     if check_for_database planetlab5 ; then
        echo "ERROR : FOUND planetlab5 - should not happen - exiting"
        exit 1
-    else
-       echo "OK, we're clear, let's go"
     fi
+}
+
+
+
+function migrate () {
+    set -e
+    cd $DIRNAME
 
     # dump planetlab4
     
-    if [ ! -f $FAKE ] ; then
-       run "Dumping     planetlab4 in $DUMP" pg_dump --user=$PLC_DB_USER -f $DUMP planetlab4 
-    else 
-       echo ''
-       echo 'xxxx     WARNING     WARNING     WARNING     WARNING     WARNING     xxx'
-       echo ''
-       echo Using fake input for tests $FAKE
-       echo ''
-       echo 'xxxx     WARNING     WARNING     WARNING     WARNING     WARNING     xxx'
-       echo ''
-       DUMP=$FAKE
-    fi
-
     run "Copying     into $RESTORE" cp $DUMP $RESTORE
     run "Renaming    identifiers in $RESTORE" sed -f $MIGRATION_SED -i $RESTORE
 
@@ -173,12 +194,29 @@ function links () {
 }
 
 function main () {
+
+    while getopts "b" opt ; do
+       case $opt in
+           b) INTERACTIVE_MODE='false' ;;
+           *) 
+               echo "migrate.sh [-b]"
+               echo " -b -- execute in batch mode without asking for user feedback"
+               exit
+           ;;
+       esac
+    done
     
-    check
-    warning
+    check_env
+    confirm_nodegroups
+    echo "OK, we're clear, let's go"
     set -e
+    get_planetlab4
+    prepare_planetlab5
     migrate
     links
+    echo "See logfile $LOG for detailed log"
+    echo "Checking for 'error' in the logfile"
+    grep -i error $LOG
 
 }
 
similarity index 71%
rename from migrations/v4-to-v5/migrate.sql
rename to migrations/v42-to-v43/migrate.sql
index 6f36acb..2b5fb2b 100644 (file)
@@ -1,8 +1,11 @@
 -- Thierry Parmentelat - INRIA
 -- 
 -- $Id$
+-- $URL$
 --
--- this is part of the script to migrate from 4.2 to 5.0
+-- this is part of the script to migrate from 4.2 to 4.3
+-- 
+-- most of the renamings have taken place already when this script is invoked
 --
 
 ----------------------------------------
@@ -51,7 +54,7 @@ DROP VIEW node_slices CASCADE;
 DROP VIEW node_slices_whitelist CASCADE;
 DROP VIEW nodegroup_conf_files CASCADE;
 DROP VIEW nodegroup_nodes CASCADE;
-DROP VIEW interface_settings CASCADE;
+DROP VIEW interface_tags CASCADE;
 DROP VIEW pcu_nodes CASCADE;
 DROP VIEW pcu_protocol_types CASCADE;
 DROP VIEW peer_keys CASCADE;
@@ -69,7 +72,7 @@ DROP VIEW site_nodes CASCADE;
 DROP VIEW site_pcus CASCADE;
 DROP VIEW site_persons CASCADE;
 DROP VIEW site_slices CASCADE;
-DROP VIEW slice_attributes CASCADE;
+DROP VIEW slice_tags CASCADE;
 DROP VIEW slice_nodes CASCADE;
 DROP VIEW slice_persons CASCADE;
 DROP VIEW slivers CASCADE;
@@ -80,7 +83,7 @@ DROP VIEW view_event_objects CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_events CASCADE;
 DROP VIEW view_keys CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_nodegroups CASCADE;
-DROP VIEW view_interface_settings CASCADE;
+DROP VIEW view_interface_tags CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_interfaces CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_nodes CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_pcu_types CASCADE;
@@ -89,7 +92,7 @@ DROP VIEW view_interface_settings CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_persons CASCADE;
 DROP VIEW view_sessions CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_sites CASCADE;
-DROP VIEW view_slice_attributes CASCADE;
+DROP VIEW view_slice_tags CASCADE;
 -- caught by some previous cascade -- DROP VIEW view_slices CASCADE;
 
 -- shows in logfile
@@ -99,6 +102,42 @@ select * from mgn_all_views;
 drop view mgn_all_views;
 drop function mgn_drop_all_views ();
 
+----------------------------------------
+-- peers
+----------------------------------------
+ALTER TABLE peers ADD COLUMN shortname TEXT;
+ALTER TABLE peers ADD COLUMN hrn_root TEXT;
+
+
+----------------------------------------
+-- run levels
+----------------------------------------
+CREATE TABLE run_levels  (
+    run_level text PRIMARY KEY
+) WITH OIDS;
+INSERT INTO run_levels  (run_level) VALUES ('boot');
+INSERT INTO run_levels  (run_level) VALUES ('safeboot');
+INSERT INTO run_levels  (run_level) VALUES ('failboot');
+INSERT INTO run_levels  (run_level) VALUES ('reinstall');
+
+
+----------------------------------------
+-- node types
+----------------------------------------
+CREATE TABLE node_types (
+    node_type text PRIMARY KEY
+) WITH OIDS;
+INSERT INTO node_types (node_type) VALUES ('regular');
+INSERT INTO node_types (node_type) VALUES ('dummynet');
+
+----------------------------------------
+-- nodes
+----------------------------------------
+ALTER TABLE nodes ADD COLUMN node_type TEXT REFERENCES node_types  DEFAULT 'regular'; -- node types
+ALTER TABLE nodes ALTER COLUMN boot_state SET DEFAULT 'reinstall'; -- boot_states changed in planetlab5.sql
+ALTER TABLE nodes ADD COLUMN verified boolean NOT NULL DEFAULT false;  -- whether or not the node & pcu are verified
+ALTER TABLE nodes ADD COLUMN run_level TEXT REFERENCES run_levels DEFAULT NULL; -- Node Run Level
+
 ----------------------------------------
 -- tag types
 ----------------------------------------
@@ -111,41 +150,41 @@ ALTER TABLE tag_types RENAME COLUMN name TO tagname;
 --- former slice_attribute_types had no 'category'
 ALTER TABLE tag_types ADD COLUMN category TEXT NOT NULL DEFAULT 'slice/legacy';
 
---- append in tag_types the contents of nodenetwork_setting_types
+--- append in tag_types the contents of former nodenetwork_setting_types
 INSERT INTO tag_types (tagname,description,min_role_id,category) 
-       SELECT name,description,min_role_id,'interface/legacy' FROM interface_setting_types;
+       SELECT name,description,min_role_id,'interface/legacy' FROM interface_tag_types;
 
 ---------- interface settings
 
---- former nodenetwork_setting_type_id are now renumbered, need to fix interface_setting accordingly
+--- former nodenetwork_setting_type_id are now renumbered, need to fix interface_tag accordingly
 
 -- old_index -> new_index relation
 CREATE OR REPLACE VIEW mgn_setting_renumber AS
    SELECT 
-      interface_setting_types.interface_setting_type_id AS old_index,  
+      interface_tag_types.interface_tag_type_id AS old_index,  
       tag_types.tag_type_id AS new_index 
    FROM 
-      interface_setting_types INNER JOIN tag_types  
-      ON interface_setting_types.name = tag_types.tagname;
+      interface_tag_types INNER JOIN tag_types  
+      ON interface_tag_types.name = tag_types.tagname;
 
--- need to temporarily drop constraint on interface_setting_type_id
-ALTER TABLE interface_setting DROP CONSTRAINT interface_setting_interface_setting_type_id_fkey;
+-- need to temporarily drop constraint on interface_tag_type_id
+ALTER TABLE interface_tag DROP CONSTRAINT interface_tag_interface_tag_type_id_fkey;
 
 -- do the transcoding
-UPDATE interface_settin
-   SET interface_setting_type_id = 
-      (select new_index from mgn_setting_renumber where old_index=interface_setting_type_id);
+UPDATE interface_ta
+   SET interface_tag_type_id = 
+      (select new_index from mgn_setting_renumber where old_index=interface_tag_type_id);
 
 -- alter column name to reflect change
-ALTER TABLE interface_setting RENAME interface_setting_type_id TO tag_type_id;
+ALTER TABLE interface_tag RENAME interface_tag_type_id TO tag_type_id;
 
--- add contraint again
-ALTER TABLE interface_setting ADD CONSTRAINT interface_setting_tag_type_id_fkey 
+-- add constraint again
+ALTER TABLE interface_tag ADD CONSTRAINT interface_tag_tag_type_id_fkey 
     FOREIGN KEY (tag_type_id) references tag_types(tag_type_id) ;
 
--- drop former interface_setting_types altogether
+-- drop former interface_tag_types altogether
 drop view mgn_setting_renumber;
-drop table interface_setting_types;
+drop table interface_tag_types;
 
 ---------- node tags
 
@@ -153,7 +192,7 @@ CREATE TABLE node_tag (
     node_tag_id serial PRIMARY KEY,                    -- ID
     node_id integer REFERENCES nodes NOT NULL,         -- node id
     tag_type_id integer REFERENCES tag_types,          -- tag type id
-    tagvalue text                                      -- value attached
+    value text                                         -- value attached
 ) WITH OIDS;
 
 
@@ -175,7 +214,7 @@ CREATE TABLE ilink (
 ---------- nodegroups table - start
 -- nodegroup_id is preserved for conf_files and other references
 -- former nodegroups table was (nodegroup_id,name,description)
--- new table is        now            (nodegroup_id, groupname, tag_type_id, tagvalue)
+-- new table is        now            (nodegroup_id, groupname, tag_type_id, value)
 
 -- rename column
 ALTER TABLE nodegroups RENAME name TO groupname;
@@ -194,23 +233,23 @@ INSERT INTO tag_types (tagname)
 ALTER TABLE nodegroups DROP COLUMN description;
 
 ---------- set the right tags so as to recover former nodegroups
-INSERT INTO node_tag (node_id, tag_type_id, tagvalue)
-   SELECT node_id, tag_type_id, tagvalue FROM
+INSERT INTO node_tag (node_id, tag_type_id, value)
+   SELECT node_id, tag_type_id, value FROM
       nodegroup_node LEFT JOIN nodegroups USING (nodegroup_id) 
          INNER JOIN mgn_site_nodegroup USING (groupname)
            LEFT JOIN tag_types using (tagname); 
 
 ---------- nodegroups table - conclusion
 ALTER TABLE nodegroups ADD COLUMN tag_type_id INTEGER;
-ALTER TABLE nodegroups ADD COLUMN tagvalue TEXT;
+ALTER TABLE nodegroups ADD COLUMN value TEXT;
 
 CREATE OR REPLACE VIEW mgn_nodegroups AS
-   SELECT groupname, tag_types.tag_type_id, mgn_site_nodegroup.tagvalue 
+   SELECT groupname, tag_types.tag_type_id, mgn_site_nodegroup.value 
       FROM nodegroups INNER JOIN mgn_site_nodegroup USING (groupname) 
          INNER JOIN tag_types USING (tagname);
 
 UPDATE nodegroups SET tag_type_id = (SELECT tag_type_id FROM mgn_nodegroups WHERE nodegroups.groupname=mgn_nodegroups.groupname);
-UPDATE nodegroups SET tagvalue = (SELECT tagvalue FROM mgn_nodegroups WHERE nodegroups.groupname=mgn_nodegroups.groupname);
+UPDATE nodegroups SET value = (SELECT value FROM mgn_nodegroups WHERE nodegroups.groupname=mgn_nodegroups.groupname);
 
 -- install corresponding constraints
 ALTER TABLE nodegroups ADD CONSTRAINT nodegroups_tag_type_id_fkey
@@ -224,36 +263,31 @@ drop view mgn_nodegroups;
 drop table mgn_site_nodegroup;
 drop table nodegroup_node;
 
+
 ----------------------------------------
 -- boot states
 ----------------------------------------
-INSERT INTO boot_states (boot_state) 
-       VALUES ('safeboot'), ('failboot'), ('disabled'), ('install'), ('reinstall');
-
--- xxx need checking 
---
--- boot          boot
--- dbg           safeboot ?
--- diag          failboot ?
--- disable       disabled
--- inst          install
--- rins          reinstall
--- new           reinstall ?
--- rcnf              failboot ?
-
-
-UPDATE nodes SET boot_state='safeboot' WHERE boot_state='dbg';
-UPDATE nodes SET boot_state='failboot' WHERE boot_state='diag';
+-- create new ones
+INSERT INTO boot_states (boot_state) VALUES ('safeboot');
+INSERT INTO boot_states (boot_state) VALUES ('failboot');
+INSERT INTO boot_states (boot_state) VALUES ('disabled');
+INSERT INTO boot_states (boot_state) VALUES ('reinstall');
+
+-- map old ones
+-- b/c boot_states are declared by users not reported by node.
+UPDATE nodes SET boot_state='safeboot' WHERE boot_state='dbg';  
+UPDATE nodes SET boot_state='safeboot' WHERE boot_state='diag';
 UPDATE nodes SET boot_state='disabled' WHERE boot_state='disable';
-UPDATE nodes SET boot_state='install' WHERE boot_state='inst';
+UPDATE nodes SET boot_state='reinstall' WHERE boot_state='inst';
 UPDATE nodes SET boot_state='reinstall' WHERE boot_state='rins';
 UPDATE nodes SET boot_state='reinstall' WHERE boot_state='new';
-UPDATE nodes SET boot_state='failboot' WHERE boot_state='rcnf';
+UPDATE nodes SET boot_state='reinstall' WHERE boot_state='rcnf';
 
--- one-by-one is safer
+-- delete old ones
 DELETE FROM boot_states WHERE boot_state='dbg';
 DELETE FROM boot_states WHERE boot_state='diag';
 DELETE FROM boot_states WHERE boot_state='disable';
+DELETE FROM boot_states WHERE boot_state='install';
 DELETE FROM boot_states WHERE boot_state='inst';
 DELETE FROM boot_states WHERE boot_state='rins';
 DELETE FROM boot_states WHERE boot_state='new';
similarity index 78%
rename from migrations/v4-to-v5/parse-site-nodegroups.py
rename to migrations/v42-to-v43/parse-site-nodegroups.py
index f19b3d8..1405ca0 100755 (executable)
@@ -12,7 +12,7 @@ class Nodegroups:
     # strip off comments
     comment=re.compile("\s*#.*")
     id="[\w\.-]+|\'[^\']+\'"
-    id3="\s*(?P<groupname>%s)\s+(?P<tagname>%s)\s+(?P<tagvalue>%s\s*)"%(id,id,id)
+    id3="\s*(?P<groupname>%s)\s+(?P<tagname>%s)\s+(?P<value>%s\s*)"%(id,id,id)
     line=re.compile(id3)
 
     def parse (self):
@@ -22,7 +22,7 @@ class Nodegroups:
             outfile = sys.stdout
         lineno=0
         print >> outfile, """
-CREATE TABLE mgn_site_nodegroup (groupname text, tagname text, tagvalue text);
+CREATE TABLE mgn_site_nodegroup (groupname text, tagname text, value text);
 """
         for line in file(self.input).readlines():
             lineno += 1
@@ -37,11 +37,11 @@ CREATE TABLE mgn_site_nodegroup (groupname text, tagname text, tagvalue text);
                 if id.find("'")==0:
                     return id
                 return "'%s'"%id
-            [groupname,tagname,tagvalue]=[normalize(x) for x in match.groups()]
+            [groupname,tagname,value]=[normalize(x) for x in match.groups()]
 
             print >> outfile, \
-"INSERT INTO mgn_site_nodegroup (groupname,tagname,tagvalue) VALUES (%s,%s,%s);"%\
-(groupname,tagname,tagvalue)
+"INSERT INTO mgn_site_nodegroup (groupname,tagname,value) VALUES (%s,%s,%s);"%\
+(groupname,tagname,value)
         if outfile != sys.stdout:
             outfile.close()
 
similarity index 97%
rename from migrations/v4-to-v5/site-nodegroups.def
rename to migrations/v42-to-v43/site-nodegroups.def
index fffdbd7..2d094a2 100644 (file)
@@ -1,6 +1,6 @@
 # this file allows you to plan the migration of your nodegroups
 # 
-# groupname tagname tagvalue
+# groupname tagname value
 # 
 # single quotes can be used
 # although tagnames can have spaces, it is not recommended 
index 560ff68..fe8842a 100644 (file)
@@ -5,6 +5,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 all: plc_api.php
index 7fbe4da..54f9b1e 100644 (file)
@@ -9,6 +9,7 @@
 // Copyright (C) 2005-2006 The Trustees of Princeton University
 //
 // $Id$
+// $URL$
 //
 //
 
index 3e20a31..621cc91 100755 (executable)
@@ -6,6 +6,7 @@
 # Copyright (C) 2005 The Trustess of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import os, sys
index d668067..435092c 100644 (file)
 
 #define PHP_EXT_VERSION "0.51"
 
+/* ====================
+ * Thierry - Dec. 17 2009 
+ * patch for php - bug first triggered on f12 with php-5.3
+ * http://pecl.php.net/bugs/bug.php?id=14369
+ * http://remi.fedorapeople.org/ncurses-1.0.0-php53.patch
+ * I'm taking out the 'static' stuff as after pre-processing it appears to be part of the macro expansion already
+ */
+#if ZEND_MODULE_API_NO >= 20071006
+// No more defined with PHP 5.3.0
+       ZEND_BEGIN_ARG_INFO(first_arg_force_ref, 0)
+               ZEND_ARG_PASS_INFO(1)
+       ZEND_END_ARG_INFO();
+
+       ZEND_BEGIN_ARG_INFO(second_arg_force_ref, 0)
+               ZEND_ARG_PASS_INFO(0)
+               ZEND_ARG_PASS_INFO(1)
+       ZEND_END_ARG_INFO();
+#endif
+/* ==================== end patch */
+
+/* ========== additional notes
+ * in the process, I've also come across the following resources that might help 
+ * if/when zend_get_parameters_ex gets deprecated (only generates warnings for now)
+ *
+ * http://developers.evrsoft.com/docs/php/zend.arguments.deprecated-retrieval.shtml
+ * explains how the old (our) stuff works
+ *
+ * http://www.hospedajeydominios.com/mambo/documentacion-manual_php-pagina-zend_arguments_retrieval.html
+ * gives info on the new scheme
+ *
+ * I'm taking tha risk as the changes seem to mean major surgery ...
+ */
+
 /* You should tweak config.m4 so this symbol (or some else suitable)
        gets defined.  */
 
index 0f3e9fd..fc39535 100644 (file)
 --
 -- Copyright (C) 2006 The Trustees of Princeton University
 --
+-- NOTE: this file was first created for version 4.3, the filename might be confusing
+--
 -- $Id$
+-- $URL$
 --
 
 SET client_encoding = 'UNICODE';
@@ -139,8 +142,11 @@ CREATE TABLE address_types (
     description text                                   -- Address type description
 ) WITH OIDS;
 
+-- Multi-rows insertion "insert .. values (row1), (row2)" is not supported by pgsql-8.1
 -- 'Billing' Used to be 'Site'
-INSERT INTO address_types (name) VALUES ('Personal'), ('Shipping'), ('Billing');
+INSERT INTO address_types (name) VALUES ('Personal');
+INSERT INTO address_types (name) VALUES ('Shipping');
+INSERT INTO address_types (name) VALUES ('Billing');
 
 -- Mailing addresses
 CREATE TABLE addresses (
@@ -225,7 +231,10 @@ CREATE TABLE roles (
     role_id integer PRIMARY KEY,                       -- Role identifier
     name text UNIQUE NOT NULL                          -- Role symbolic name
 ) WITH OIDS;
-INSERT INTO roles (role_id, name) VALUES (10, 'admin'), (20, 'pi'), (30, 'user'), (40, 'tech');
+INSERT INTO roles (role_id, name) VALUES (10, 'admin');
+INSERT INTO roles (role_id, name) VALUES (20, 'pi');
+INSERT INTO roles (role_id, name) VALUES (30, 'user');
+INSERT INTO roles (role_id, name) VALUES (40, 'tech');
 
 CREATE TABLE person_role (
     person_id integer REFERENCES persons NOT NULL,     -- Account identifier
@@ -247,22 +256,43 @@ GROUP BY person_id;
 -- Nodes
 --------------------------------------------------------------------------------
 
--- Valid node boot states
+-- Valid node boot states (Nodes.py expect max length to be 20)
 CREATE TABLE boot_states (
     boot_state text PRIMARY KEY
 ) WITH OIDS;
-INSERT INTO boot_states (boot_state) 
-       VALUES ('boot'), ('dbg'), ('diag'), ('disable'), ('inst'), ('rins'), ('new');
+INSERT INTO boot_states (boot_state) VALUES ('boot');
+INSERT INTO boot_states (boot_state) VALUES ('safeboot');
+INSERT INTO boot_states (boot_state) VALUES ('reinstall');
+INSERT INTO boot_states (boot_state) VALUES ('disabled');
+
+CREATE TABLE run_levels  (
+    run_level text PRIMARY KEY
+) WITH OIDS;
+INSERT INTO run_levels  (run_level) VALUES ('boot');
+INSERT INTO run_levels  (run_level) VALUES ('safeboot');
+INSERT INTO run_levels  (run_level) VALUES ('failboot');
+INSERT INTO run_levels  (run_level) VALUES ('reinstall');
+
+-- Known node types (Nodes.py expect max length to be 20)
+CREATE TABLE node_types (
+    node_type text PRIMARY KEY
+) WITH OIDS;
+INSERT INTO node_types (node_type) VALUES ('regular');
+-- old dummynet stuff, to be removed
+INSERT INTO node_types (node_type) VALUES ('dummynet');
 
 -- Nodes
 CREATE TABLE nodes (
     -- Mandatory
     node_id serial PRIMARY KEY,                                -- Node identifier
+    node_type text REFERENCES node_types               -- node type
+              DEFAULT 'regular',
+
     hostname text NOT NULL,                            -- Node hostname
     site_id integer REFERENCES sites NOT NULL,         -- At which site 
-
     boot_state text REFERENCES boot_states NOT NULL    -- Node boot state
-              DEFAULT 'inst', 
+              DEFAULT 'reinstall', 
+    run_level  text REFERENCES run_levels DEFAULT NULL, -- Node Run Level
     deleted boolean NOT NULL DEFAULT false,            -- Is deleted
 
     -- Optional
@@ -271,6 +301,7 @@ CREATE TABLE nodes (
     version text,                                      -- Boot CD version string updated by Boot Manager
     ssh_rsa_key text,                                  -- SSH host key updated by Boot Manager
     key text,                                          -- Node key generated when boot file is downloaded
+       verified boolean NOT NULL DEFAULT false,                -- whether or not the node & pcu are verified
 
     -- Timestamps
     date_created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -304,30 +335,9 @@ CREATE TABLE node_tag (
     node_tag_id serial PRIMARY KEY,                    -- ID
     node_id integer REFERENCES nodes NOT NULL,         -- node id
     tag_type_id integer REFERENCES tag_types,          -- tag type id
-    tagvalue text                                      -- value attached
+    value text                                         -- value attached
 ) WITH OIDS;
 
-CREATE OR REPLACE VIEW node_tags AS
-SELECT node_id,
-array_accum(node_tag_id) AS tag_ids
-FROM node_tag
-GROUP BY node_id;
-
-CREATE OR REPLACE VIEW view_node_tags AS
-SELECT
-node_tag.node_tag_id,
-node_tag.node_id,
-nodes.hostname,
-tag_types.tag_type_id,
-tag_types.tagname,
-tag_types.description,
-tag_types.category,
-tag_types.min_role_id,
-node_tag.tagvalue
-FROM node_tag 
-INNER JOIN tag_types USING (tag_type_id)
-INNER JOIN nodes USING (node_id);
-
 --------------------------------------------------------------------------------
 -- (network) interfaces
 --------------------------------------------------------------------------------
@@ -342,10 +352,15 @@ INSERT INTO network_types (type) VALUES ('ipv4');
 CREATE TABLE network_methods (
     method text PRIMARY KEY -- Configuration method
 ) WITH OIDS;
-INSERT INTO network_methods (method) VALUES
-        ('static'), ('dhcp'), ('proxy'), ('tap'), ('ipmi'), ('unknown');
 
--- Node network interfaces
+INSERT INTO network_methods (method) VALUES ('static');
+INSERT INTO network_methods (method) VALUES ('dhcp');
+INSERT INTO network_methods (method) VALUES ('proxy');
+INSERT INTO network_methods (method) VALUES ('tap');
+INSERT INTO network_methods (method) VALUES ('ipmi');
+INSERT INTO network_methods (method) VALUES ('unknown');
+
+-- Network interfaces
 CREATE TABLE interfaces (
     -- Mandatory
     interface_id serial PRIMARY KEY,                   -- Network interface identifier
@@ -382,34 +397,36 @@ FROM interfaces_ordered
 GROUP BY node_id;
 
 --------------------------------------------------------------------------------
--- Interface settings
+-- Interface tags (formerly known as interface settings)
 --------------------------------------------------------------------------------
 
-CREATE TABLE interface_setting (
-    interface_setting_id serial PRIMARY KEY,           -- Interface Setting Identifier
+CREATE TABLE interface_tag (
+    interface_tag_id serial PRIMARY KEY,               -- Interface Setting Identifier
     interface_id integer REFERENCES interfaces NOT NULL,-- the interface this applies to
     tag_type_id integer REFERENCES tag_types NOT NULL, -- the setting type
     value text                                         -- value attached
 ) WITH OIDS;
 
-CREATE OR REPLACE VIEW interface_settings AS 
+CREATE OR REPLACE VIEW interface_tags AS 
 SELECT interface_id,
-array_accum(interface_setting_id) AS interface_setting_ids
-FROM interface_setting
+array_accum(interface_tag_id) AS interface_tag_ids
+FROM interface_tag
 GROUP BY interface_id;
 
-CREATE OR REPLACE VIEW view_interface_settings AS
+CREATE OR REPLACE VIEW view_interface_tags AS
 SELECT
-interface_setting.interface_setting_id,
-interface_setting.interface_id,
+interface_tag.interface_tag_id,
+interface_tag.interface_id,
+interfaces.ip,
 tag_types.tag_type_id,
 tag_types.tagname,
 tag_types.description,
 tag_types.category,
 tag_types.min_role_id,
-interface_setting.value
-FROM interface_setting
-INNER JOIN tag_types USING (tag_type_id);
+interface_tag.value
+FROM interface_tag
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN interfaces USING (interface_id);
 
 CREATE OR REPLACE VIEW view_interfaces AS
 SELECT
@@ -428,7 +445,7 @@ interfaces.dns1,
 interfaces.dns2,
 interfaces.bwlimit,
 interfaces.hostname,
-COALESCE((SELECT interface_setting_ids FROM interface_settings WHERE interface_settings.interface_id = interfaces.interface_id), '{}') AS interface_setting_ids
+COALESCE((SELECT interface_tag_ids FROM interface_tags WHERE interface_tags.interface_id = interfaces.interface_id), '{}') AS interface_tag_ids
 FROM interfaces;
 
 --------------------------------------------------------------------------------
@@ -446,23 +463,10 @@ CREATE OR REPLACE VIEW view_ilinks AS
 SELECT * FROM tag_types 
 INNER JOIN ilink USING (tag_type_id);
 
--- expose node_ids ???
--- -- cannot mention the same table twice in a join ?
--- -- CREATE OR REPLACE VIEW ilink_src_node AS 
--- SELECT 
--- ilink.tag_type_id,
--- ilink.src_interface_id,
--- interfaces.node_id AS src_node_id,
--- ilink.dst_interface_id
--- FROM ilink 
--- INNER JOIN interfaces ON ilink.src_interface_id = interfaces.interface_id;
--- 
--- CREATE OR REPLACE VIEW ilink_nodes AS
--- SELECT 
--- ilink_src_node.*,
--- interfaces.node_id as dst_node_id
--- FROM ilink_src_node
--- INNER JOIN interfaces ON ilink_src_node.dst_interface_id = interfaces.interface_id;
+-- xxx TODO : expose to view_interfaces the set of ilinks a given interface is part of
+-- this is needed for properly deleting these ilinks when an interface gets deleted
+-- as this is not done yet, it prevents DeleteInterface, thus DeleteNode, thus DeleteSite
+-- from working correctly when an iLink is set
 
 --------------------------------------------------------------------------------
 -- Node groups
@@ -474,7 +478,7 @@ CREATE TABLE nodegroups (
     groupname text UNIQUE NOT NULL,            -- Group name 
     tag_type_id integer REFERENCES tag_types,  -- node is in nodegroup if it has this tag defined
     -- can be null, make management faster & easier
-    tagvalue text                              -- with this value attached
+    value text                                 -- with this value attached
 ) WITH OIDS;
 
 -- xxx - first rough implem. similar to former semantics but might be slow
@@ -484,7 +488,7 @@ FROM tag_types
 JOIN node_tag 
 USING (tag_type_id) 
 JOIN nodegroups 
-USING (tag_type_id,tagvalue);
+USING (tag_type_id,value);
 
 CREATE OR REPLACE VIEW nodegroup_nodes AS
 SELECT nodegroup_id,
@@ -621,11 +625,10 @@ GROUP BY pcu_id;
 CREATE TABLE slice_instantiations (
     instantiation text PRIMARY KEY
 ) WITH OIDS;
-INSERT INTO slice_instantiations (instantiation) VALUES 
-       ('not-instantiated'),                           -- Placeholder slice
-       ('plc-instantiated'),                           -- Instantiated by Node Manager
-       ('delegated'),                                  -- Manually instantiated
-       ('nm-controller');                              -- NM Controller
+INSERT INTO slice_instantiations (instantiation) VALUES ('not-instantiated');  -- Placeholder slice
+INSERT INTO slice_instantiations (instantiation) VALUES ('plc-instantiated');  -- Instantiated by Node Manager
+INSERT INTO slice_instantiations (instantiation) VALUES ('delegated');         -- Manually instantiated
+INSERT INTO slice_instantiations (instantiation) VALUES ('nm-controller');     -- NM Controller
 
 -- Slices
 CREATE TABLE slices (
@@ -729,27 +732,21 @@ FROM node_slice_whitelist
 GROUP BY node_id;
 
 --------------------------------------------------------------------------------
--- Slice attributes
+-- Slice tags (formerly known as slice attributes)
 --------------------------------------------------------------------------------
 
 -- Slice/sliver attributes
-CREATE TABLE slice_attribute (
-    slice_attribute_id serial PRIMARY KEY,             -- Slice attribute identifier
+CREATE TABLE slice_tag (
+    slice_tag_id serial PRIMARY KEY,           -- Slice attribute identifier
     slice_id integer REFERENCES slices NOT NULL,       -- Slice identifier
     node_id integer REFERENCES nodes,                  -- Sliver attribute if set
     nodegroup_id integer REFERENCES nodegroups,                -- Node group attribute if set
     tag_type_id integer REFERENCES tag_types NOT NULL, -- Attribute type identifier
     value text
 ) WITH OIDS;
-CREATE INDEX slice_attribute_slice_id_idx ON slice_attribute (slice_id);
-CREATE INDEX slice_attribute_node_id_idx ON slice_attribute (node_id);
-CREATE INDEX slice_attribute_nodegroup_id_idx ON slice_attribute (nodegroup_id);
-
-CREATE OR REPLACE VIEW slice_attributes AS
-SELECT slice_id,
-array_accum(slice_attribute_id) AS slice_attribute_ids
-FROM slice_attribute
-GROUP BY slice_id;
+CREATE INDEX slice_tag_slice_id_idx ON slice_tag (slice_id);
+CREATE INDEX slice_tag_node_id_idx ON slice_tag (node_id);
+CREATE INDEX slice_tag_nodegroup_id_idx ON slice_tag (nodegroup_id);
 
 --------------------------------------------------------------------------------
 -- Initscripts
@@ -777,9 +774,12 @@ CREATE TABLE peers (
     peer_url text NOT NULL,                            -- (HTTPS) URL of the peer PLCAPI interface
     cacert text,                                       -- (SSL) Public certificate of peer API server
     key text,                                          -- (GPG) Public key used for authentication
+    shortname text,                                    -- abbreviated name for displaying foreign objects
+    hrn_root text,                                             -- root for this peer domain
     deleted boolean NOT NULL DEFAULT false
 ) WITH OIDS;
 CREATE INDEX peers_peername_idx ON peers (peername) WHERE deleted IS false;
+CREATE INDEX peers_shortname_idx ON peers (shortname) WHERE deleted IS false;
 
 -- Objects at each peer
 CREATE TABLE peer_site (
@@ -970,6 +970,7 @@ COALESCE((SELECT pcu_protocol_type_ids FROM pcu_protocol_types
 AS pcu_protocol_type_ids
 FROM pcu_types;
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_events AS
 SELECT
 events.event_id,
@@ -1001,6 +1002,7 @@ event_object.object_id,
 event_object.object_type
 FROM events LEFT JOIN event_object USING (event_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_persons AS
 SELECT
 persons.person_id,
@@ -1028,6 +1030,7 @@ COALESCE((SELECT slice_ids FROM person_slices WHERE person_slices.person_id = pe
 FROM persons
 LEFT JOIN peer_person USING (person_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_peers AS
 SELECT 
 peers.*, 
@@ -1043,16 +1046,41 @@ COALESCE((SELECT slice_ids FROM peer_slices WHERE peer_slices.peer_id = peers.pe
 COALESCE((SELECT peer_slice_ids FROM peer_slices WHERE peer_slices.peer_id = peers.peer_id), '{}') AS peer_slice_ids
 FROM peers;
 
+--------------------------------------------------------------------------------
+CREATE OR REPLACE VIEW node_tags AS
+SELECT node_id,
+array_accum(node_tag_id) AS node_tag_ids
+FROM node_tag
+GROUP BY node_id;
+
+CREATE OR REPLACE VIEW view_node_tags AS
+SELECT
+node_tag.node_tag_id,
+node_tag.node_id,
+nodes.hostname,
+tag_types.tag_type_id,
+tag_types.tagname,
+tag_types.description,
+tag_types.category,
+tag_types.min_role_id,
+node_tag.value
+FROM node_tag 
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN nodes USING (node_id);
+
 CREATE OR REPLACE VIEW view_nodes AS
 SELECT
 nodes.node_id,
+nodes.node_type,
 nodes.hostname,
 nodes.site_id,
 nodes.boot_state,
+nodes.run_level,
 nodes.deleted,
 nodes.model,
 nodes.boot_nonce,
 nodes.version,
+nodes.verified,
 nodes.ssh_rsa_key,
 nodes.key,
 CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created,
@@ -1081,14 +1109,15 @@ AS ports,
 COALESCE((SELECT conf_file_ids FROM node_conf_files
                 WHERE node_conf_files.node_id = nodes.node_id), '{}') 
 AS conf_file_ids,
-COALESCE((SELECT tag_ids FROM node_tags 
+COALESCE((SELECT node_tag_ids FROM node_tags 
                 WHERE node_tags.node_id = nodes.node_id), '{}') 
-AS tag_ids,
+AS node_tag_ids,
 node_session.session_id AS session
 FROM nodes
 LEFT JOIN peer_node USING (node_id)
 LEFT JOIN node_session USING (node_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_nodegroups AS
 SELECT
 nodegroups.*,
@@ -1101,6 +1130,7 @@ COALESCE((SELECT node_ids FROM nodegroup_nodes
 AS node_ids
 FROM nodegroups INNER JOIN tag_types USING (tag_type_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_conf_files AS
 SELECT
 conf_files.*,
@@ -1112,6 +1142,7 @@ COALESCE((SELECT nodegroup_ids FROM conf_file_nodegroups
 AS nodegroup_ids
 FROM conf_files;
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_pcus AS
 SELECT
 pcus.*,
@@ -1119,6 +1150,7 @@ COALESCE((SELECT node_ids FROM pcu_nodes WHERE pcu_nodes.pcu_id = pcus.pcu_id),
 COALESCE((SELECT ports FROM pcu_nodes WHERE pcu_nodes.pcu_id = pcus.pcu_id), '{}') AS ports
 FROM pcus;
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_sites AS
 SELECT
 sites.site_id,
@@ -1146,6 +1178,7 @@ COALESCE((SELECT pcu_ids FROM site_pcus WHERE site_pcus.site_id = sites.site_id)
 FROM sites
 LEFT JOIN peer_site USING (site_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_addresses AS
 SELECT
 addresses.*,
@@ -1153,6 +1186,7 @@ COALESCE((SELECT address_type_ids FROM address_address_types WHERE address_addre
 COALESCE((SELECT address_types FROM address_address_types WHERE address_address_types.address_id = addresses.address_id), '{}') AS address_types
 FROM addresses;
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_keys AS
 SELECT
 keys.*,
@@ -1163,6 +1197,13 @@ FROM keys
 LEFT JOIN person_key USING (key_id)
 LEFT JOIN peer_key USING (key_id);
 
+--------------------------------------------------------------------------------
+CREATE OR REPLACE VIEW slice_tags AS
+SELECT slice_id,
+array_accum(slice_tag_id) AS slice_tag_ids
+FROM slice_tag
+GROUP BY slice_id;
+
 CREATE OR REPLACE VIEW view_slices AS
 SELECT
 slices.slice_id,
@@ -1180,25 +1221,28 @@ peer_slice.peer_id,
 peer_slice.peer_slice_id,
 COALESCE((SELECT node_ids FROM slice_nodes WHERE slice_nodes.slice_id = slices.slice_id), '{}') AS node_ids,
 COALESCE((SELECT person_ids FROM slice_persons WHERE slice_persons.slice_id = slices.slice_id), '{}') AS person_ids,
-COALESCE((SELECT slice_attribute_ids FROM slice_attributes WHERE slice_attributes.slice_id = slices.slice_id), '{}') AS slice_attribute_ids
+COALESCE((SELECT slice_tag_ids FROM slice_tags WHERE slice_tags.slice_id = slices.slice_id), '{}') AS slice_tag_ids
 FROM slices
 LEFT JOIN peer_slice USING (slice_id);
 
-CREATE OR REPLACE VIEW view_slice_attributes AS
+CREATE OR REPLACE VIEW view_slice_tags AS
 SELECT
-slice_attribute.slice_attribute_id,
-slice_attribute.slice_id,
-slice_attribute.node_id,
-slice_attribute.nodegroup_id,
+slice_tag.slice_tag_id,
+slice_tag.slice_id,
+slice_tag.node_id,
+slice_tag.nodegroup_id,
 tag_types.tag_type_id,
 tag_types.tagname,
 tag_types.description,
 tag_types.category,
 tag_types.min_role_id,
-slice_attribute.value
-FROM slice_attribute
-INNER JOIN tag_types USING (tag_type_id);
+slice_tag.value,
+slices.name
+FROM slice_tag
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN slices USING (slice_id);
 
+--------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_sessions AS
 SELECT
 sessions.session_id,
@@ -1213,15 +1257,13 @@ LEFT JOIN node_session USING (session_id);
 -- Built-in maintenance account and default site
 --------------------------------------------------------------------------------
 
-INSERT INTO persons
-(first_name, last_name, email, password, enabled)
-VALUES
-('Maintenance', 'Account', 'maint@localhost.localdomain', 'nopass', true);
+INSERT INTO persons (first_name, last_name, email, password, enabled)
+VALUES              ('Maintenance', 'Account', 'maint@localhost.localdomain', 'nopass', true);
 
-INSERT INTO person_role (person_id, role_id) 
-       VALUES (1, 10), (1, 20), (1, 30), (1, 40);
+INSERT INTO person_role (person_id, role_id) VALUES (1, 10);
+INSERT INTO person_role (person_id, role_id) VALUES (1, 20);
+INSERT INTO person_role (person_id, role_id) VALUES (1, 30);
+INSERT INTO person_role (person_id, role_id) VALUES (1, 40);
 
-INSERT INTO sites
-(login_base, name, abbreviated_name, max_slices)
-VALUES
-('pl', 'PlanetLab Central', 'PLC', 100);
+INSERT INTO sites (login_base, name, abbreviated_name, max_slices)
+VALUES ('pl', 'PlanetLab Central', 'PLC', 100);
diff --git a/plc.d/api b/plc.d/api
new file mode 100755 (executable)
index 0000000..4061b8d
--- /dev/null
+++ b/plc.d/api
@@ -0,0 +1,58 @@
+#!/bin/bash
+# $Id$
+# $URL$
+#
+# priority: 800
+#
+# Configure the API. Must be done after SSL certificates are generated
+# and before the API web server is brought up.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+
+# Source function library and configuration
+. /etc/plc.d/functions
+. /etc/planetlab/plc_config
+local_config=/etc/planetlab/configs/site.xml
+
+# Be verbose
+set -x
+
+case "$1" in
+    start)
+       if [ "$PLC_API_ENABLED" != "1" ] ; then
+           exit 0
+       fi
+
+       MESSAGE=$"Configuring the API"
+       dialog "$MESSAGE"
+
+       # Make sure that the API maintenance account is protected by a
+       # password.
+       if [ -z "$PLC_API_MAINTENANCE_PASSWORD" ] ; then
+           PLC_API_MAINTENANCE_PASSWORD=$(uuidgen)
+           plc-config --category=plc_api --variable=maintenance_password --value="$PLC_API_MAINTENANCE_PASSWORD" --save=$local_config $local_config
+           service plc reload
+       fi
+
+       # Make sure that all PLC servers are allowed to access the API
+       # through the maintenance account.
+       PLC_API_MAINTENANCE_SOURCES=($((
+           for ip in $PLC_API_MAINTENANCE_SOURCES ; do
+               echo $ip
+           done
+           for server in API BOOT WWW ; do
+               hostname=PLC_${server}_HOST
+               gethostbyname ${!hostname}
+           done
+        ) | sort -u))
+       PLC_API_MAINTENANCE_SOURCES=${PLC_API_MAINTENANCE_SOURCES[*]}
+       plc-config --category=plc_api --variable=maintenance_sources --value="$PLC_API_MAINTENANCE_SOURCES" --save=$local_config $local_config
+       service plc reload
+
+       result "$MESSAGE"
+       ;;
+esac
+
+exit $ERRORS
diff --git a/plc.d/db b/plc.d/db
new file mode 100755 (executable)
index 0000000..a295aad
--- /dev/null
+++ b/plc.d/db
@@ -0,0 +1,193 @@
+#!/bin/bash
+# $Id$
+# $URL$
+#
+# priority: 900
+#
+# Bootstrap the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+
+# Source function library and configuration
+. /etc/plc.d/functions
+. /etc/planetlab/plc_config
+
+# Be verbose
+set -x
+
+# Export so that we do not have to specify -p to psql invocations
+export PGPORT=$PLC_DB_PORT
+
+# Updates the database by applying all migration scripts in
+# /usr/share/plc_api/migrations/N-up-*, where N is greater than the
+# current subversion. At least one of the migration scripts with the
+# same N must update plc_db_version.subversion.
+function migrate_db()
+{
+    subversion=$(psql -U $PLC_DB_USER --quiet --tuples-only --no-align -c \
+                "SELECT subversion FROM plc_db_version LIMIT 1" \
+                $PLC_DB_NAME 2>/dev/null || echo 0)
+    shopt -s nullglob
+    for file in /usr/share/plc_api/migrations/[0-9]*-up-* ; do
+       script=$(basename $file)
+       index=${script%-up*}
+       extension=${script##*.}
+       if [ $index -gt $subversion ] ; then
+           if [ "$extension" = "sql" ] ; then
+               dialog " - $script (dbdumped)"
+               dump_planetlab_db "before-$script"
+               psql -U $PLC_DB_USER -f $file $PLC_DB_NAME
+           elif [ -x $file ] ; then
+               dialog " - $script (dbdumped)"
+               dump_planetlab_db "before-$script"
+               $file
+           else
+               dialog "\nWarning: migration $file not executable"
+           fi
+           check
+       fi
+    done
+}
+
+function checkpoint_planetlab_db()
+{
+    dumpfile=$1
+    pg_dump -U $PLC_DB_USER $PLC_DB_NAME > $dumpfile
+    check
+}
+
+function restore_planetlab_db()
+{
+    dumpfile=$1
+    if [ -n "$dumpfile" ] ; then 
+       [ -f "$dumpfile" ] && psql -a -U $PLC_DB_USER $PLC_DB_NAME < $dumpfile
+       check
+    fi
+}
+
+# use a single date of this script invocation for the dump_*_db functions.
+DATE=$(date +"%Y-%m-%d-%H-%M-%S")
+
+# Dumps the database - optional argument to specify filename suffix
+function dump_planetlab_db()
+{
+    if [ -n "$1" ] ; then suffix="-$1" ; else suffix="" ; fi
+    dumpfile=/var/lib/pgsql/backups/$(date +"${PLC_DB_NAME}.${DATE}${suffix}.sql")
+    checkpoint_planetlab_db $dumpfile
+}
+
+function restore_drupal_db()
+{
+    dumpfile=$1
+    if [ -n "$dumpfile" ] ; then 
+       [ -f "$dumpfile" ] && psql -a -U $PLC_DB_USER drupal < $1
+       check
+    fi
+}
+
+function checkpoint_drupal_db()
+{
+    dumpfile=$1
+    pg_dump -U $PLC_DB_USER drupal > $dumpfile
+    check
+}
+
+function dump_drupal_db()
+{
+    dumpfile=/var/lib/pgsql/backups/$(date +"drupal.${DATE}.sql")
+    checkpoint_drupal_db $dumpfile
+    check
+}
+
+# Clean up old backups
+function clean_dumps()
+{
+    find /var/lib/pgsql/backups '(' -name "$PLC_DB_NAME.*.sql" -o -name "drupal.*.sql" ')' -a -atime +15 | xargs rm -f
+    check
+}
+
+[ $PLC_DB_ENABLED -ne 1 ] && exit 0
+case "$1" in
+    start)
+       MESSAGE=$"Bootstrapping the database"
+       dialog "$MESSAGE"
+
+       # Apply schema updates
+       migrate_db
+
+       # Update the maintenance account username. This can't be
+       # done through the api-config script since it uses the
+       # maintenance account to access the API. The maintenance
+       # account should be person_id 1 since it is created by the
+       # DB schema itself.
+       psql -U $PLC_DB_USER -c "UPDATE persons SET email='$PLC_API_MAINTENANCE_USER' WHERE person_id=1" $PLC_DB_NAME
+
+       # Update the Drupal site_name variable
+       # also turn off drupal native user registration
+       psql -U $PLC_DB_USER drupal <<EOF
+DELETE FROM variable WHERE name = 'site_name';
+INSERT INTO variable (name, value) VALUES ('site_name', 's:${#PLC_NAME}:"$PLC_NAME";');
+DELETE FROM variable WHERE name = 'user_register';
+INSERT INTO variable (name, value) VALUES ('user_register', 's:1:"0";');
+DELETE FROM cache;
+EOF
+
+       # Bootstrap the DB
+       db-config
+       check
+
+       result "$MESSAGE"
+       ;;
+
+    migrate)
+       MESSAGE=$"Migrating the database"
+       dialog "$MESSAGE"
+
+       migrate_db
+       result "$MESSAGE"
+       ;;
+
+    dump)
+       MESSAGE=$"Dumping the databases in /var/lib/pgsql/backups"
+       dialog "$MESSAGE"
+
+       dump_planetlab_db
+       dump_drupal_db
+       result "$MESSAGE"
+       ;;
+
+    checkpoint)
+       MESSAGE=$"Checkpointing the databases"
+       checkpoint_planetlab_db $2
+       checkpoint_drupal_db $3
+       ;;
+
+    restore)
+       MESSAGE=$"Restoring the databases from checkpoint files"
+       restore_planetlab_db $2
+       restore_drupal_db $3
+       ;;
+
+    clean-dump)
+       MESSAGE=$"Cleaning old database dumps"
+       dialog "$MESSAGE"
+
+       clean_dumps
+       result "$MESSAGE"
+       ;;
+
+    stop)
+       MESSAGE="Ignoring request to stop myplc databases"
+       dialog "$MESSAGE"
+       result ""
+       ;;
+
+    *)
+        echo "Usage: $0 [start|migrate|dump|checkpoint|restore|clean-dump|stop]"
+       exit 1
+       ;;
+esac
+
+exit $ERRORS
diff --git a/plc.d/postgresql b/plc.d/postgresql
new file mode 100755 (executable)
index 0000000..defab86
--- /dev/null
@@ -0,0 +1,196 @@
+#!/bin/bash
+# $Id$
+# $URL$
+#
+# priority: 700
+#
+# Manage the PostgreSQL database server
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+
+# Source function library and configuration
+. /etc/plc.d/functions
+. /etc/planetlab/plc_config
+local_config=/etc/planetlab/configs/site.xml
+
+# Be verbose
+set -x
+
+# Default locations
+PGDATA=/var/lib/pgsql/data
+postgresql_conf=$PGDATA/postgresql.conf
+pghba_conf=$PGDATA/pg_hba.conf
+
+# Export so that we do not have to specify -p to psql invocations
+export PGPORT=$PLC_DB_PORT
+
+# /etc/init.d/postgresql always returns 0, even on failure
+postgresql_start ()
+{
+    # start() always returns 0
+    (exec 3>&- 4>&- ; service postgresql start)
+
+    # status() will still return 0 even while still initializing
+    if status postmaster && [ -f /var/lock/subsys/postgresql ] ; then
+       # The only way we can be sure is if we can access it
+       for i in $(seq 1 10) ; do
+           # Must do this as the postgres user initially (before we
+           # fix pg_hba.conf to passwordless localhost access).
+           su -c 'psql -U postgres -c "" template1' postgres && return 0
+           sleep 1
+       done
+    fi
+
+    return 1
+}
+
+postgresql_init ()
+{
+    service postgresql initdb &> /dev/null || :
+    postgresql_start
+}
+
+case "$1" in
+    start)
+       if [ "$PLC_DB_ENABLED" != "1" ] ; then
+           exit 0
+       fi
+
+       MESSAGE=$"Starting PostgreSQL server"
+       dialog "$MESSAGE"
+
+       # Set data directory and redirect startup output to /var/log/pgsql
+       mkdir -p /etc/sysconfig/pgsql
+       (
+           echo "PGDATA=$PGDATA"
+           echo "PGLOG=/var/log/pgsql"
+           echo "PGPORT=$PLC_DB_PORT"
+       ) >>/etc/sysconfig/pgsql/postgresql
+
+       # Fix ownership (rpm installation may have changed it)
+       chown -R -H postgres:postgres $(dirname $PGDATA)
+
+       # PostgreSQL must be started at least once to bootstrap
+       # /var/lib/pgsql/data
+       if [ ! -f $postgresql_conf ] ; then
+           postgresql_init
+           check
+           service postgresql stop
+           check
+       fi
+
+       # Enable DB server. PostgreSQL >=8.0 defines listen_addresses,
+       # PostgreSQL 7.x uses tcpip_socket.
+       if grep -q listen_addresses $postgresql_conf ; then
+           sed -i -e '/^listen_addresses/d' $postgresql_conf
+           echo "listen_addresses = '*'" >>$postgresql_conf
+       elif grep -q tcpip_socket $postgresql_conf ; then
+           sed -i -e '/^tcpip_socket/d' $postgresql_conf
+           echo "tcpip_socket = true" >>$postgresql_conf
+       fi
+
+       # Disable access to all DBs from all hosts
+       sed -i -e '/^\(host\|local\)/d' $pghba_conf
+
+       # Enable passwordless localhost access
+       echo "local all all trust" >>$pghba_conf
+
+       # Enable access from the API, boot, and web servers
+       PLC_API_IP=$(gethostbyname $PLC_API_HOST)
+       PLC_BOOT_IP=$(gethostbyname $PLC_BOOT_HOST)
+       PLC_WWW_IP=$(gethostbyname $PLC_WWW_HOST)
+       ip_failure=0
+       if [ -z "$PLC_API_IP" ] ; then
+           MESSAGE=$"PLC_API_IP is not set"
+           dialog "$MESSAGE"
+           ip_failure=1
+       fi
+       if [ -z "$PLC_BOOT_IP" ] ; then
+           MESSAGE=$"PLC_BOOT_IP is not set"
+           dialog "$MESSAGE"
+           ip_failure=1
+       fi
+       if [ -z "$PLC_WWW_IP" ] ; then
+           MESSAGE=$"PLC_WWW_IP is not set"
+           dialog "$MESSAGE"
+           ip_failure=1
+       fi
+       if [ $ip_failure -eq 1 ] ; then
+           /bin/false
+           check
+       fi
+
+       (
+           echo "host $PLC_DB_NAME $PLC_DB_USER 127.0.0.1/32 password"
+           echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_API_IP/32 password"
+           echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_BOOT_IP/32 password"
+           echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_WWW_IP/32 password"
+           # Drupal also uses PostgreSQL
+           echo "host drupal $PLC_DB_USER 127.0.0.1/32 password"
+           echo "host drupal $PLC_DB_USER $PLC_WWW_IP/32 password"
+       ) >>$pghba_conf
+
+       # Append site-specific access rules
+       for file in $pghba_conf.d/*.conf ; do
+           cat "$file" >>$pghba_conf
+       done
+
+       # Fix ownership (sed -i changes it)
+       chown postgres:postgres $postgresql_conf $pghba_conf
+
+       # Start up the server
+       postgresql_start
+       check
+
+       # Create/update the unprivileged database user and password
+       if [ -z "$PLC_DB_PASSWORD" ] ; then
+           PLC_DB_PASSWORD=$(uuidgen)
+           plc-config --category=plc_db --variable=password --value="$PLC_DB_PASSWORD" --save=$local_config $local_config
+           service plc reload
+       fi
+       if ! psql -U $PLC_DB_USER -c "" template1 >/dev/null 2>&1 ; then
+           psql -U postgres -c "CREATE USER $PLC_DB_USER PASSWORD '$PLC_DB_PASSWORD'" template1
+       else
+           psql -U postgres -c "ALTER USER $PLC_DB_USER WITH PASSWORD '$PLC_DB_PASSWORD'" template1
+       fi
+       check
+
+       # Create the databases if necessary
+       if ! psql -U $PLC_DB_USER -c "" $PLC_DB_NAME >/dev/null 2>&1 ; then
+           createdb -U postgres --template=template0 --encoding=UNICODE --owner=$PLC_DB_USER $PLC_DB_NAME
+           psql -U $PLC_DB_USER -f /usr/share/plc_api/$PLC_DB_NAME.sql $PLC_DB_NAME
+       fi
+       check
+       if ! psql -U $PLC_DB_USER -c "" drupal >/dev/null 2>&1 ; then
+           createdb -U postgres --template=template0 --encoding=UNICODE --owner=$PLC_DB_USER drupal
+            psql -U $PLC_DB_USER -f /var/www/html/database/database.pgsql drupal 
+       fi
+       check
+
+       result "$MESSAGE"
+       ;;
+
+    stop)
+       MESSAGE=$"Stopping PostgreSQL server"
+       dialog "$MESSAGE"
+
+       # Drop the current user in case the username changes
+       psql -U postgres -c "DROP USER $PLC_DB_USER" template1
+
+       # WARNING: If the DB name changes, the old DB will be left
+       # intact and a new one will be created. If it changes
+       # back, the old DB will not be re-created.
+
+       # Shut down the server
+       service postgresql stop
+
+       # /etc/init.d/postgresql fails if it is not running
+       [ "$PLC_DB_ENABLED" = 1 ] && check
+
+       result "$MESSAGE"
+       ;;
+esac
+
+exit $ERRORS
index 1e7725d..04ac71a 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -6,6 +6,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 from distutils.core import setup
@@ -14,7 +15,18 @@ from glob import glob
 setup(py_modules = ['ModPython'],
       packages = ['PLC', 'PLC/Methods', 'PLC/Methods/system', 'PLC/Accessors', 'PLC/Legacy'],
       scripts = ['plcsh', 'Server.py'],
-      data_files = [('', ['planetlab5.sql']),
-                    ('php', ['php/plc_api.php']),
-                    ('migrations', ['migrations/README.txt'] + glob('migrations/[0-9][0-9][0-9]*')),
-                    ])
+      data_files = [
+        ('', ['planetlab5.sql']),
+        ('php', ['php/plc_api.php']),
+        ('migrations', 
+         ['migrations/README.txt',
+          'migrations/extract-views.py'] 
+         + glob('migrations/[0-9][0-9][0-9]*')),
+        ('migrations/v42-to-v43', 
+         ['migrations/v42-to-v43/migrate.sh',
+          'migrations/v42-to-v43/migrate.sed',
+          'migrations/v42-to-v43/migrate.sql',
+          'migrations/v42-to-v43/parse-site-nodegroups.py',
+          'migrations/v42-to-v43/site-nodegroups.def'
+          ]),
+        ])
index d206098..7bae2ef 100755 (executable)
@@ -7,6 +7,7 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # $Id$
+# $URL$
 #
 
 import re
@@ -18,7 +19,7 @@ rename = {'nm_net_min_rate': 'net_min_rate',
           'nm_net_max_rate': 'net_max_rate',
           'nm_net_exempt_min_rate': 'net_i2_min_rate',
           'nm_net_exempt_max_rate': 'net_i2_max_rate'}
-for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
+for slice_attribute in GetSliceTags({'name': rename.keys()}):
     id = slice_attribute['slice_attribute_id']
     name = slice_attribute['name']
     slice_id = slice_attribute['slice_id']
@@ -29,10 +30,10 @@ for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
 
     # Add the new attribute
     if GetSlices([slice_id]):
-        AddSliceAttribute(slice_id, rename[name], str(kbps))
+        AddSliceTag(slice_id, rename[name], str(kbps))
 
     # Delete the old attribute
-    DeleteSliceAttribute(id)
+    DeleteSliceTag(id)
 
 # Convert nm_net_{exempt_,}avg_rate to
 # net_{i2_,}max_kbyte and net_{i2_,}thresh_kbyte
@@ -40,7 +41,7 @@ rename = {'nm_net_avg_rate': {'max': 'net_max_kbyte',
                               'thresh': 'net_thresh_kbyte'},
           'nm_net_exempt_avg_rate': {'max': 'net_i2_max_kbyte',
                                      'thresh': 'net_i2_thresh_kbyte'}}
-for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
+for slice_attribute in GetSliceTags({'name': rename.keys()}):
     id = slice_attribute['slice_attribute_id']
     name = slice_attribute['name']
     slice_id = slice_attribute['slice_id']
@@ -52,14 +53,14 @@ for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
 
     # Add the new attribute
     if GetSlices([slice_id]):
-        AddSliceAttribute(slice_id, rename[name]['max'], str(max_kbyte))
-        AddSliceAttribute(slice_id, rename[name]['thresh'], str(thresh_kbyte))
+        AddSliceTag(slice_id, rename[name]['max'], str(max_kbyte))
+        AddSliceTag(slice_id, rename[name]['thresh'], str(thresh_kbyte))
 
     # Delete the old attribute
-    DeleteSliceAttribute(id)
+    DeleteSliceTag(id)
 
 # Convert plc_slice_state
-for slice_attribute in GetSliceAttributes({'name': 'plc_slice_state'}):
+for slice_attribute in GetSliceTags({'name': 'plc_slice_state'}):
     id = slice_attribute['slice_attribute_id']
     name = slice_attribute['name']
     slice_id = slice_attribute['slice_id']
@@ -67,12 +68,12 @@ for slice_attribute in GetSliceAttributes({'name': 'plc_slice_state'}):
     # Add the new attribute
     if GetSlices([slice_id]):
         if slice_attribute['value'] == "suspended":
-            AddSliceAttribute(slice_id, 'enabled', "0")
+            AddSliceTag(slice_id, 'enabled', "0")
         else:
-            AddSliceAttribute(slice_id, 'enabled', "1")
+            AddSliceTag(slice_id, 'enabled', "1")
 
     # Delete the old attribute
-    DeleteSliceAttribute(id)
+    DeleteSliceTag(id)
     
 # Straight renames
 rename = {'nm_cpu_share': 'cpu_share',
@@ -83,7 +84,7 @@ rename = {'nm_cpu_share': 'cpu_share',
           'nm_net_max_thresh_byte': 'net_thresh_kbyte',
           'nm_net_max_exempt_byte': 'net_i2_max_kbyte',
           'nm_net_max_thresh_exempt_byte': 'net_i2_thresh_kbyte'}
-for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
+for slice_attribute in GetSliceTags({'name': rename.keys()}):
     id = slice_attribute['slice_attribute_id']
     name = slice_attribute['name']
     slice_id = slice_attribute['slice_id']
@@ -93,16 +94,16 @@ for slice_attribute in GetSliceAttributes({'name': rename.keys()}):
 
     # Add the new attribute
     if GetSlices([slice_id]):
-        AddSliceAttribute(slice_id, rename[name], value)
+        AddSliceTag(slice_id, rename[name], value)
 
     # Delete the old attribute
-    DeleteSliceAttribute(id)
+    DeleteSliceTag(id)
 
 # Update plc_ticket_pubkey attribute
-for slice_attribute in GetSliceAttributes({'name': "plc_ticket_pubkey"}):
+for slice_attribute in GetSliceTags({'name': "plc_ticket_pubkey"}):
     id = slice_attribute['slice_attribute_id']
 
-    UpdateSliceAttribute(id, """
+    UpdateSliceTag(id, """
 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKXa72MEKDAnVyzEpKOB1ot2eW
 xG/TG2aa7q/2oy1xf5XMmU9H9uKwO+GoUeinp1BSxgkVRF0VhEGGaqKR9kYQzX0k
 ht4+P2hAr+UyU4cp0NxV4xfmyAbrNKuHVjawMUCu5BH0IkBUC/89ckxk71oROnak
@@ -110,14 +111,14 @@ FbI7ojUezSGr4aVabQIDAQAB
 """.lstrip())
 
 # Delete _deleted and deprecated slice attributes and types
-for attribute_type in GetSliceAttributeTypes():
+for attribute_type in GetSliceTagTypes():
     id = attribute_type['attribute_type_id']
     name = attribute_type['name']
 
     if name == 'general_prop_share' or \
        re.match('nm_', name) or \
        re.search('_deleted$', name):
-        DeleteSliceAttributeType(id)
+        DeleteSliceTagType(id)
         # N.B. Automatically deletes all slice attributes of this type
 
 # Add Proper ops
@@ -209,20 +210,20 @@ proper_ops = [
 
 for slice, op in proper_ops:
     try:
-        AddSliceAttribute(slice, 'proper_op', op)
+        AddSliceTag(slice, 'proper_op', op)
     except Exception, err:
         print "Warning: %s:" % slice, err
 
 initscripts = dict([(initscript['initscript_id'], initscript) for initscript in [{'initscript_id': 8, 'script': 'IyEgL2Jpbi9zaA0KDQojIDxQcm9ncmFtIE5hbWU+DQojICAgIGJpbmRzY3JpcHQNCiMNCiMgPEF1dGhvcj4NCiMgICAgSmVmZnJ5IEpvaG5zdG9uIGFuZCBKZXJlbXkgUGxpY2h0YQ0KIw0KIyA8UHVycG9zZT4NCiMgICAgRG93bmxvYWRzIGFuZCBpbnN0YWxscyBzdG9yayBvbiBhIG5vZGUuDQoNCiMgc2F2ZSBvcmlnaW5hbCBQV0QNCk9MRFBXRD0kUFdEDQoNCiMgZXJyb3IgcmVwb3J0aW5nIGZ1bmN0aW9uDQplcnJvcigpDQp7DQogICBlY2hvDQogICBlY2hvICJQbGVhc2UgRS1tYWlsIHN0b3JrLXN1cHBvcnRAY3MuYXJpem9uYS5lZHUgaWYgeW91IGJlbGlldmUgeW91IGhhdmUiIA0KICAgZWNobyAicmVjZWl2ZWQgdGhpcyBtZXNzYWdlIGluIGVycm9yLiINCg0KICAgIyBnZXQgcmlkIG9mIENFUlQgZmlsZQ0KICAgaWYgWyAtZiAkQ0VSVCBdDQogICB0aGVuDQogICAgICBybSAtZiAkQ0VSVCA+IC9kZXYvbnVsbA0KICAgZmkNCg0KICAgIyByZXN0b3JlIG9yaWdpbmFsIFBXRA0KICAgY2QgJE9MRFBXRA0KICAgZXhpdCAxDQp9DQoNCkNFUlQ9YHB3ZGAvdGVtcGNydGZpbGUNCg0KI2Z1bmN0aW9ucw0KDQojIyMNCiMjIyBjcmVhdGVDZXJ0aWZpY2F0ZSgpDQojIyMgICAgcHJpbnRzIG91dCB0aGUgZXF1aWZheCBjZXJ0aWZpY2F0ZSB0byB1c2UgYW5kIHN0b3Jlcw0KIyMjICAgIHRoZSBmaWxlIG5hbWUgaW4gJENFUlQNCiMjIw0KZnVuY3Rpb24gY3JlYXRlQ2VydGlmaWNhdGUoKXsNCmNhdCA+ICRDRVJUIDw8RVFVSUZBWA0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlDa0RDQ0FmbWdBd0lCQWdJQkFUQU5CZ2txaGtpRzl3MEJBUVFGQURCYU1Rc3dDUVlEVlFRR0V3SlYNClV6RWNNQm9HQTFVRUNoTVRSWEYxYVdaaGVDQlRaV04xY21VZ1NXNWpMakV0TUNzR0ExVUVBeE1rUlhGMQ0KYVdaaGVDQlRaV04xY21VZ1IyeHZZbUZzSUdWQ2RYTnBibVZ6Y3lCRFFTMHhNQjRYRFRrNU1EWXlNVEEwDQpNREF3TUZvWERUSXdNRFl5TVRBME1EQXdNRm93V2pFTE1Ba0dBMVVFQmhNQ1ZWTXhIREFhQmdOVkJBb1QNCkUwVnhkV2xtWVhnZ1UyVmpkWEpsSUVsdVl5NHhMVEFyQmdOVkJBTVRKRVZ4ZFdsbVlYZ2dVMlZqZFhKbA0KSUVkc2IySmhiQ0JsUW5WemFXNWxjM01nUTBFdE1UQ0JuekFOQmdrcWhraUc5dzBCQVFFRkFBT0JqUUF3DQpnWWtDZ1lFQXV1Y1hrQUpsc1RSVlBFbkNVZFhmcDlFM2o5SG5nWE5CVW1DYm5hRVhKbml0eDdIb0pwUXkNCnRkNHpqVG92Mi9LYWVscHptS05jNmZ1S2N4dGM1OE8vZ0d6TnFmVFdLOEQzK1ptcVk2S3hSd0lQMU9SUg0KT2hJOGJJcGFWSVJ3MjhIRmtNOXlSY3VvV2NETk01MC9vNWJyaFRNaEhENGVQbUJ1ZHB4bmhjWEl3MkVDDQpBd0VBQWFObU1HUXdFUVlKWUlaSUFZYjRRZ0VCQkFRREFnQUhNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHcNCkh3WURWUjBqQkJnd0ZvQVV2cWlnZEhKUWEwUzN5U1BZKzZqL3MxZHJhR3d3SFFZRFZSME9CQllFRkw2bw0Kb0hSeVVHdEV0OGtqMlB1by83TlhhMmhzTUEwR0NTcUdTSWIzRFFFQkJBVUFBNEdCQUREaUFWR3F4K3BmDQoycm5RWlE4dzFqN2FEUlJKYnBHVEp4UXg3OFQzTFVYNDdNZS9va0VOSTdTUytSa0FaNzBCcjgzZ2NmeGENCnoyVEU0SmFZMEtOQTRnR0s3eWNIOFdVQmlrUXRCbVYxVXNDR0VDQWhYMnhyRDJ5dUNSeXY4cUlZTk1SMQ0KcEhNYzhZM2M3NjM1czNhMGtyL2NsUkFldnN2SU8xcUVZQmxXbEtsVg0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLSANCkVRVUlGQVgNCn0NCg0KIyMjDQojIyMgb3ZlcldyaXRlQ29uZigpDQojIyMJb3ZlcndyaXRlIHRoZSBkZWZhdWx0IHN0b3JrLmNvbmYgZmlsZQ0KIyMjICAgICB0aGF0IHdhcyBpbnN0YWxsZWQgYnkgdGhlIHJwbSBwYWNrYWdlLg0KIyMjICAgICB0aGlzIGlzIGEgdGVtcG9yYXJ5IGhhY2sgYmVjYXVzZSBJIG5lZWQNCiMjIyAgICAgdG8gY2hhbmdlIHRoZSBuZXN0cG9ydCBhbmQgSSBkb250IGtub3cNCiMjIyAgICAgZW5vdWdoIHRvIHJlcGFja2FnZSB0aGUgcnBtIHdpdGggdGhlDQojIyMgICAgIGNvcnJlY3Qgc2V0dGluZ3MNCmZ1bmN0aW9uIG92ZXJXcml0ZUNvbmYoKXsNCmNhdCA+IC91c3IvbG9jYWwvc3RvcmsvZXRjL3N0b3JrLmNvbmYgPDxFTkRPRkZJTEUNCnBhY21hbj0vdXNyL2xvY2FsL3N0b3JrL2Jpbi9wYWNtYW4NCmR0ZC1wYWNrYWdlcz0vdXNyL2xvY2FsL3N0b3JrL2Jpbi9wYWNrYWdlcy5kdGQNCmR0ZC1ncm91cHM9L3Vzci9sb2NhbC9zdG9yay9iaW4vZ3JvdXBzLmR0ZA0Kc3RvcmtuZXN0dXBkYXRlbGlzdGVuZXJwb3J0PTY0OQ0KDQojYml0dG9ycmVudHRyYWNrZXJob3N0PXF1YWRydXMuY3MuYXJpem9uYS5lZHUNCmJpdHRvcnJlbnR0cmFja2VyaG9zdD1ucjA2LmNzLmFyaXpvbmEuZWR1DQoNCmJpdHRvcnJlbnR0cmFja2VycG9ydD02ODgwDQpiaXR0b3JyZW50dXBsb2FkcmF0ZT0wDQpiaXR0b3JyZW50c2VlZGxvb2t1cHRpbWVvdXQ9MzANCg0KI3BhY2thZ2VyZXBvc2l0b3J5ID0gcXVhZHJ1cy5jcy5hcml6b25hLmVkdS9QbGFuZXRMYWIvVjN8ZGlzdCwgc3RhYmxlDQpwYWNrYWdlcmVwb3NpdG9yeSA9IG5yMDYuY3MuYXJpem9uYS5lZHUvUGxhbmV0TGFiL1YzfGRpc3QsIHN0YWJsZQ0KI3BhY2thZ2VpbmZvcmVwb3NpdG9yeSA9IHF1YWRydXMuY3MuYXJpem9uYS5lZHUvUGxhbmV0TGFiL1YzL3N0b3JrLmluZm8NCnBhY2thZ2VpbmZvcmVwb3NpdG9yeSA9IG5yMDYuY3MuYXJpem9uYS5lZHUvUGxhbmV0TGFiL1YzL3N0b3JrLmluZm8NCg0KdXNlcm5hbWUgPSBQbGFuZXRMYWINCnB1YmxpY2tleWZpbGUgPSAvdXNyL2xvY2FsL3N0b3JrL3Zhci9rZXlzL1BsYW5ldExhYi5wdWJsaWNrZXkNCnBhY2thZ2VtYW5hZ2VycyA9IG5lc3RycG0sIHJwbSwgdGFyZ3oNCnRyYW5zZmVybWV0aG9kPSBuZXN0LGJpdHRvcnJlbnQsY29ibGl0eixjb3JhbCxodHRwLGZ0cA0KbmVzdHBvcnQ9NjAwMA0KdGFycGFja2luZm9wYXRoPS91c3IvbG9jYWwvc3RvcmsvdmFyL3RhcmluZm8NCkVORE9GRklMRQ0KfSANCg0KDQojIyMNCiMjIyBkb3dubG9hZE5SMDYoKQ0KIyMjICAgIGRvd25sb2FkIGEgZmlsZSBmcm9tIG5yMDYgdXNpbmcgY3VybA0KIyMjDQojIyMgYXJnczogDQojIyMgICAgICAgLSB0aGUgcGF0aCBvZiB0aGUgZmlsZSB5b3Ugd2lzaCB0byBkb3dubG9hZA0KIyMjICAgICAgICAgcmVsYXRpdmUgZnJvbSBodHRwczovL25yMDYuY3MuYXJpem9uYS5lZHUNCiMjIyAgICAgICAtIHRoZSBmaWxlIHRvIHNhdmUgaXQgdG8NCiMjIyAgICAgICAtIHJldHVybmVkIHZhbHVlIGFzIHNwZWNpZmllZCBpbiB2ZXJpZnlEb3dubG9hZA0KZnVuY3Rpb24gZG93bmxvYWROUjA2KCl7DQogICAgY3VybCAtLWNhY2VydCAkQ0VSVCBodHRwczovL25yMDYuY3MuYXJpem9uYS5lZHUvJDEgLW8gJDIgMj4vZGV2L251bGwNCiAgICB2ZXJpZnlEb3dubG9hZCAkMiAkMw0KfQ0KDQojIyMNCiMjIyB2ZXJpZnlEb3dubG9hZCgpDQojIyMgICAgIHZlcmlmeSB0aGF0IGEgZmlsZSB0aGF0IHdhcyBqdXN0IGRvd25sb2FkIHdpdGggZG93bmxvYWROUjA2DQojIyMgICAgIHdhcyBkb3dubG9hZCBjb3JyZWN0bHkuIFNpbmNlIHdlIGFyZSBnZXR0aW5nIHN0dWZmIGZyb20gYQ0KIyMjICAgICBodHRwIHNlcnZlciB3ZSBhcmUgYXNzdW1pbmcgdGhhdCBpZiB3ZSBnZXQgYSA0MDQgcmVzcG9uc2UNCiMjIyAgICAgdGhhdCB0aGUgcGFnZSB3ZSB3YW50IGRvZXMgbm90IGV4aXN0LiBBbHNvLCBpZiB0aGUgb3V0cHV0IGZpbGUNCiMjIyAgICAgZG9lcyBub3QgZXhpc3QgdGhhdCBtZWFucyB0aGF0IG9ubHkgaGVhZGVycyB3ZXJlIHJldHVybmVkDQojIyMgICAgIHdpdGhvdXQgYW55IGNvbnRlbnQuIHRoaXMgdG9vIGlzIGEgaW52YWxpZCBmaWxlIGRvd25sb2FkDQojIyMNCiMjIyBhcmdzOg0KIyMjICAgICAgIC0gdGhlIGZpbGUgdG8gdmVyaWZ5DQojIyMgICAgICAgLSByZXR1cm4gdmFyaWFibGUsIHdpbGwgaGF2ZSAxIGlmIGZhaWwgMCBpZiBnb29kDQojIyMNCmZ1bmN0aW9uIHZlcmlmeURvd25sb2FkKCl7DQogICAgZXZhbCAiJDI9MCINCiAgICBpZiBbICEgLWYgJDEgXTsNCiAgICB0aGVuDQogICAgICAgIGV2YWwgIiQyPTEiDQogICAgZWxpZiBncmVwICc0MDQgTm90IEZvdW5kJyAkMSA+IC9kZXYvbnVsbA0KICAgIHRoZW4NCglybSAtZiAkMQ0KICAgICAgICBldmFsICIkMj0xIg0KICAgIGVsc2UNCiAgICAgICAgZXZhbCAiJDI9MCINCiAgICBmaQ0KfQ0KDQoNCiMgY2hlY2sgZm9yIHJvb3QgdXNlcg0KaWYgWyAkVUlEIC1uZSAiMCIgXQ0KdGhlbg0KICAgZWNobyAiWW91IG11c3QgcnVuIHRoaXMgcHJvZ3JhbSB3aXRoIHJvb3QgcGVybWlzc2lvbnMuLi4iDQogICBlcnJvcg0KZmkgICANCiANCiMgY2xlYW4gdXAgaW4gY2FzZSB0aGlzIHNjcmlwdCB3YXMgcnVuIGJlZm9yZSBhbmQgZmFpbGVkDQpybSAtcmYgL3RtcC9zdG9yayAmPiAvZGV2L251bGwNCg0KIyBjcmVhdGUgL3RtcC9zdG9yayBkaXJlY3RvcnkNCm1rZGlyIC90bXAvc3RvcmsgDQppZiBbICQ/IC1uZSAiMCIgXQ0KdGhlbg0KICAgZWNobw0KICAgZWNobyAiQ291bGQgbm90IGNyZWF0ZSB0aGUgL3RtcC9zdG9yayBkaXJlY3RvcnkuLi4iDQogICBlcnJvcg0KZmkNCg0KIyBleHBvcnQgb3VyIHJvb3QgZGlyZWN0b3J5IHRvIFN0b3JrDQplY2hvICJhcml6b25hX3N0b3JrMiIgPiAvLmV4cG9ydGRpcg0KaWYgWyAkPyAtbmUgIjAiIF0NCnRoZW4NCiAgIGVjaG8NCiAgIGVjaG8gIkNvdWxkIG5vdCBjcmVhdGUgdGhlIC8uZXhwb3J0ZGlyIGZpbGUuLi4iDQogICBlcnJvcg0KZmkNCiANCiMgdGVsbCBzdG9yayB0aGF0IHdlIHdhbnQgdG8gYmUgc2VydmVkDQppZiBbIC1mIC9ldGMvc2xpY2VuYW1lIF0NCnRoZW4NCiAgIFNMSUNFTkFNRT1gY2F0IC9ldGMvc2xpY2VuYW1lYA0KZWxzZSANCiAgIFNMSUNFTkFNRT0kVVNFUg0KZmkNCndnZXQgLU8gL3RtcC9zdG9yay8kU0xJQ0VOQU1FICJodHRwOi8vbG9jYWxob3N0OjY0OC8kU0xJQ0VOQU1FXCRiaW5kc2NyaXB0Ig0KDQojIHZlcmlmeSB0aGF0IHRoZSBkb3dubG9hZCB3YXMgc3VjY2Vzc2Z1bA0KaWYgWyAhIC1mIC90bXAvc3RvcmsvJFNMSUNFTkFNRSAtbyAkPyAtbmUgMCBdDQp0aGVuDQogICBlY2hvDQogICBlY2hvICJTdG9yayBkb2Vzbid0IHNlZW0gdG8gYmUgcnVubmluZyBvbiB0aGlzIG5vZGUuLi4iDQogICBlcnJvcg0KZmkNCg0KIyB3YWl0IGZvciBzdG9yayBzbGljZSANCmVjaG8gIldhaXRpbmcgZm9yIFN0b3JrIHRvIGFjY2VwdCBvdXIgYmluZGluZy4uLiINCndoaWxlIFsgISAtZiAvdG1wL3N0b3JrL3N0b3JrX3NheXNfZ28gXQ0KZG8NCiAgIHNsZWVwIDENCmRvbmUNCg0KIyBjaGFuZ2UgUFdEIHRvIHRoZSAvdG1wL3N0b3JrIGRpcmVjdG9yeSANCmNkIC90bXAvc3RvcmsNCmlmIFsgJD8gLW5lICIwIiBdDQp0aGVuDQogICBlY2hvDQogICBlY2hvICJDb3VsZCBub3QgYWNjZXNzIHRoZSAvdG1wL3N0b3JrIGRpcmVjdG9yeS4uLiINCiAgIGVycm9yDQpmaQ0KDQojIGNvbmZpcm0gdGhhdCBwYWNrYWdlcyB0byBiZSBpbnN0YWxsZWQgYWN0dWFsbHkgZXhpc3QNCmlmIGVjaG8gKi5ycG0gfCBncmVwICcqJyA+IC9kZXYvbnVsbA0KdGhlbg0KICAgZWNobw0KICAgZWNobyAiRXJyb3I6IFN0b3JrIHBhY2thZ2UgZG93bmxvYWQgZmFpbGVkLi4uIg0KICAgZXJyb3INCmZpDQoNCiMgcmVtb3ZlIFN0b3JrIHBhY2thZ2VzIGFuZCBmaWxlcw0KZWNobw0KZWNobyAiUmVtb3ZpbmcgU3RvcmsgZmlsZXMuLi4iDQoNCiMgYnVpbGQgYSBsaXN0IG9mIHBhY2thZ2VzIHRvIHJlbW92ZQ0KcGFja2FnZXM9IiINCmZvciBmaWxlbmFtZSBpbiAqLnJwbQ0KZG8NCiAgIyBjb252ZXJ0IGZpbGVuYW1lIHRvIGEgcGFja2FnZSBuYW1lDQogIHBhY2s9YHJwbSAtcXAgLS1xZiAiJXtOQU1FfVxuIiAkZmlsZW5hbWVgDQogIGlmIFsgJD8gLWVxICIwIiBdDQogIHRoZW4NCiAgICBwYWNrYWdlcz0iJHBhY2thZ2VzICRwYWNrIg0KICBmaQ0KZG9uZSAgIA0KDQojIHJlbW92ZSBvbGQgU3RvcmsgcGFja2FnZXMNCnJwbSAtZSAkcGFja2FnZXMgJj4gL2Rldi9udWxsDQoNCiMgcmVtb3ZlIGFueXRoaW5nIGxlZnQgaW4gL3Vzci9sb2NhbC9zdG9yay9iaW4NCnJtIC1yZiAvdXNyL2xvY2FsL3N0b3JrL2Jpbi8qICY+IC9kZXYvbnVsbCANCg0KIyBpbnN0YWxsIFN0b3JrIHBhY2thZ2VzDQplY2hvDQplY2hvICJJbnN0YWxsaW5nIHBhY2thZ2VzLi4uIiANCg0KIyBidWlsZCBhIGxpc3Qgb2YgcGFja2FnZXMgdG8gaW5zdGFsbA0KcGFja2FnZXM9IiINCmZvciBmaWxlbmFtZSBpbiAqLnJwbQ0KZG8NCiAgcGFja2FnZXM9IiRwYWNrYWdlcyAkZmlsZW5hbWUiDQpkb25lICAgDQoNCiMgaW5zdGFsbCB0aGUgbmV3IHN0b3JrIHBhY2thZ2VzDQpycG0gLWkgJHBhY2thZ2VzDQoNCiMgcmVwb3J0IHBhY2thZ2UgaW5zdGFsbGF0aW9uIGVycm9ycw0KaWYgWyAkPyAtbmUgIjAiIF0NCnRoZW4NCiAgZWNobyAiV2FybmluZzogUG9zc2libGUgZXJyb3IgaW5zdGFsbGluZyBTdG9yayBwYWNrYWdlcy4uLiINCmZpDQoNCiMgcmVzdG9yZSBvcmlnaW5hbCBQV0QNCmNkICRPTERQV0QNCg0KIyBjbGVhbiB1cCB0ZW1wb3JhcnkgZmlsZXMNCnJtIC1yZiAvdG1wL3N0b3JrICY+IC9kZXYvbnVsbA0KDQojIFNFRSBUTy1ETyAxDQojY3JlYXRlIHRoZSBlcXVpZmF4IGNlcnRpZmljYXRlIHRvIHVzZSBmb3IgY3VybA0KI2NyZWF0ZUNlcnRpZmljYXRlDQoNCiMgVE8tRE8gMQ0KIyBpbXBsZW1lbnQgdGhlIGJlbG93IGluIHRoZSBiZWdnaW5pbmcgb2Ygc3RvcmsucHkNCiNhdHRlbXB0IHRvIGRvd25sb2FkIHRoZSB1c2VycyBwdWJsaWMga2V5IGZyb20gdGhlIHJlcG9zaXRvcnkNCiNkb3dubG9hZE5SMDYgInVzZXItdXBsb2FkL3B1YmtleXMvJFNMSUNFTkFNRS5wdWJsaWNrZXkiICIvdXNyL2xvY2FsL3N0b3JrL3Zhci8kU0xJQ0VOQU1FLnB1YmxpY2tleSIgUkVUDQoNCiNpZiBbICRSRVQgLW5lIDAgXTsNCiN0aGVuDQojICAgZWNobw0KIyAgIGVjaG8gIkNvdWxkIG5vdCBmZXRjaCB5b3VyIHB1YmxpYyBrZXkgZnJvbSB0aGUgcmVwb3NpdG9yeS4iDQojICAgZWNobyAiSWYgeW91IHdhbnQgdG8gdXBsb2FkIG9uZSBmb3IgdGhlIG5leHQgdGltZSB5b3UgcnVuIg0KIyAgIGVjaG8gInRoZSBpbml0c2NyaXB0IHBsZWFzZSB2aXNpdCINCiMgICBlY2hvICJodHRwOi8vbnIwNi5jcy5hcml6b25hLmVkdS90ZXN0cGhwL3VwbG9hZC5waHAiDQojICAgZWNobw0KI2ZpDQoNCiNhdHRlbXB0IHRvIGRvd25sb2FkIHRoZSB1c2VycyBzdG9yay5jb25mIGZpbGUgZnJvbSB0aGUgcmVwb3NpdG9yeQ0KI2Rvd25sb2FkTlIwNiAidXNlci11cGxvYWQvY29uZi8kU0xJQ0VOQU1FLnN0b3JrLmNvbmYiICIvdXNyL2xvY2FsL3N0b3JrL2V0Yy9zdG9yay5jb25mLnVzZXJzIiBSRVQNCg0KI2lmIFsgJFJFVCAtbmUgMCBdOw0KI3RoZW4NCiMgICBlY2hvDQojICAgZWNobyAiQ291bGQgbm90IGZldGNoIHlvdXIgc3RvcmsuY29uZiBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkuIg0KIyAgIGVjaG8gIklmIHlvdSB3YW50IHRvIHVwbG9hZCBvbmUgZm9yIHRoZSBuZXh0IHRpbWUgeW91IHJ1biINCiMgICBlY2hvICJ0aGUgaW5pdHNjcmlwdCBwbGVhc2UgdmlzaXQiDQojICAgZWNobyAiaHR0cDovL25yMDYuY3MuYXJpem9uYS5lZHUvdGVzdHBocC91cGxvYWQucGhwIg0KIyAgIGVjaG8gIlN0b3JrIHdpbGwgd29yayB3aXRob3V0IGEgY29uZmlndXJhdGlvbiBmaWxlIGJ1dCB0byBtYWtlIG9uZSINCiMgICBlY2hvICJwbGVhc2UgcGxhY2UgYSBmaWxlIG5hbWVkIHN0b3JrLmNvbmYgaW4gL3Vzci9sb2NhbC9zdG9yay9ldGMiDQojICAgZWNobyAicmVmZXIgdG8gdGhlIG1hbnVhbCBmb3IgbW9yZSBkaXJlY3Rpb25zIG9yIGVtYWlsOiINCiMgICBlY2hvICJzdG9yay1zdXBwb3J0QGNzLmFyaXpvbmEuZWR1IGZvciBhZGRpdGlvbmFsIGFzc2lzdGFuY2UuIg0KIyAgIGVjaG8NCiNmaQ0KDQojZG9udCBuZWVkIHRvIG92ZXJ3cml0ZSB0aGUgZGVmYXVsdCBjb25mIGZpbGUNCiNiZWNhdXNlIGl0IHNob3VsZCBiZSBmaXhlZCBpbiB0aGUgbmV3IHJwbXMNCiNvdmVyV3JpdGVDb25mDQoNCiMgcnVuIHN0b3JrIHRvIHVwZGF0ZSBrZXlmaWxlcyBhbmQgZG93bmxvYWQgcGFja2FnZSBsaXN0cw0KZWNobw0KZWNobyAiQXR0ZW1wdGluZyB0byBjb21tdW5pY2F0ZSB3aXRoIHN0b3JrLi4uIg0KaWYgc3RvcmsgDQp0aGVuDQogICBlY2hvDQogICBlY2hvICJDb25ncmF0dWxhdGlvbnMsIHlvdSBoYXZlIHN1Y2Nlc3NmdWxseSBib3VuZCB0byBzdG9yayEiDQogICBlY2hvDQogICBlY2hvICJGb3IgaGVscCwgeW91IG1heSB0eXBlIHN0b3JrIC0taGVscCINCiAgIGVjaG8NCiAgICNlY2hvICJUaGVyZSBpcyBhbHNvIGEgc3RvcmtxdWVyeSBjb21tYW5kIHRoYXQgd2lsbCBwcm92aWRlIGluZm9ybWF0aW9uIg0KICAgI2VjaG8gImFib3V0IHBhY2thZ2VzIGluIHRoZSByZXBvc2l0b3J5LiINCiAgIGVjaG8NCiAgIGVjaG8gIkZvciBtb3JlIGhlbHAsIHZpc2l0IHRoZSBzdG9yayBwcm9qZWN0IG9ubGluZSBhdCINCiAgIGVjaG8gImh0dHA6Ly93d3cuY3MuYXJpem9uYS5lZHUvc3RvcmsvLiAgUGxlYXNlIGNvbnRhY3QiDQogICBlY2hvICJzdG9yay1zdXBwb3J0QGNzLmFyaXpvbmEuZWR1IGZvciBhZGRpdGlvbmFsIGFzc2lzdGFuY2UuIiANCiAgICNybSAtZiAkQ0VSVCA+IC9kZXYvbnVsbA0KZWxzZQ0KICAgZWNobw0KICAgZWNobyAiQW4gZXJyb3Igb2NjdXJyZWQgZHVyaW5nIGluc3RhbGwgZmluYWxpemF0aW9uLi4uICBQbGVhc2UgY29udGFjdCINCiAgIGVjaG8gInN0b3JrLXN1cHBvcnRAY3MuYXJpem9uYS5lZHUgZm9yIGFzc2lzdGFuY2UuIg0KICAgI3JtIC1mICRDRVJUID4gL2Rldi9udWxsDQogICBleGl0IDENCmZpDQoNCiMgZG9uZQ0KZXhpdCAwDQo=', 'name': 'arizona_stork_2', 'encoding': 'base64'}, {'initscript_id': 9, 'script': 'IyEvYmluL2Jhc2gNCmNkIC8NCnJtIC1mIHN0YXJ0X3B1cnBsZQ0Kd2dldCBodHRwOi8vd3d3LmNzLnByaW5jZXRvbi5lZHUvfmRlaXNlbnN0L3B1cnBsZS9zdGFydF9wdXJwbGUNCmNobW9kIDc1NSBzdGFydF9wdXJwbGUNCnN1IHByaW5jZXRvbl9wdXJwbGUgLWMgJy4vc3RhcnRfcHVycGxlJw0K', 'name': 'princeton_purple', 'encoding': 'base64'}, {'initscript_id': 6, 'script': 'IyEgL2Jpbi9zaA0KDQojIHNhdmUgb3JpZ2luYWwgUFdEDQpPTERQV0Q9JFBXRA0KDQojIGVycm9yIHJlcG9ydGluZyBmdW5jdGlvbg0KZXJyb3IoKQ0Kew0KICAgZWNobw0KICAgZWNobyAiUGxlYXNlIEUtbWFpbCBzdG9yay1zdXBwb3J0QGNzLmFyaXpvbmEuZWR1IGlmIHlvdSBiZWxpZXZlIHlvdSBoYXZlIiANCiAgIGVjaG8gInJlY2VpdmVkIHRoaXMgbWVzc2FnZSBpbiBlcnJvci4iDQoNCiAgICMgcmVzdG9yZSBvcmlnaW5hbCBQV0QNCiAgIGNkICRPTERQV0QNCiAgIGV4aXQgMQ0KfQ0KDQojIGNoZWNrIGZvciByb290IHVzZXINCmlmIFsgJFVJRCAtbmUgIjAiIF0NCnRoZW4NCiAgIGVjaG8gJ1lvdSBtdXN0IGJlIHJvb3QgdG8gcnVuIHRoaXMgcHJvZ3JhbS4uLicNCiAgIGVycm9yDQpmaSAgIA0KIA0KIyBDbGVhbiB1cCBpbiBjYXNlIEkgcmFuIHRoaXMgYmVmb3JlDQpybSAtZiAvdG1wL3N0b3JrKiA+IC9kZXYvbnVsbCAyPiYxDQoNCiMgRmlyc3Qgb2YgYWxsIGV4cG9ydCBvdXIgcm9vdCBkaXJlY3RvcnkgdG8gU3RvcmsNCmVjaG8gImFyaXpvbmFfc3RvcmsiID4gLy5leHBvcnRkaXINCiANCiMgTm93IHRlbGwgc3RvcmsgdGhhdCB3ZSB3YW50IHRvIGJlIHNlcnZlZA0KaWYgWyAtZiAvZXRjL3NsaWNlbmFtZSBdDQp0aGVuDQogICBTTElDRU5BTUU9YGNhdCAvZXRjL3NsaWNlbmFtZWANCmVsc2UgDQogICBTTElDRU5BTUU9JFVTRVINCmZpDQoNCndnZXQgaHR0cDovL2xvY2FsaG9zdDo2NDAvJFNMSUNFTkFNRQ0KDQojIGNoZWNrIHRvIG1ha2Ugc3VyZSB0aGUgZG93bmxvYWQgd2FzIHN1Y2Nlc3NmdWwNCmlmIFsgISAtZiAkU0xJQ0VOQU1FIC1vICQ/IC1uZSAwIF0NCnRoZW4NCiAgIGVjaG8NCiAgIGVjaG8gIlN0b3JrIGRvZXNuJ3Qgc2VlbSB0byBiZSBydW5uaW5nIG9uIHRoaXMgbm9kZS4uLiINCiAgIGVycm9yDQpmaQ0KDQojIHdhaXQgZm9yIHN0b3JrIHNsaWNlIA0KZWNobyAiV2FpdGluZyBmb3IgU3RvcmsgdG8gYWNjZXB0IG91ciBiaW5kaW5nLi4uIg0Kd2hpbGUgWyAhIC1mIC90bXAvc3Rvcmtfc2F5c19nbyBdDQpkbw0KICAgc2xlZXAgMQ0KZG9uZQ0KDQojIGNoYW5nZSBQV0QgdG8gdGhlIC90bXAgZGlyZWN0b3J5IA0KY2QgL3RtcA0KaWYgWyAkPyAtbmUgIjAiIF0NCnRoZW4NCiAgIGVjaG8NCiAgIGVjaG8gIkNvdWxkIG5vdCBhY2Nlc3MgdGhlIC90bXAgZGlyZWN0b3J5Li4uIg0KICAgZXJyb3INCmZpDQoNCiMgY29uZmlybSB0aGF0IHBhY2thZ2VzIHRvIGJlIGluc3RhbGxlZCBhY3R1YWxseSBleGlzdA0KaWYgZWNobyAqLnJwbSB8IGdyZXAgJyonID4gL2Rldi9udWxsDQp0aGVuDQogICBlY2hvDQogICBlY2hvICJFcnJvcjogU3RvcmsgcGFja2FnZSBkb3dubG9hZCBmYWlsZWQuLi4iDQogICBlcnJvcg0KZmkNCg0KIyBpbnN0YWxsIFN0b3JrIHBhY2thZ2VzDQplY2hvICJJbnN0YWxsaW5nIHBhY2thZ2VzLi4uIiANCmZvciBwYWNrIGluICoucnBtDQpkbw0KICAgIyByZW1vdmUgdGhlIG9sZCBzdG9yayBwYWNrYWdlLCBpZiBhbnkNCiAgIHJwbSAtZSBgcnBtIC1xcCAtLXFmICIle05BTUV9XG4iICRwYWNrYCA+IC9kZXYvbnVsbCAyPiYxDQoNCiAgICMgcmVtb3ZlIGFueXRoaW5nIGxlZnQgaW4gL3Vzci9sb2NhbC9zdG9yay9iaW4NCiAgIHJtIC1yZiAvdXNyL2xvY2FsL3N0b3JrL2Jpbi8qID4gL2Rldi9udWxsIDI+JjENCg0KICAgIyBpbnN0YWxsIHRoZSBuZXcgc3RvcmsgcGFja2FnZQ0KICAgcnBtIC1pICRwYWNrDQoNCiAgICMgcmVwb3J0IHBhY2thZ2UgaW5zdGFsbGF0aW9uIGVycm9ycw0KICAgaWYgWyAkPyAtbmUgIjAiIF0NCiAgIHRoZW4NCiAgICAgZWNobyAiV2FybmluZzogUG9zc2libGUgZXJyb3IgaW5zdGFsbGluZyBTdG9yayBwYWNrYWdlOiAkcGFjay4uLiINCiAgIGZpDQpkb25lDQoNCiMgcmVzdG9yZSBvcmlnaW5hbCBQV0QNCmNkICRPTERQV0QNCg0KIyBjbGVhbiB1cCB0ZW1wb3JhcnkgZmlsZXMNCnJtIC1mIC90bXAvc3RvcmsqID4gL2Rldi9udWxsIDI+JjENCnJtICRTTElDRU5BTUUqIA0KDQojIHJ1biBzdG9yayB0byB1cGRhdGUga2V5ZmlsZXMgYW5kIGRvd25sb2FkIHBhY2thZ2UgbGlzdHMNCmVjaG8gIkF0dGVtcHRpbmcgdG8gY29tbXVuaWNhdGUgd2l0aCBzdG9yay4uLiINCmlmIHN0b3JrIA0KdGhlbg0KICAgZWNobw0KICAgZWNobyAiQ29uZ3JhdHVsYXRpb25zLCB5b3UgaGF2ZSBzdWNjZXNzZnVsbHkgYm91bmQgdG8gc3RvcmshIg0KICAgZWNobw0KICAgZWNobyAiRm9yIGhlbHAsIHlvdSBtYXkgdHlwZSBzdG9yayAtLWhlbHAgIg0KICAgZWNobw0KICAgZWNobyAiVGhlcmUgaXMgYWxzbyBhIHN0b3JrcXVlcnkgY29tbWFuZCB0aGF0IHdpbGwgcHJvdmlkZSBpbmZvcm1hdGlvbiINCiAgIGVjaG8gImFib3V0IHBhY2thZ2VzIGluIHRoZSByZXBvc2l0b3J5LiINCiAgIGVjaG8NCiAgIGVjaG8gIkZvciBtb3JlIGhlbHAsIHZpc2l0IHRoZSBzdG9yayBwcm9qZWN0IG9ubGluZSBhdCINCiAgIGVjaG8gImh0dHA6Ly93d3cuY3MuYXJpem9uYS5lZHUvc3RvcmsvLiAgUGxlYXNlIGNvbnRhY3QiDQogICBlY2hvICJzdG9yay1zdXBwb3J0QGNzLmFyaXpvbmEuZWR1IGZvciBhZGRpdGlvbmFsIGFzc2lzdGFuY2UuIiANCmVsc2UNCiAgIGVjaG8NCiAgIGVjaG8gIkFuIGVycm9yIG9jY3VycmVkIGR1cmluZyBpbnN0YWxsIGZpbmFsaXphdGlvbi4uLiAgUGxlYXNlIGNvbnRhY3QiDQogICBlY2hvICJzdG9yay1zdXBwb3J0QGNzLmFyaXpvbmEuZWR1IGZvciBhc3Npc3RhbmNlLiINCiAgIGV4aXQgMQ0KZmkNCg0KIw0KIyBIZWxsbyBXb3JsZCBkZW1vIGNvZGUNCiMNCg0KIyBQdWJsaWMga2V5IGZvciB0aGlzIGRlbW8NCmNhdCA+L3Vzci9sb2NhbC9zdG9yay92YXIva2V5cy9oZWxsby5wdWJsaWNrZXkgPDwiRU9GIg0KLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1Gd3dEUVlKS29aSWh2Y05BUUVCQlFBRFN3QXdTQUpCQU1XcVE3K2VxQVljNlRPSUJPbkJyRnZqYjlnRVViaWgNCkkxd0Nyeld4a09aa01BcXFmY1RuMW9tcCtLMGd0cUtBK3VaNEIzRGlQRXI0Q0V0Myt5MmJlMGtDQXdFQUFRPT0NCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ0KRU9GDQpzZWQgLWkgLWUgJ3MvXnVzZXJuYW1lLiovdXNlcm5hbWUgPSBoZWxsby8nIC91c3IvbG9jYWwvc3RvcmsvZXRjL3N0b3JrLmNvbmYNCg0KIyBJbnN0YWxsIFJQTQ0Kc3RvcmsgdXBncmFkZSBoZWxsbw0KDQojIGVuZA0KZXhpdCAwDQo=', 'name': 'princeton_hello_stork', 'encoding': 'base64'}, {'initscript_id': 10, 'script': 'IyEvYmluL2Jhc2gNCg0KIyBJbml0IHNjcmlwdCBmb3IgdGhlIFBsYW5ldExhYiAiSGVsbG8gV29ybGQiIGRlbW8gdXNpbmcgR29vZ2xlIEVhcnRoLg0KIyBJbnN0YWxscyBhIGNyb250YWIgZW50cnkgb24gdGhlIG5vZGUgdGhhdCBwaG9uZXMgaG9tZSB0byB0aGUgc2VydmVyDQojIGV2ZXJ5IHRocmVlIG1pbnV0ZXMuDQoNClNFUlZFUj0xMjguMTEyLjEzOS43Mzo4MDQyCQkjIHBsYW5ldGxhYi0zLmNzLnByaW5jZXRvbi5lZHUNCg0KL3Vzci9iaW4vY3VybCAtcyBodHRwOi8vJFNFUlZFUi8NCmVjaG8gIiovNSAqICogKiAqIC91c3IvYmluL2N1cmwgLXMgaHR0cDovLyRTRVJWRVIvIiB8IGNyb250YWIgLQ0KL3NiaW4vY2hrY29uZmlnIGNyb25kIG9uDQo=', 'name': 'princeton_hello', 'encoding': 'base64'}]])
 
 # Convert plc_initscript.initscript_id to raw initscript attribute
-for slice_attribute in GetSliceAttributes({'name': 'plc_initscript'}):
+for slice_attribute in GetSliceTags({'name': 'plc_initscript'}):
     id = slice_attribute['slice_attribute_id']
     slice_id = slice_attribute['slice_id']
     initscript_id = int(slice_attribute['value'])
 
     # Delete old attribute
-    DeleteSliceAttribute(id)
+    DeleteSliceTag(id)
 
     if initscript_id not in initscripts:
         print "Warning: Missing initscript %d" % initscript_id
@@ -231,7 +232,7 @@ for slice_attribute in GetSliceAttributes({'name': 'plc_initscript'}):
     initscript = base64.b64decode(initscripts[initscript_id]['script'])
 
     # Add as initscript attribute
-    AddSliceAttribute(slice_id, 'initscript', initscript)
+    AddSliceTag(slice_id, 'initscript', initscript)
 
 # Add our custom yum.conf entries
 conf_file_id = AddConfFile({
diff --git a/wsdl/Makefile b/wsdl/Makefile
new file mode 100644 (file)
index 0000000..0a3ec4c
--- /dev/null
@@ -0,0 +1,11 @@
+# Build a WSDL spec of the API
+
+all: plcapi.wsdl
+
+plcapi.wsdl:
+       PYTHONPATH=../ python api2wsdl.py > $@
+
+clean:
+       rm -f plcapi.wsdl
+
+.PHONY: all clean
diff --git a/wsdl/api2wsdl.py b/wsdl/api2wsdl.py
new file mode 100755 (executable)
index 0000000..36866c9
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+#
+# Sapan Bhatia <sapanb@cs.princeton.edu>
+#
+# Generates a WSDL for plcapi
+# Current limitations:
+# - Invalid for the following reasons 
+# - The types are python types, not WSDL types
+# - I'm not sure of what to do with the auth structure 
+
+import os, sys
+import time
+import pdb
+import xml.dom.minidom
+import xml.dom.ext
+import inspect
+import globals
+
+from PLC.API import PLCAPI
+from PLC.Method import *
+from PLC.Auth import Auth
+from PLC.Parameter import Parameter, Mixed, python_type, xmlrpc_type
+
+
+api = PLCAPI(None)
+
+try:
+    set
+except NameError:
+    from sets import Set
+    set = Set
+
+# Class functions
+
+def param_type(param):
+    if isinstance(param, Mixed) and len(param):
+        subtypes = [param_type(subparam) for subparam in param]
+        return " or ".join(subtypes)
+    elif isinstance(param, (list, tuple, set)) and len(param):
+        return "array of " + " or ".join([param_type(subparam) for subparam in param])
+    else:
+        return xmlrpc_type(python_type(param))
+
+
+def add_wsdl_ports_and_bindings (wsdl):
+    api.all_methods.sort()
+    for method in api.all_methods:
+        # Skip system. methods
+        if "system." in method:
+            continue
+
+        function = api.callable(method)
+
+        # Commented documentation
+        #lines = ["// " + line.strip() for line in function.__doc__.strip().split("\n")]
+        #print "\n".join(lines)
+        #print
+
+        
+        in_el = wsdl.firstChild.appendChild(wsdl.createElement("wsdl:message"))
+        in_el.setAttribute("name", function.name + "_in")
+
+        # Arguments
+
+        if (function.accepts):
+            (min_args, max_args, defaults) = function.args()
+            for (argname,argtype) in zip(max_args,function.accepts):
+                arg_part = in_el.appendChild(wsdl.createElement("wsdl:part"))
+                arg_part.setAttribute("name", argname)
+                arg_part.setAttribute("type", param_type(argtype))
+                
+        # Return type            
+        return_type = function.returns
+        out_el = wsdl.firstChild.appendChild(wsdl.createElement("wsdl:message"))
+        out_el.setAttribute("name", function.name + "_out")
+        ret_part = out_el.appendChild(wsdl.createElement("wsdl:part"))
+        ret_part.setAttribute("name", "returnvalue")
+        ret_part.setAttribute("type", param_type(return_type))
+
+        # Port connecting arguments with return type
+
+        port_el = wsdl.firstChild.appendChild(wsdl.createElement("wsdl:portType"))
+        port_el.setAttribute("name", function.name + "_port")
+        
+        op_el = port_el.appendChild(wsdl.createElement("wsdl:operation"))
+        op_el.setAttribute("name", function.name)
+        op_el.appendChild(wsdl.createElement("wsdl:input")).setAttribute("message","tns:" + function.name + "_in")
+        op_el.appendChild(wsdl.createElement("wsdl:output")).setAttribute("message","tns:" + function.name + "_out")
+
+        # Bindings
+
+        bind_el = wsdl.firstChild.appendChild(wsdl.createElement("wsdl:binding"))
+        bind_el.setAttribute("name", function.name + "_binding")
+        bind_el.setAttribute("type", "tns:" + function.name + "_port")
+        
+        soap_bind = bind_el.appendChild(wsdl.createElement("soap:binding"))
+        soap_bind.setAttribute("style", "rpc")
+        soap_bind.setAttribute("transport","http://schemas.xmlsoap.org/soap/http")
+
+        
+        wsdl_op = bind_el.appendChild(wsdl.createElement("wsdl:operation"))
+        wsdl_op.setAttribute("name", function.name)
+        wsdl_op.appendChild(wsdl.createElement("soap:operation")).setAttribute("soapAction",
+                "urn:" + function.name)
+
+        
+        wsdl_input = wsdl_op.appendChild(wsdl.createElement("wsdl:input"))
+        input_soap_body = wsdl_input.appendChild(wsdl.createElement("soap:body"))
+        input_soap_body.setAttribute("use", "encoded")
+        input_soap_body.setAttribute("namespace", "urn:" + function.name)
+        input_soap_body.setAttribute("encodingStyle","http://schemas.xmlsoap.org/soap/encoding/")
+
+        
+        wsdl_output = wsdl_op.appendChild(wsdl.createElement("wsdl:output"))
+        output_soap_body = wsdl_output.appendChild(wsdl.createElement("soap:body"))
+        output_soap_body.setAttribute("use", "encoded")
+        output_soap_body.setAttribute("namespace", "urn:" + function.name)
+        output_soap_body.setAttribute("encodingStyle","http://schemas.xmlsoap.org/soap/encoding/")
+        
+
+def add_wsdl_service(wsdl):
+    service_el = wsdl.firstChild.appendChild(wsdl.createElement("wsdl:service"))
+    service_el.setAttribute("name", "plc_api_service")
+
+    for method in api.all_methods:
+        name=api.callable(method).name
+        servport_el = service_el.appendChild(wsdl.createElement("wsdl:port"))
+        servport_el.setAttribute("name", name + "_port")
+        servport_el.setAttribute("binding", "tns:" + name + "_binding")
+
+    soapaddress = servport_el.appendChild(wsdl.createElement("soap:address"))
+    soapaddress.setAttribute("location", "%s" % globals.plc_ns)
+
+
+def get_wsdl_definitions():
+    wsdl_text_header = """
+        <wsdl:definitions
+        name="auto_generated"
+        targetNamespace="%s"
+        xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"
+        xmlns:tns="xmlns:%s"
+        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>""" % (globals.plc_ns,globals.plc_ns)
+        
+    wsdl = xml.dom.minidom.parseString(wsdl_text_header)
+
+    return wsdl
+    
+
+wsdl = get_wsdl_definitions()
+add_wsdl_ports_and_bindings(wsdl)
+add_wsdl_service(wsdl)
+
+
+xml.dom.ext.PrettyPrint(wsdl)
+
diff --git a/wsdl/globals.py b/wsdl/globals.py
new file mode 100644 (file)
index 0000000..9b4f5e2
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/python
+
+plc_ns="http://www.planet-lab.org/plcapi.wsdl"