# 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
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
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)
# (*) 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)
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
#
# Copyright (C) 2004-2006 The Trustees of Princeton University
# $Id$
+# $URL$
#
import sys
#
# Copyright (C) 2004-2006 The Trustees of Princeton University
# $Id$
+# $URL$
#
import sys
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)
# 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]
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
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):
"""
--- /dev/null
+# $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
--- /dev/null
+# 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)
# 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
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
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)
# 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)
+
# 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
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)
-
# 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
# 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") ]
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
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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
+# $Id$
+# $URL$
+
from PLC.Faults import *
from PLC.Parameter import Parameter
from PLC.Table import Row, Table
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import crypt
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
# 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)
# 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"
# Copyright (C) 2007 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2004-2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import os
+# $Id$
+# $URL$
import time
import sys
import syslog
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
#
# Copyright (C) 2004-2006 The Trustees of Princeton University
# $Id$
+# $URL$
#
import xmlrpclib
+# $Id$
+# $URL$
from types import StringTypes
try:
set
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),
...}
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 }
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' ] }
* '-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"):
# 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.
'<' : 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"
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['<']:
operator='<='
if modifiers[']']:
operator='>='
- else:
- value = str(api.db.quote(value))
+
+ value = str(api.db.quote(value))
clause = "%s %s %s" % (field, operator, value)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import os
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
-# $Revision: 9423 $
-#
from PLC.Faults import *
from PLC.Parameter import Parameter
from PLC.Filter import Filter
+# $Id$
+# $URL$
#
# Functions for interacting with the initscripts table in the database
#
+++ /dev/null
-#
-# 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)
--- /dev/null
+# $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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
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')"),
'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:
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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
+# $Id$
+# $URL$
import re
from PLC.Faults import *
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
+++ /dev/null
-# 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))
-
+++ /dev/null
-# 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))
-
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
-# 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()
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Parameter import Parameter
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import xmlrpclib
# 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)
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $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
# 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']
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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.
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.
"""
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'
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']})
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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $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):
"""
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])
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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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):
nodegroup_fields = { 'groupname' : groupname,
'tag_type_id' : tag_type['tag_type_id'],
- 'tagvalue' : tagvalue }
+ 'value' : value }
nodegroup = NodeGroup(self.api, nodegroup_fields)
nodegroup.sync()
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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):
"""
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')
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']]
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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):
"""
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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"
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']
--- /dev/null
+# $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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
import time
from PLC.Method import Method
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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):
"""
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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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']
+# $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):
"""
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).
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']
+++ /dev/null
-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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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']
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
# 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:
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import Auth, BootAuth
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Methods.AuthCheck import AuthCheck
class BootCheckAuthentication(AuthCheck):
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import BootAuth
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
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import Auth, BootAuth, SessionAuth
roles = ['node']
accepts = [
- Mixed(BootAuth(), SessionAuth()),
+ Auth(),
Message.fields['message_id'],
Parameter(int, "Notify PIs"),
Parameter(int, "Notify technical contacts"),
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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'):
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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.
"""
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
# 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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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
accepts = [
Auth(),
- InterfaceSetting.fields['interface_setting_id']
+ InterfaceTag.fields['interface_tag_id']
]
returns = Parameter(int, '1 if successful')
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']:
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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']:
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
import time
from PLC.Method import Method
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $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.
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']:
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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
import random
import base64
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:
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
# $Id$
+# $URL$
import random
import base64
import os
import os.path
+import time
from PLC.Faults import *
from PLC.Method import Method
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)
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
(*) 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
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>'
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
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@/"
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"
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:
( 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
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()
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:
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':
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:
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 :
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
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:
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
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()
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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.
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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.
"""
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)
]
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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),
+ }
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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
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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
--- /dev/null
+# $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)]
+# $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
-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.
"""
# 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]
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
# Thierry Parmentelat - INRIA
#
# $Id$
+# $URL$
import time
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):
"""
'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()})
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter
from PLC.Auth import Auth
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
# 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 = []
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Auth import Auth
from PLC.Faults import *
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
import time
from PLC.Method import Method
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
--- /dev/null
+# $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)
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
+# $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
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 \
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
# $Id$
+# $URL$
import time
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
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
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
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
+++ /dev/null
-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"
-
+# $Id$
+# $URL$
import time
from PLC.Faults import *
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()
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:
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.
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'],
'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
'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'],
'key': Key.fields['key']
}],
'attributes': [{
- 'tagname': SliceAttribute.fields['tagname'],
- 'value': SliceAttribute.fields['value']
+ 'tagname': SliceTag.fields['tagname'],
+ 'value': SliceTag.fields['value']
}]
}]
}
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()
'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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import Auth
+# $Id$
+# $URL$
import socket
from PLC.Faults import *
--- /dev/null
+# $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
# Thierry Parmentelat - INRIA
#
# $Id$
+# $URL$
+import os
+import sys
+import fcntl
import time
from PLC.Debug import log
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
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:
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']))
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":
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')
# 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]
# Update peer itself and commit
peer.sync(commit = True)
-
+
return timers
--- /dev/null
+# $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
+# $Id$
+# $URL$
import random
import base64
import time
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
import re
from PLC.Faults import *
+# $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
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'],
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]
+# $Id$
+# $URL$
import os
import sys
from subprocess import Popen, PIPE, call
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
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()
# <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
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)
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Faults import *
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
+# $Id$
+# $URL$
import time
from PLC.Faults import *
+# $Id$
+# $URL$
from PLC.Methods.SliceGetTicket import SliceGetTicket
class SliceTicketGet(SliceGetTicket):
+# $Id$
+# $URL$
import time
from PLC.Faults import *
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Filter import Filter
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $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]
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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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
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']:
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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $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):
"""
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.
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
+# $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):
"""
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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):
"""
accepts = [
Auth(),
NodeTag.fields['node_tag_id'],
- NodeTag.fields['tagvalue']
+ NodeTag.fields['value']
]
returns = Parameter(int, '1 if successful')
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']:
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']]
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
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):
"""
+# $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 \
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']]}
--- /dev/null
+# $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
+# $Id$
+# $URL$
from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
--- /dev/null
+# $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
+# $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):
"""
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
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
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 \
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
+++ /dev/null
-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
--- /dev/null
+# $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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
+# $Id$
+# $URL$
import random
import base64
import time
+## Please use make index to update this file
native_methods = """
AddAddressType
AddAddressTypeToAddress
AddIlink
AddInitScript
AddInterface
-AddInterfaceSetting
+AddInterfaceTag
AddKeyType
AddMessage
AddNetworkMethod
AddNodeGroup
AddNodeTag
AddNodeToPCU
+AddNodeType
AddPCU
AddPCUProtocolType
AddPCUType
AddPeer
AddPerson
AddPersonKey
+AddPersonTag
AddPersonToSite
AddPersonToSlice
AddRole
AddSession
AddSite
AddSiteAddress
+AddSiteTag
AddSlice
-AddSliceAttribute
AddSliceInstantiation
+AddSliceTag
AddSliceToNodes
AddSliceToNodesWhitelist
AddTagType
AuthCheck
+BindObjectToPeer
BlacklistKey
BootCheckAuthentication
BootGetNodeDetails
DeleteIlink
DeleteInitScript
DeleteInterface
-DeleteInterfaceSetting
+DeleteInterfaceTag
DeleteKey
DeleteKeyType
DeleteMessage
DeleteNodeFromPCU
DeleteNodeGroup
DeleteNodeTag
+DeleteNodeType
DeletePCU
DeletePCUProtocolType
DeletePCUType
DeletePerson
DeletePersonFromSite
DeletePersonFromSlice
+DeletePersonTag
DeleteRole
DeleteRoleFromPerson
DeleteSession
DeleteSite
+DeleteSiteTag
DeleteSlice
-DeleteSliceAttribute
DeleteSliceFromNodes
DeleteSliceFromNodesWhitelist
DeleteSliceInstantiation
+DeleteSliceTag
DeleteTagType
GenerateNodeConfFile
GetAddressTypes
GetEvents
GetIlinks
GetInitScripts
-GetInterfaceSettings
+GetInterfaceTags
GetInterfaces
GetKeyTypes
GetKeys
GetMessages
GetNetworkMethods
GetNetworkTypes
+GetNodeFlavour
GetNodeGroups
GetNodeTags
+GetNodeTypes
GetNodes
GetPCUProtocolTypes
GetPCUTypes
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
SliceUserAdd
SliceUserDel
SliceUsersList
+UnBindObjectFromPeer
UpdateAddress
UpdateAddressType
UpdateConfFile
UpdateIlink
UpdateInitScript
UpdateInterface
-UpdateInterfaceSetting
+UpdateInterfaceTag
UpdateKey
UpdateMessage
UpdateNode
UpdatePCUType
UpdatePeer
UpdatePerson
+UpdatePersonTag
UpdateSite
+UpdateSiteTag
UpdateSlice
-UpdateSliceAttribute
+UpdateSliceTag
UpdateTagType
VerifyPerson
system.listMethods
self.name = "system.listMethods"
def call(self):
- return self.api.methods
+ return self.api.all_methods
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
'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"),
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
-# $Revision: 9423 $
-#
from PLC.Faults import *
from PLC.Parameter import Parameter
from PLC.Filter import Filter
'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'],
--- /dev/null
+#
+# 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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
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.
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),
'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"),
'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"))],
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"
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
"""
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:
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)):
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]})
# Mark Huang <mlhuang@cs.princeton.edu>
# Copyright (C) 2006 The Trustees of Princeton University
#
-# $Id:
+# $Id$
+# $URL$
#
from PLC.Faults import *
# Mark Huang <mlhuang@cs.princeton.edu>
# Copyright (C) 2006 The Trustees of Princeton University
#
-# $Id:
+# $Id$
+# $URL$
#
from types import StringTypes
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
+# $Id$
+# $URL$
# Marc E. Fiuczynski <mef@cs.princeton.edu>
# Copyright (C) 2004 The Trustees of Princeton University
#
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import *
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
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
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):
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"),
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 \
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):
"""
'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.
'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):
"""
'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.
'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.
'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.
--- /dev/null
+# $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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
'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"),
'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):
"""
"""
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
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
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
# 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
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
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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import psycopg2
def __init__(self, api):
self.api = api
self.debug = False
+# self.debug = True
self.connection = None
def cursor(self):
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.
# 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):
return '%(' + name + ')' + conversion
- param = classmethod(param)
-
def begin_work(self):
# Implicit in pgdb.connect()
pass
# 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:
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()).
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import os
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
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from types import StringTypes
+# $Id$
+# $URL$
from types import StringTypes
import random
import base64
# Copyright (C) 2005 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import os
--- /dev/null
+# $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)
+# $Id$
+# $URL$
from types import StringTypes
import string
'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 = {
'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):
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)):
+++ /dev/null
-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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from PLC.Faults import *
--- /dev/null
+# $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)
+# $Id$
+# $URL$
from types import StringTypes
import time
import re
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):
"""
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"),
'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),
}
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
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)
# 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:
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):
"""
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:
+# $Id$
+# $URL$
from types import StringTypes, IntType, LongType
import time
import calendar
"""
# 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
# 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):
"""
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:
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
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):
"""
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
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):
"""
+# $Id$
+# $URL$
#
# Thierry Parmentelat - INRIA
#
-# $Revision: 9423 $
-#
from types import StringTypes
from PLC.Faults import *
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),
+## Please use make index to update this file
all = """
API
AddressTypes
GPG
Ilinks
InitScripts
-InterfaceSettings
+InterfaceTags
Interfaces
KeyTypes
Keys
NetworkTypes
NodeGroups
NodeTags
+NodeTypes
Nodes
PCUProtocolTypes
PCUTypes
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()
+# $Id$
+# $URL$
import os
import sys
import pprint
--- /dev/null
+# $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)
+
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
--- /dev/null
+# $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)
# 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
%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
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
%{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
* Fri Oct 27 2006 Mark Huang <mlhuang@CS.Princeton.EDU> -
- Initial build.
-%define module_current_branch 4.2
+%define module_current_branch 4.3
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import os
--- /dev/null
+# -*-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)
+
--- /dev/null
+# -*-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)
+
--- /dev/null
+# -*-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
--- /dev/null
+# -*-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)
--- /dev/null
+# -*-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)
+
--- /dev/null
+# -*-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)
--- /dev/null
+# -*-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)
+
--- /dev/null
+# -*-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)
+
--- /dev/null
+# -*-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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import xml.dom.minidom
#!/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)
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
all: PLCAPI.html
$(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
<?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>
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>
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">
--- /dev/null
+--
+-- 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;
--- /dev/null
+--
+-- 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;
-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
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
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
# 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
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
}
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
}
-- 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
--
----------------------------------------
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;
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;
-- 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;
-- 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
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
----------------------------------------
--- 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_setting
- SET interface_setting_type_id =
- (select new_index from mgn_setting_renumber where old_index=interface_setting_type_id);
+UPDATE interface_tag
+ 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
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;
---------- 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;
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
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';
# 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):
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
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()
# 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
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
all: plc_api.php
// Copyright (C) 2005-2006 The Trustees of Princeton University
//
// $Id$
+// $URL$
//
//
# Copyright (C) 2005 The Trustess of Princeton University
#
# $Id$
+# $URL$
#
import os, sys
#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. */
--
-- 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';
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 (
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
-- 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
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,
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
--------------------------------------------------------------------------------
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
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
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;
--------------------------------------------------------------------------------
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
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
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,
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 (
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
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 (
AS pcu_protocol_type_ids
FROM pcu_types;
+--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW view_events AS
SELECT
events.event_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,
FROM persons
LEFT JOIN peer_person USING (person_id);
+--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW view_peers AS
SELECT
peers.*,
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,
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.*,
AS node_ids
FROM nodegroups INNER JOIN tag_types USING (tag_type_id);
+--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW view_conf_files AS
SELECT
conf_files.*,
AS nodegroup_ids
FROM conf_files;
+--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW view_pcus AS
SELECT
pcus.*,
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,
FROM sites
LEFT JOIN peer_site USING (site_id);
+--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW view_addresses AS
SELECT
addresses.*,
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.*,
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,
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,
-- 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);
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
from distutils.core import setup
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'
+ ]),
+ ])
# Copyright (C) 2006 The Trustees of Princeton University
#
# $Id$
+# $URL$
#
import re
'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']
# 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
'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']
# 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']
# 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',
'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']
# 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
""".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
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
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({
--- /dev/null
+# Build a WSDL spec of the API
+
+all: plcapi.wsdl
+
+plcapi.wsdl:
+ PYTHONPATH=../ python api2wsdl.py > $@
+
+clean:
+ rm -f plcapi.wsdl
+
+.PHONY: all clean
--- /dev/null
+#!/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)
+
--- /dev/null
+#!/usr/bin/python
+
+plc_ns="http://www.planet-lab.org/plcapi.wsdl"