From fcef24e66e137f58d048c54c3c286e574ab0880d Mon Sep 17 00:00:00 2001 From: Planet-Lab Support Date: Thu, 13 Sep 2007 18:19:09 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'planetlab-4_0-branch'. --- PLC/Methods/AddNodeNetworkSetting.py | 89 ++++ PLC/Methods/AddNodeNetworkSettingType.py | 45 ++ PLC/Methods/DeleteNodeNetworkSetting.py | 73 ++++ PLC/Methods/DeleteNodeNetworkSettingType.py | 39 ++ PLC/Methods/GetBootMedium.py | 400 ++++++++++++++++++ PLC/Methods/GetNodeNetworkSettingTypes.py | 33 ++ PLC/Methods/GetNodeNetworkSettings.py | 45 ++ PLC/Methods/GetPlcRelease.py | 57 +++ PLC/Methods/GetSliceKeys.py | 134 ++++++ PLC/Methods/UpdateNodeNetworkSetting.py | 72 ++++ PLC/Methods/UpdateNodeNetworkSettingType.py | 48 +++ migrations/003-down-network-settings.sql | 24 ++ migrations/003-test.py | 60 +++ migrations/003-up-network-settings.sql | 86 ++++ migrations/004-up-fix-site-nodes.sql | 16 + migrations/005-down-import-apr-2007.sql | 112 +++++ migrations/005-up-import-apr-2007.sql | 154 +++++++ .../006-down-slice-attribute-nodegroup.sql | 25 ++ .../006-up-slice-attribute-nodegroup.sql | 29 ++ migrations/007-down-event-objects-view.sql | 11 + migrations/007-up-event-objects-view.sql | 26 ++ migrations/008-down-import-aug-2007.sql | 42 ++ migrations/008-up-import-aug-2007.sql | 64 +++ 23 files changed, 1684 insertions(+) create mode 100644 PLC/Methods/AddNodeNetworkSetting.py create mode 100644 PLC/Methods/AddNodeNetworkSettingType.py create mode 100644 PLC/Methods/DeleteNodeNetworkSetting.py create mode 100644 PLC/Methods/DeleteNodeNetworkSettingType.py create mode 100644 PLC/Methods/GetBootMedium.py create mode 100644 PLC/Methods/GetNodeNetworkSettingTypes.py create mode 100644 PLC/Methods/GetNodeNetworkSettings.py create mode 100644 PLC/Methods/GetPlcRelease.py create mode 100644 PLC/Methods/GetSliceKeys.py create mode 100644 PLC/Methods/UpdateNodeNetworkSetting.py create mode 100644 PLC/Methods/UpdateNodeNetworkSettingType.py create mode 100644 migrations/003-down-network-settings.sql create mode 100755 migrations/003-test.py create mode 100644 migrations/003-up-network-settings.sql create mode 100644 migrations/004-up-fix-site-nodes.sql create mode 100644 migrations/005-down-import-apr-2007.sql create mode 100644 migrations/005-up-import-apr-2007.sql create mode 100644 migrations/006-down-slice-attribute-nodegroup.sql create mode 100644 migrations/006-up-slice-attribute-nodegroup.sql create mode 100644 migrations/007-down-event-objects-view.sql create mode 100644 migrations/007-up-event-objects-view.sql create mode 100644 migrations/008-down-import-aug-2007.sql create mode 100644 migrations/008-up-import-aug-2007.sql diff --git a/PLC/Methods/AddNodeNetworkSetting.py b/PLC/Methods/AddNodeNetworkSetting.py new file mode 100644 index 0000000..b2915a2 --- /dev/null +++ b/PLC/Methods/AddNodeNetworkSetting.py @@ -0,0 +1,89 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 90 $ +# +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Auth import Auth + +from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes +from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings +from PLC.NodeNetworks import NodeNetwork, NodeNetworks + +from PLC.Nodes import Nodes +from PLC.Sites import Sites + +class AddNodeNetworkSetting(Method): + """ + Sets the specified setting for the specified nodenetwork + 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 nodenetwork setting type object. + + Returns the new nodenetwork_setting_id (> 0) if successful, faults + otherwise. + """ + + roles = ['admin', 'pi', 'tech', 'user'] + + accepts = [ + Auth(), + # no other way to refer to a nodenetwork + NodeNetworkSetting.fields['nodenetwork_id'], + Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'], + NodeNetworkSettingType.fields['name']), + NodeNetworkSetting.fields['value'], + ] + + returns = Parameter(int, 'New nodenetwork_setting_id (> 0) if successful') + + object_type = 'NodeNetwork' + + + def call(self, auth, nodenetwork_id, nodenetwork_setting_type_id_or_name, value): + nodenetworks = NodeNetworks(self.api, [nodenetwork_id]) + if not nodenetworks: + raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_id + nodenetwork = nodenetworks[0] + + nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name]) + if not nodenetwork_setting_types: + raise PLCInvalidArgument, "No such nodenetwork setting type %r"%nodenetwork_setting_type_id_or_name + nodenetwork_setting_type = nodenetwork_setting_types[0] + + # checks for existence - does not allow several different settings + conflicts = NodeNetworkSettings(self.api, + {'nodenetwork_id':nodenetwork['nodenetwork_id'], + 'nodenetwork_setting_type_id':nodenetwork_setting_type['nodenetwork_setting_type_id']}) + + if len(conflicts) : + raise PLCInvalidArgument, "Nodenetwork %d already has setting %d"%(nodenetwork['nodenetwork_id'], + nodenetwork_setting_type['nodenetwork_setting_type_id']) + + # check permission : it not admin, is the user affiliated with the right site + if 'admin' not in self.caller['roles']: + # locate node + node = Nodes (self.api,[nodenetwork['node_id']])[0] + # locate site + site = Sites (self.api, [node['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 = nodenetwork_setting_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 nodenetwork setting, requires role %d",required_min_role + + nodenetwork_setting = NodeNetworkSetting(self.api) + nodenetwork_setting['nodenetwork_id'] = nodenetwork['nodenetwork_id'] + nodenetwork_setting['nodenetwork_setting_type_id'] = nodenetwork_setting_type['nodenetwork_setting_type_id'] + nodenetwork_setting['value'] = value + + nodenetwork_setting.sync() + self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']] + + return nodenetwork_setting['nodenetwork_setting_id'] diff --git a/PLC/Methods/AddNodeNetworkSettingType.py b/PLC/Methods/AddNodeNetworkSettingType.py new file mode 100644 index 0000000..8a16112 --- /dev/null +++ b/PLC/Methods/AddNodeNetworkSettingType.py @@ -0,0 +1,45 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 88 $ +# + + +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes +from PLC.Auth import Auth + +can_update = lambda (field, value): field in \ + ['name', 'description', 'category', 'min_role_id'] + +class AddNodeNetworkSettingType(Method): + """ + Adds a new type of nodenetwork setting. + Any fields specified are used, otherwise defaults are used. + + Returns the new nodenetwork_setting_id (> 0) if successful, + faults otherwise. + """ + + roles = ['admin'] + + nodenetwork_setting_type_fields = dict(filter(can_update, NodeNetworkSettingType.fields.items())) + + accepts = [ + Auth(), + nodenetwork_setting_type_fields + ] + + returns = Parameter(int, 'New nodenetwork_setting_id (> 0) if successful') + + + def call(self, auth, nodenetwork_setting_type_fields): + nodenetwork_setting_type_fields = dict(filter(can_update, nodenetwork_setting_type_fields.items())) + nodenetwork_setting_type = NodeNetworkSettingType(self.api, nodenetwork_setting_type_fields) + nodenetwork_setting_type.sync() + + self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']] + + return nodenetwork_setting_type['nodenetwork_setting_type_id'] diff --git a/PLC/Methods/DeleteNodeNetworkSetting.py b/PLC/Methods/DeleteNodeNetworkSetting.py new file mode 100644 index 0000000..34dcda2 --- /dev/null +++ b/PLC/Methods/DeleteNodeNetworkSetting.py @@ -0,0 +1,73 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 90 $ +# + +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Auth import Auth + +from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings +from PLC.NodeNetworks import NodeNetwork, NodeNetworks + +from PLC.Nodes import Node, Nodes +from PLC.Sites import Site, Sites + +class DeleteNodeNetworkSetting(Method): + """ + Deletes the specified nodenetwork setting + + Attributes may require the caller to have a particular role in order + to be deleted, depending on the related nodenetwork setting type. + Admins may delete attributes of any slice or sliver. + + Returns 1 if successful, faults otherwise. + """ + + roles = ['admin', 'pi', 'user'] + + accepts = [ + Auth(), + NodeNetworkSetting.fields['nodenetwork_setting_id'] + ] + + returns = Parameter(int, '1 if successful') + + object_type = 'NodeNetwork' + + + def call(self, auth, nodenetwork_setting_id): + nodenetwork_settings = NodeNetworkSettings(self.api, [nodenetwork_setting_id]) + if not nodenetwork_settings: + raise PLCInvalidArgument, "No such nodenetwork setting %r"%nodenetwork_setting_id + nodenetwork_setting = nodenetwork_settings[0] + + ### reproducing a check from UpdateSliceAttribute, looks dumb though + nodenetworks = NodeNetworks(self.api, [nodenetwork_setting['nodenetwork_id']]) + if not nodenetworks: + raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_setting['nodenetwork_id'] + nodenetwork = nodenetworks[0] + + assert nodenetwork_setting['nodenetwork_setting_id'] in nodenetwork['nodenetwork_setting_ids'] + + # check permission : it not admin, is the user affiliated with the right site + if 'admin' not in self.caller['roles']: + # locate node + node = Nodes (self.api,[nodenetwork['node_id']])[0] + # locate site + site = Sites (self.api, [node['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 = nodenetwork_setting_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 nodenetwork setting, requires role %d",required_min_role + + nodenetwork_setting.delete() + self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']] + + return 1 diff --git a/PLC/Methods/DeleteNodeNetworkSettingType.py b/PLC/Methods/DeleteNodeNetworkSettingType.py new file mode 100644 index 0000000..ec8d2b1 --- /dev/null +++ b/PLC/Methods/DeleteNodeNetworkSettingType.py @@ -0,0 +1,39 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 88 $ +# +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes +from PLC.Auth import Auth + +class DeleteNodeNetworkSettingType(Method): + """ + Deletes the specified nodenetwork setting type. + + Returns 1 if successful, faults otherwise. + """ + + roles = ['admin'] + + accepts = [ + Auth(), + Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'], + NodeNetworkSettingType.fields['name']), + ] + + returns = Parameter(int, '1 if successful') + + + def call(self, auth, nodenetwork_setting_type_id_or_name): + nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name]) + if not nodenetwork_setting_types: + raise PLCInvalidArgument, "No such nodenetwork settnig type" + nodenetwork_setting_type = nodenetwork_setting_types[0] + + nodenetwork_setting_type.delete() + self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']] + + return 1 diff --git a/PLC/Methods/GetBootMedium.py b/PLC/Methods/GetBootMedium.py new file mode 100644 index 0000000..5daac9b --- /dev/null +++ b/PLC/Methods/GetBootMedium.py @@ -0,0 +1,400 @@ +import random +import base64 +import os +import os.path + +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.NodeNetworks import NodeNetwork, NodeNetworks +from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings + +# +# xxx todo +# Thierry on june 5 2007 +# +# it turns out that having either apache (when invoked through xmlrpc) +# or root (when running plcsh directly) run this piece of code is +# problematic. In fact although we try to create intermediate dirs +# with mode 777, what happens is that root's umask in the plc chroot +# jail is set to 0022. +# +# the bottom line is, depending on who (apache or root) runs this for +# the first time, we can access denied issued (when root comes first) +# so probably we'd better implement a scheme where files are stored +# directly under /var/tmp or something +# +# in addition the sequels of a former run (e.g. with a non-empty +# filename) can prevent subsequent runs if the file is not properly +# cleaned up after use, which is generally the case if someone invokes +# this through plcsh and does not clean up +# so maybe a dedicated cleanup method could be useful just in case +# + +# could not define this in the class.. +boot_medium_actions = [ 'node-preview', + 'node-floppy', + 'node-iso', + 'node-usb', + 'generic-iso', + 'generic-usb', + ] + +class GetBootMedium(Method): + """ + This method is a redesign based on former, supposedly dedicated, + AdmGenerateNodeConfFile + + As compared with its ancestor, this method provides a much more detailed + 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 regenerate the node config file for storage on a floppy + that is, exactly what the ancestor method used todo, + including renewing the node's key + (*) or regenerate the config file and bundle it inside an ISO or USB image + (*) 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 + (*) node-preview + (*) node-floppy + (*) node-iso + (*) node-usb + (*) generic-iso + (*) generic-usb + + Apart for the preview mode, this method generates a new node key for the + specified node, effectively invalidating any old boot medium. + + Non-admins can only generate files for nodes at their sites. + + In addition, two return mechanisms are supported. + (*) The default behaviour is that the file's content is returned as a + base64-encoded string. This is how the ancestor method used to work. + To use this method, pass an empty string as the file parameter. + + (*) Or, for efficiency -- this makes sense only when the API is used + by the web pages that run on the same host -- the caller may provide + a filename, in which case the resulting file is stored in that location instead. + The filename argument can use the following markers, that are expanded + within the method + - %d : default root dir (some builtin dedicated area under /var/tmp/) + Using this is recommended, and enforced for non-admin users + - %n : the node's name when this makes sense, or a mktemp-like name when + generic media is requested + - %s : a file suffix appropriate in the context (.txt, .iso or the like) + - %v : the bootcd version string (e.g. 4.0) + - %p : the PLC name + With the file-based return mechanism, the method returns the full pathname + of the result file; it is the caller's responsability to remove + this file after use. + + Security: + When the user's role is not admin, the provided directory *must* be under + the %d area + + Housekeeping: + Whenever needed, the method stores intermediate files in a + private area, typically not located under the web server's + accessible area, and are cleaned up by the method. + + """ + + roles = ['admin', 'pi', 'tech'] + + accepts = [ + Auth(), + Mixed(Node.fields['node_id'], + Node.fields['hostname']), + Parameter (str, "Action mode, expected in " + "|".join(boot_medium_actions)), + Parameter (str, "Empty string for verbatim result, resulting file full path otherwise"), + ] + + returns = Parameter(str, "Node boot medium, either inlined, or filename, depending to the filename parameter") + + BOOTCDDIR = "/usr/share/bootcd/" + BOOTCUSTOM = "/usr/share/bootcd/bootcustom.sh" + GENERICDIR = "/var/www/html/download/" + NODEDIR = "/var/tmp/bootmedium/results" + WORKDIR = "/var/tmp/bootmedium/work" + DEBUG = False + # uncomment this to preserve temporary area and bootcustom logs + #DEBUG = True + + ### returns (host, domain) : + # 'host' : host part of the hostname + # 'domain' : domain part of the hostname + def split_hostname (self, node): + # Split hostname into host and domain parts + parts = node['hostname'].split(".", 1) + if len(parts) < 2: + raise PLCInvalidArgument, "Node hostname %s is invalid"%node['hostname'] + return parts + + # plnode.txt content + def floppy_contents (self, node, renew_key): + + if node['peer_id'] is not None: + raise PLCInvalidArgument, "Not a local node" + + # 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 generate a configuration file for %s"%node['hostname'] + + # Get node networks for this node + primary = None + nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids']) + for nodenetwork in nodenetworks: + if nodenetwork['is_primary']: + primary = nodenetwork + break + if primary is None: + raise PLCInvalidArgument, "No primary network configured on %s"%node['hostname'] + + ( host, domain ) = self.split_hostname (node) + + if renew_key: + # Generate 32 random bytes + bytes = random.sample(xrange(0, 256), 32) + # Base64 encode their string representation + node['key'] = base64.b64encode("".join(map(chr, bytes))) + # XXX Boot Manager cannot handle = in the key + node['key'] = node['key'].replace("=", "") + # Save it + node.sync() + + # Generate node configuration file suitable for BootCD + file = "" + + if renew_key: + file += 'NODE_ID="%d"\n' % node['node_id'] + file += 'NODE_KEY="%s"\n' % node['key'] + + if primary['mac']: + file += 'NET_DEVICE="%s"\n' % primary['mac'].lower() + + file += 'IP_METHOD="%s"\n' % primary['method'] + + if primary['method'] == 'static': + file += 'IP_ADDRESS="%s"\n' % primary['ip'] + file += 'IP_GATEWAY="%s"\n' % primary['gateway'] + file += 'IP_NETMASK="%s"\n' % primary['netmask'] + file += 'IP_NETADDR="%s"\n' % primary['network'] + file += 'IP_BROADCASTADDR="%s"\n' % primary['broadcast'] + file += 'IP_DNS1="%s"\n' % primary['dns1'] + file += 'IP_DNS2="%s"\n' % (primary['dns2'] or "") + + file += 'HOST_NAME="%s"\n' % host + file += 'DOMAIN_NAME="%s"\n' % domain + + # define various nodenetwork settings attached to the primary nodenetwork + settings = NodeNetworkSettings (self.api, {'nodenetwork_id':nodenetwork['nodenetwork_id']}) + + categories = set() + for setting in settings: + if setting['category'] is not None: + categories.add(setting['category']) + + for category in categories: + category_settings = NodeNetworkSettings(self.api,{'nodenetwork_id':nodenetwork['nodenetwork_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']) + + for nodenetwork in nodenetworks: + if nodenetwork['method'] == 'ipmi': + file += 'IPMI_ADDRESS="%s"\n' % nodenetwork['ip'] + if nodenetwork['mac']: + file += 'IPMI_MAC="%s"\n' % nodenetwork['mac'].lower() + break + + return file + + def bootcd_version (self): + try: + f = open (self.BOOTCDDIR + "/build/version.txt") + version=f.readline().strip() + finally: + f.close() + return version + + def cleandir (self,tempdir): + if not self.DEBUG: + os.system("rm -rf %s"%tempdir) + + def call(self, auth, node_id_or_hostname, action, filename): + + ### check action + if action not in boot_medium_actions: + raise PLCInvalidArgument, "Unknown action %s"%action + + ### compute file suffix + if action.find("-iso") >= 0 : + suffix=".iso" + elif action.find("-usb") >= 0: + suffix=".usb" + else: + suffix=".txt" + + ### compute a 8 bytes random number + tempbytes = random.sample (xrange(0,256), 8); + def hexa2 (c): + return chr((c>>4)+65) + chr ((c&16)+65) + temp = "".join(map(hexa2,tempbytes)) + + ### check node if needed + 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 + nodename = temp + + ### handle filename + filename = filename.replace ("%d",self.NODEDIR) + filename = filename.replace ("%n",nodename) + filename = filename.replace ("%s",suffix) + filename = filename.replace ("%p",self.api.config.PLC_NAME) + # only if filename contains "%v", bootcd is maybe not avail ? + if filename.find("%v") >=0: + filename = filename.replace ("%v",self.bootcd_version()) + + ### Check filename location + if filename != '': + if 'admin' not in self.caller['roles']: + if ( filename.index(self.NODEDIR) != 0): + raise PLCInvalidArgument, "File %s not under %s"%(filename,self.NODEDIR) + + ### 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 + if not os.path.exists(os.path.dirname(filename)): + try: + os.makedirs (os.path.dirname(filename),0777) + except: + raise PLCPermissionDenied, "Could not create dir %s"%os.path.dirname(filename) + + + ### generic media + if action == 'generic-iso' or action == 'generic-usb': + # this raises an exception if bootcd is missing + version = self.bootcd_version() + generic_name = "%s-BootCD-%s%s"%(self.api.config.PLC_NAME, + version, + suffix) + generic_path = "%s/%s" % (self.GENERICDIR,generic_name) + + if 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) + else: + ### return the generic medium content as-is, just base64 encoded + return base64.b64encode(file(generic_path).read()) + + ### floppy preview + if action == 'node-preview': + floppy = self.floppy_contents (node,False) + if filename: + try: + file(filename,'w').write(floppy) + except: + raise PLCPermissionDenied, "Could not write into %s"%filename + return filename + else: + return floppy + + if action == 'node-floppy': + floppy = self.floppy_contents (node,True) + 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 + if action == 'node-iso' or action == 'node-usb': + + ### check we've got required material + version = self.bootcd_version() + generic_name = "%s-BootCD-%s%s"%(self.api.config.PLC_NAME, + version, + suffix) + generic_path = "%s/%s" % (self.GENERICDIR,generic_name) + if not os.path.isfile(generic_path): + raise PLCAPIError, "Cannot locate generic medium %s"%generic_path + + if not os.path.isfile(self.BOOTCUSTOM): + raise PLCAPIError, "Cannot locate bootcustom script %s"%self.BOOTCUSTOM + + # need a temporary area + tempdir = "%s/%s"%(self.WORKDIR,nodename) + if not os.path.isdir(tempdir): + try: + os.makedirs(tempdir,0777) + except: + raise PLCPermissionDenied, "Could not create dir %s"%tempdir + + try: + # generate floppy config + floppy = self.floppy_contents(node,True) + # store it + node_floppy = "%s/%s"%(tempdir,nodename) + try: + file(node_floppy,"w").write(floppy) + except: + raise PLCPermissionDenied, "Could not write into %s"%node_floppy + + # invoke bootcustom + bootcustom_command = 'sudo %s -C "%s" "%s" "%s"'%(self.BOOTCUSTOM, + tempdir, + generic_path, + node_floppy) + if self.DEBUG: + print 'bootcustom command:',bootcustom_command + ret=os.system(bootcustom_command) + if ret != 0: + raise PLCPermissionDenied,"bootcustom.sh failed to create node-specific medium" + + node_image = "%s/%s%s"%(tempdir,nodename,suffix) + if not os.path.isfile (node_image): + raise PLCAPIError,"Unexpected location of bootcustom output - %s"%node_image + + # cache result + if filename: + ret=os.system("mv %s %s"%(node_image,filename)) + if ret != 0: + raise PLCAPIError, "Could not move node image %s into %s"%(node_image,filename) + self.cleandir(tempdir) + return filename + else: + result = file(node_image).read() + self.cleandir(tempdir) + return base64.b64encode(result) + except: + self.cleandir(tempdir) + raise + + # we're done here, or we missed something + raise PLCAPIError,'Unhandled action %s'%action + diff --git a/PLC/Methods/GetNodeNetworkSettingTypes.py b/PLC/Methods/GetNodeNetworkSettingTypes.py new file mode 100644 index 0000000..16eec0a --- /dev/null +++ b/PLC/Methods/GetNodeNetworkSettingTypes.py @@ -0,0 +1,33 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 88 $ +# +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Filter import Filter +from PLC.Auth import Auth +from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes + +class GetNodeNetworkSettingTypes(Method): + """ + Returns an array of structs containing details about + nodenetwork setting types. + + The usual filtering scheme applies on this method. + """ + + roles = ['admin', 'pi', 'user', 'tech', 'node'] + + accepts = [ + Auth(), + Mixed([Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'], + NodeNetworkSettingType.fields['name'])], + Filter(NodeNetworkSettingType.fields)), + Parameter([str], "List of fields to return", nullok = True) + ] + + returns = [NodeNetworkSettingType.fields] + + def call(self, auth, nodenetwork_setting_type_filter = None, return_fields = None): + return NodeNetworkSettingTypes(self.api, nodenetwork_setting_type_filter, return_fields) diff --git a/PLC/Methods/GetNodeNetworkSettings.py b/PLC/Methods/GetNodeNetworkSettings.py new file mode 100644 index 0000000..1eca4f4 --- /dev/null +++ b/PLC/Methods/GetNodeNetworkSettings.py @@ -0,0 +1,45 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 88 $ +# +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Filter import Filter +from PLC.Persons import Person, Persons +from PLC.Auth import Auth + +from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings +from PLC.Sites import Site, Sites +from PLC.NodeNetworks import NodeNetwork, NodeNetworks + +class GetNodeNetworkSettings(Method): + """ + Returns an array of structs containing details about + nodenetworks and related settings. + + If nodenetwork_setting_filter is specified and is an array of + nodenetwork setting identifiers, only nodenetwork 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([NodeNetworkSetting.fields['nodenetwork_setting_id']], + Parameter(int,"Nodenetwork setting id"), + Filter(NodeNetworkSetting.fields)), + Parameter([str], "List of fields to return", nullok = True) + ] + + returns = [NodeNetworkSetting.fields] + + + def call(self, auth, nodenetwork_setting_filter = None, return_fields = None): + + nodenetwork_settings = NodeNetworkSettings(self.api, nodenetwork_setting_filter, return_fields) + + return nodenetwork_settings diff --git a/PLC/Methods/GetPlcRelease.py b/PLC/Methods/GetPlcRelease.py new file mode 100644 index 0000000..7df66aa --- /dev/null +++ b/PLC/Methods/GetPlcRelease.py @@ -0,0 +1,57 @@ +from PLC.Method import Method +from PLC.Auth import Auth +from PLC.Faults import * + +import re + +comment_regexp = '\A\s*#.|\A\s*\Z|\Axxxxx' + +regexps = { 'build' : '\A[bB]uild\s+(?P[^:]+)\s*:\s*(?P.*)\Z', + 'tags' : '\A(?P[^:]+)\s*:=\s*(?P.*)\Z'} + +class GetPlcRelease(Method): + """ + Returns various information about the current myplc installation. + """ + + roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous'] + + accepts = [ + Auth(), + ] + + # for now only return /etc/myplc-release verbatim + returns = { 'build' : 'information about the build', + 'tags' : 'describes the codebase location and tags used for building', + 'rpms' : 'details the rpm installed in the myplc chroot jail' } + + def call(self, auth): + + comment_matcher = re.compile(comment_regexp) + + matchers = {} + result = {} + for field in regexps.keys(): + matchers[field] = re.compile(regexps[field]) + result[field]={} + result['rpms']="Not implemented yet" + + try: + release = open('/etc/myplc-release') + for line in release.readlines(): + line=line.strip() + if comment_matcher.match(line): + continue + for field in regexps.keys(): + m=matchers[field].match(line) + if m: + (key,value)=m.groups(['key','value']) + result[field][key]=value + break + else: + if not result.has_key('unexpected'): + result['unexpected']="" + result['unexpected'] += (line+"\n") + except: + raise PLCNotImplemented, 'Cannot open /etc/myplc-release' + return result diff --git a/PLC/Methods/GetSliceKeys.py b/PLC/Methods/GetSliceKeys.py new file mode 100644 index 0000000..4029c83 --- /dev/null +++ b/PLC/Methods/GetSliceKeys.py @@ -0,0 +1,134 @@ +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 Person, Persons +from PLC.Sites import Site, Sites +from PLC.Slices import Slice, Slices +from PLC.Keys import Key, Keys + +class GetSliceKeys(Method): + """ + Returns an array of structs containing public key info for users in + the specified 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. + """ + + roles = ['admin', 'pi', 'user', 'node'] + + accepts = [ + Auth(), + Mixed([Mixed(Slice.fields['slice_id'], + Slice.fields['name'])], + Filter(Slice.fields)), + Parameter([str], "List of fields to return", nullok = True) + ] + + returns = [ + { + 'slice_id': Slice.fields['slice_id'], + 'name': Slice.fields['name'], + 'person_id': Person.fields['person_id'], + 'email': Person.fields['email'], + 'key': Key.fields['key'] + }] + + def call(self, auth, slice_filter = None, return_fields = None): + slice_fields = ['slice_id', 'name'] + person_fields = ['person_id', 'email'] + key_fields = ['key'] + + # If we are not admin, make sure to return only viewable + # slices. + if isinstance(self.caller, Person) and \ + 'admin' not in self.caller['roles']: + # Get slices that we are able to view + valid_slice_ids = self.caller['slice_ids'] + if 'pi' in self.caller['roles'] and self.caller['site_ids']: + sites = Sites(self.api, self.caller['site_ids']) + for site in sites: + valid_slice_ids += site['slice_ids'] + + if not valid_slice_ids: + return [] + + if slice_filter is None: + slice_filter = valid_slice_ids + + if return_fields: + slice_return_fields = filter(lambda field: field in slice_fields, return_fields) + person_return_fields = filter(lambda field: field in person_fields, return_fields) + key_return_fields = filter(lambda field: field in key_fields, return_fields) + else: + slice_return_fields = slice_fields + person_return_fields = person_fields + key_return_fields = key_fields + + # Must query at least Slice.slice_id, Slice.person_ids, + # and Person.person_id and Person.key_ids so we can join data correctly + slice_added_fields = set(['slice_id', 'person_ids']).difference(slice_return_fields) + slice_return_fields += slice_added_fields + person_added_fields = set(['person_id', 'key_ids']).difference(person_return_fields) + person_return_fields += person_added_fields + key_added_fields = set(['key_id']).difference(key_return_fields) + key_return_fields += key_added_fields + + # Get the slices + all_slices = Slices(self.api, slice_filter, slice_return_fields).dict('slice_id') + slice_ids = all_slices.keys() + slices = all_slices.values() + + # Filter out slices that are not viewable + if isinstance(self.caller, Person) and \ + 'admin' not in self.caller['roles']: + slices = filter(lambda slice: slice['slice_id'] in valid_slice_ids, slices) + + # Get the persons + person_ids = set() + for slice in slices: + person_ids.update(slice['person_ids']) + + all_persons = Persons(self.api, list(person_ids), person_return_fields).dict('person_id') + person_ids = all_persons.keys() + persons = all_persons.values() + + # Get the keys + key_ids = set() + for person in persons: + key_ids.update(person['key_ids']) + + all_keys = Keys(self.api, list(key_ids), key_return_fields).dict('key_id') + key_ids = all_keys.keys() + keys = all_keys.values() + + # Create slice_keys list + slice_keys = [] + slice_fields = list(set(slice_return_fields).difference(slice_added_fields)) + person_fields = list(set(person_return_fields).difference(person_added_fields)) + key_fields = list(set(key_return_fields).difference(key_added_fields)) + + for slice in slices: + slice_key = dict.fromkeys(slice_fields + person_fields + key_fields) + if not slice['person_ids']: + continue + for person_id in slice['person_ids']: + person = all_persons[person_id] + if not person['key_ids']: + continue + for key_id in person['key_ids']: + key = all_keys[key_id] + slice_key.update(dict(filter(lambda (k, v): k in slice_fields, slice.items()))) + slice_key.update(dict(filter(lambda (k, v): k in person_fields, person.items()))) + slice_key.update(dict(filter(lambda (k, v): k in key_fields, key.items()))) + slice_keys.append(slice_key.copy()) + + return slice_keys + diff --git a/PLC/Methods/UpdateNodeNetworkSetting.py b/PLC/Methods/UpdateNodeNetworkSetting.py new file mode 100644 index 0000000..f0ed8de --- /dev/null +++ b/PLC/Methods/UpdateNodeNetworkSetting.py @@ -0,0 +1,72 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 90 $ +# + +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Auth import Auth + +from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings +from PLC.NodeNetworks import NodeNetwork, NodeNetworks + +from PLC.Nodes import Nodes +from PLC.Sites import Sites + +class UpdateNodeNetworkSetting(Method): + """ + Updates the value of an existing nodenetwork setting + + Access rights depend on the nodenetwork setting type. + + Returns 1 if successful, faults otherwise. + """ + + roles = ['admin', 'pi', 'tech', 'user'] + + accepts = [ + Auth(), + NodeNetworkSetting.fields['nodenetwork_setting_id'], + NodeNetworkSetting.fields['value'] + ] + + returns = Parameter(int, '1 if successful') + + object_type = 'NodeNetwork' + + def call(self, auth, nodenetwork_setting_id, value): + nodenetwork_settings = NodeNetworkSettings(self.api, [nodenetwork_setting_id]) + if not nodenetwork_settings: + raise PLCInvalidArgument, "No such nodenetwork setting %r"%nodenetwork_setting_id + nodenetwork_setting = nodenetwork_settings[0] + + ### reproducing a check from UpdateSliceAttribute, looks dumb though + nodenetworks = NodeNetworks(self.api, [nodenetwork_setting['nodenetwork_id']]) + if not nodenetworks: + raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_setting['nodenetwork_id'] + nodenetwork = nodenetworks[0] + + assert nodenetwork_setting['nodenetwork_setting_id'] in nodenetwork['nodenetwork_setting_ids'] + + # check permission : it not admin, is the user affiliated with the right site + if 'admin' not in self.caller['roles']: + # locate node + node = Nodes (self.api,[nodenetwork['node_id']])[0] + # locate site + site = Sites (self.api, [node['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 = nodenetwork_setting_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 nodenetwork setting, requires role %d",required_min_role + + nodenetwork_setting['value'] = value + nodenetwork_setting.sync() + + self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']] + return 1 diff --git a/PLC/Methods/UpdateNodeNetworkSettingType.py b/PLC/Methods/UpdateNodeNetworkSettingType.py new file mode 100644 index 0000000..caa0511 --- /dev/null +++ b/PLC/Methods/UpdateNodeNetworkSettingType.py @@ -0,0 +1,48 @@ +# +# Thierry Parmentelat - INRIA +# +# $Revision: 88 $ +# +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes +from PLC.Auth import Auth + +can_update = lambda (field, value): field in \ + ['name', 'description', 'category', 'min_role_id'] + +class UpdateNodeNetworkSettingType(Method): + """ + Updates the parameters of an existing setting type + with the values in nodenetwork_setting_type_fields. + + Returns 1 if successful, faults otherwise. + """ + + roles = ['admin'] + + nodenetwork_setting_type_fields = dict(filter(can_update, NodeNetworkSettingType.fields.items())) + + accepts = [ + Auth(), + Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'], + NodeNetworkSettingType.fields['name']), + nodenetwork_setting_type_fields + ] + + returns = Parameter(int, '1 if successful') + + def call(self, auth, nodenetwork_setting_type_id_or_name, nodenetwork_setting_type_fields): + nodenetwork_setting_type_fields = dict(filter(can_update, nodenetwork_setting_type_fields.items())) + + nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name]) + if not nodenetwork_setting_types: + raise PLCInvalidArgument, "No such setting type" + nodenetwork_setting_type = nodenetwork_setting_types[0] + + nodenetwork_setting_type.update(nodenetwork_setting_type_fields) + nodenetwork_setting_type.sync() + self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']] + + return 1 diff --git a/migrations/003-down-network-settings.sql b/migrations/003-down-network-settings.sql new file mode 100644 index 0000000..796e4b4 --- /dev/null +++ b/migrations/003-down-network-settings.sql @@ -0,0 +1,24 @@ +-- IMPORTANT NOTICE +-- +-- this down script is provided for convenience only +-- DO NOT USE on an operational site +-- the change in migration 003 involves creating +-- the new view view_nodenetworks for fetching instances +-- of NodeNetworks +-- AND to alter NodeNetworks.py accordingly +-- so this change cannot be easily undone +-- unless you also revert the API itself + +DROP VIEW view_nodenetworks; + +DROP VIEW view_nodenetwork_settings; + +DROP VIEW nodenetwork_settings; + +DROP TABLE nodenetwork_setting; + +DROP TABLE nodenetwork_setting_types; + +-- deflate subversion +UPDATE plc_db_version SET subversion = 2; +SELECT subversion from plc_db_version; diff --git a/migrations/003-test.py b/migrations/003-test.py new file mode 100755 index 0000000..aed650b --- /dev/null +++ b/migrations/003-test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env plcsh + +nnst = GetNodeNetworkSettingTypes(['interface_name']) +if nnst: + print 'NodeNetworkSettingType interface_name already defined' +else: + AddNodeNetworkSettingType({ + 'category':'general', + 'min_role_id':30, + 'name':'interface_name', + 'description':'allows to specify a custom interface name' + }) +nnst_ifname_id = GetNodeNetworkSettingTypes(['interface_name'])[0]['nodenetwork_setting_type_id'] + + +nnst = GetNodeNetworkSettingTypes(['ssid']) +if nnst: + print 'NodeNetworkSettingType ssid already defined' +else: + AddNodeNetworkSettingType({ + 'category':'wifi', + 'min_role_id':30, + 'name':'ssid', + 'description':'allows to set ESSID' + }) +nnst_ssid_id = GetNodeNetworkSettingTypes(['ssid'])[0]['nodenetwork_setting_type_id'] + +nodename = 'onelab2.inria.fr' + +nodenetwork_id=GetNodes(nodename)[0]['nodenetwork_ids'][0] + +####### +nns_ifname = GetNodeNetworkSettings ({'nodenetwork_id':nodenetwork_id, + 'nodenetwork_setting_type_id':nnst_ifname_id}) +if nns_ifname: + print "interface name for %s already set (got %s - cat=%s)" %\ + (nodename,nns_ifname[0]['value'],nns_ifname[0]['category']) +else: + AddNodeNetworkSetting(nodenetwork_id, 'interface_name', 'custom-eth0') + +nns_ifname_id = GetNodeNetworkSettings ({'nodenetwork_id':nodenetwork_id, + 'nodenetwork_setting_type_id':nnst_ifname_id})[0]['nodenetwork_setting_id'] +####### +nns_ssid = GetNodeNetworkSettings ({'nodenetwork_id':nodenetwork_id, + 'nodenetwork_setting_type_id':nnst_ssid_id}) +if nns_ssid: + print "ssid for %s already set (got %s - cat=%s)" %\ + (nodename,nns_ifname[0]['value'],nns_ifname[0]['category']) +else: + AddNodeNetworkSetting(nodenetwork_id, 'ssid', 'init-onelab-g') + +nns_ssid_id = GetNodeNetworkSettings ({'nodenetwork_id':nodenetwork_id, + 'nodenetwork_setting_type_id':nnst_ssid_id})[0]['nodenetwork_setting_id'] + +####### + +UpdateNodeNetworkSetting (nns_ssid_id,'onelab-g') + +DeleteNodeNetworkSetting (nns_ifname_id) + diff --git a/migrations/003-up-network-settings.sql b/migrations/003-up-network-settings.sql new file mode 100644 index 0000000..64b0ff6 --- /dev/null +++ b/migrations/003-up-network-settings.sql @@ -0,0 +1,86 @@ +-- +-- Thierry Parmentelat -- INRIA +-- +-- migration 003 +-- +-- purpose : provide a generic mechanism for assigning +-- nodenetworks (read, network interfaces) with +-- custom settings +-- +-- design +-- mimicks the way slice attributes are being handled, +-- not that this design is particularly attractive +-- but let's not add confusion here +-- i.e: +-- (*) nodenetwork_setting_types (see slice_attribute_types) +-- allows to define a new setting +-- e.g, define one such object for storing wifi SSID +-- +-- (*) nodenetwork_setting (see slice_attribute) +-- allow to associate a nodenetwork, a nodenetwork_setting_type, and a value +-- +-- NOTE. with slice_attributes there is a trick that allows to define +-- the attribute either on the slice globally or on a particular node only. +-- of course we do not need such a trick + +CREATE TABLE nodenetwork_setting_types ( + nodenetwork_setting_type_id serial PRIMARY KEY, + -- Setting Type Identifier + name text UNIQUE NOT NULL, -- Setting Name + description text, -- Optional Description + category text NOT NULL, -- Category, e.g. Wifi, or whatever + min_role_id integer references roles -- If set, minimal role required +) WITH OIDS; + +CREATE TABLE nodenetwork_setting ( + nodenetwork_setting_id serial PRIMARY KEY, -- Nodenetwork Setting Identifier + nodenetwork_id integer REFERENCES nodenetworks NOT NULL, + -- the nodenetwork this applies to + nodenetwork_setting_type_id integer REFERENCES nodenetwork_setting_types NOT NULL, + -- the setting type + value text +) WITH OIDS; + + +CREATE OR REPLACE VIEW nodenetwork_settings AS +SELECT nodenetwork_id, +array_accum(nodenetwork_setting_id) AS nodenetwork_setting_ids +FROM nodenetwork_setting +GROUP BY nodenetwork_id; + +CREATE OR REPLACE VIEW view_nodenetwork_settings AS +SELECT +nodenetwork_setting.nodenetwork_setting_id, +nodenetwork_setting.nodenetwork_id, +nodenetwork_setting_types.nodenetwork_setting_type_id, +nodenetwork_setting_types.name, +nodenetwork_setting_types.description, +nodenetwork_setting_types.category, +nodenetwork_setting_types.min_role_id, +nodenetwork_setting.value +FROM nodenetwork_setting +INNER JOIN nodenetwork_setting_types USING (nodenetwork_setting_type_id); + +CREATE VIEW view_nodenetworks AS +SELECT +nodenetworks.nodenetwork_id, +nodenetworks.node_id, +nodenetworks.is_primary, +nodenetworks.type, +nodenetworks.method, +nodenetworks.ip, +nodenetworks.mac, +nodenetworks.gateway, +nodenetworks.network, +nodenetworks.broadcast, +nodenetworks.netmask, +nodenetworks.dns1, +nodenetworks.dns2, +nodenetworks.bwlimit, +nodenetworks.hostname, +COALESCE((SELECT nodenetwork_setting_ids FROM nodenetwork_settings WHERE nodenetwork_settings.nodenetwork_id = nodenetworks.nodenetwork_id), '{}') AS nodenetwork_setting_ids +FROM nodenetworks; + +-- Bump subversion +UPDATE plc_db_version SET subversion = 3; +SELECT subversion from plc_db_version; diff --git a/migrations/004-up-fix-site-nodes.sql b/migrations/004-up-fix-site-nodes.sql new file mode 100644 index 0000000..8bc9c58 --- /dev/null +++ b/migrations/004-up-fix-site-nodes.sql @@ -0,0 +1,16 @@ +-- +-- bugfix +-- the site_nodes should restrict to nodes where deleted is false +-- + +CREATE OR REPLACE VIEW site_nodes AS +SELECT site_id, +array_accum(node_id) AS node_ids +FROM nodes +WHERE deleted is false +GROUP BY site_id; + +-- Bump subversion +UPDATE plc_db_version SET subversion = 4; +SELECT subversion from plc_db_version; + diff --git a/migrations/005-down-import-apr-2007.sql b/migrations/005-down-import-apr-2007.sql new file mode 100644 index 0000000..fa16b12 --- /dev/null +++ b/migrations/005-down-import-apr-2007.sql @@ -0,0 +1,112 @@ +-- revert migration 005 +-- +-- this is a rather complex example, so for next times, make sure that you +-- * first restore old columns or tables +-- * then create or replace views +-- * and only finally drop new columns and tables +-- otherwise the columns may refuse to get dropped if they are still used by views +-- + +---------- creations + +ALTER TABLE events ADD object_type text NOT NULL Default 'Unknown'; + +---------- view changes + +-- for some reason these views require to be dropped first +DROP VIEW view_events; +DROP VIEW event_objects; +DROP VIEW view_nodes; +DROP VIEW view_sites; + +CREATE OR REPLACE VIEW event_objects AS +SELECT event_id, +array_accum(object_id) AS object_ids +FROM event_object +GROUP BY event_id; + +CREATE OR REPLACE VIEW view_events AS +SELECT +events.event_id, +events.person_id, +events.node_id, +events.fault_code, +events.call_name, +events.call, +events.object_type, +events.message, +events.runtime, +CAST(date_part('epoch', events.time) AS bigint) AS time, +COALESCE((SELECT object_ids FROM event_objects WHERE event_objects.event_id = events.event_id), '{}') AS object_ids +FROM events; + +CREATE OR REPLACE VIEW view_nodes AS +SELECT +nodes.node_id, +nodes.hostname, +nodes.site_id, +nodes.boot_state, +nodes.deleted, +nodes.model, +nodes.boot_nonce, +nodes.version, +nodes.ssh_rsa_key, +nodes.key, +CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created, +CAST(date_part('epoch', nodes.last_updated) AS bigint) AS last_updated, +peer_node.peer_id, +peer_node.peer_node_id, +COALESCE((SELECT nodenetwork_ids FROM node_nodenetworks WHERE node_nodenetworks.node_id = nodes.node_id), '{}') AS nodenetwork_ids, +COALESCE((SELECT nodegroup_ids FROM node_nodegroups WHERE node_nodegroups.node_id = nodes.node_id), '{}') AS nodegroup_ids, +COALESCE((SELECT slice_ids FROM node_slices WHERE node_slices.node_id = nodes.node_id), '{}') AS slice_ids, +COALESCE((SELECT pcu_ids FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS pcu_ids, +COALESCE((SELECT ports FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS ports, +COALESCE((SELECT conf_file_ids FROM node_conf_files WHERE node_conf_files.node_id = nodes.node_id), '{}') AS conf_file_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_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, +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 +FROM sites +LEFT JOIN peer_site USING (site_id); + +---------- deletions + +ALTER TABLE sites DROP COLUMN ext_consortium_id; + +ALTER TABLE nodes DROP COLUMN last_contact; + +DROP INDEX initscripts_name_idx; +DROP TABLE initscripts; + +ALTER TABLE events DROP COLUMN auth_type; + +ALTER TABLE event_object DROP COLUMN object_type; + +---------- revert subversion + +UPDATE plc_db_version SET subversion = 4; +SELECT subversion from plc_db_version; diff --git a/migrations/005-up-import-apr-2007.sql b/migrations/005-up-import-apr-2007.sql new file mode 100644 index 0000000..3a34f25 --- /dev/null +++ b/migrations/005-up-import-apr-2007.sql @@ -0,0 +1,154 @@ +-- +-- to apply changes from import done in april 2007 from the +-- planetlab-4_0-branch tag +-- +-- this is a rather complex example, so for next times, make sure that you +-- * first add new columns and new tables +-- * then create or replace views +-- * and only finally drop columns +-- otherwise the columns may refuse to get dropped if they are still used by views +-- + +---------- creations + +ALTER TABLE sites ADD ext_consortium_id integer; + +ALTER TABLE nodes ADD last_contact timestamp without time zone; + +-- Initscripts +CREATE TABLE initscripts ( + initscript_id serial PRIMARY KEY, -- Initscript identifier + name text NOT NULL, -- Initscript name + enabled bool NOT NULL DEFAULT true, -- Initscript is active + script text NOT NULL, -- Initscript + UNIQUE (name) +) WITH OIDS; +CREATE INDEX initscripts_name_idx ON initscripts (name); + +-- rather drop the tables altogether, +-- ALTER TABLE events ADD auth_type text; +-- ALTER TABLE event_object ADD COLUMN object_type text NOT NULL Default 'Unknown'; +-- CREATE INDEX event_object_object_type_idx ON event_object (object_type); + +-- for some reason these views require to be dropped first +DROP VIEW view_events; +DROP VIEW event_objects; +DROP VIEW view_nodes; +DROP VIEW view_sites; + +----dropping tables must be preceded by dropping views using those tables +----otherwise dependency problems +DROP TABLE event_object; +DROP TABLE events; + +CREATE TABLE events ( + event_id serial PRIMARY KEY, -- Event identifier + person_id integer REFERENCES persons, -- Person responsible for event, if any + node_id integer REFERENCES nodes, -- Node responsible for event, if any + auth_type text, -- Type of auth used. i.e. AuthMethod + fault_code integer NOT NULL DEFAULT 0, -- Did this event result in error + call_name text NOT NULL, -- Call responsible for this event + call text NOT NULL, -- Call responsible for this event, including parameters + message text, -- High level description of this event + runtime float DEFAULT 0, -- Event run time + time timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP -- Event timestamp +) WITH OIDS; + +-- Database object(s) that may have been affected by a particular event +CREATE TABLE event_object ( + event_id integer REFERENCES events NOT NULL, -- Event identifier + object_id integer NOT NULL, -- Object identifier + object_type text NOT NULL Default 'Unknown' -- What type of object is this event affecting +) WITH OIDS; +CREATE INDEX event_object_event_id_idx ON event_object (event_id); +CREATE INDEX event_object_object_id_idx ON event_object (object_id); +CREATE INDEX event_object_object_type_idx ON event_object (object_type); + +---------- view changes + +CREATE OR REPLACE VIEW event_objects AS +SELECT event_id, +array_accum(object_id) AS object_ids, +array_accum(object_type) AS object_types +FROM event_object +GROUP BY event_id; + +CREATE OR REPLACE VIEW view_events AS +SELECT +events.event_id, +events.person_id, +events.node_id, +events.auth_type, +events.fault_code, +events.call_name, +events.call, +events.message, +events.runtime, +CAST(date_part('epoch', events.time) AS bigint) AS time, +COALESCE((SELECT object_ids FROM event_objects WHERE event_objects.event_id = events.event_id), '{}') AS object_ids, +COALESCE((SELECT object_types FROM event_objects WHERE event_objects.event_id = events.event_id), '{}') AS object_types +FROM events; + +CREATE OR REPLACE VIEW view_nodes AS +SELECT +nodes.node_id, +nodes.hostname, +nodes.site_id, +nodes.boot_state, +nodes.deleted, +nodes.model, +nodes.boot_nonce, +nodes.version, +nodes.ssh_rsa_key, +nodes.key, +CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created, +CAST(date_part('epoch', nodes.last_updated) AS bigint) AS last_updated, +CAST(date_part('epoch', nodes.last_contact) AS bigint) AS last_contact, +peer_node.peer_id, +peer_node.peer_node_id, +COALESCE((SELECT nodenetwork_ids FROM node_nodenetworks WHERE node_nodenetworks.node_id = nodes.node_id), '{}') AS nodenetwork_ids, +COALESCE((SELECT nodegroup_ids FROM node_nodegroups WHERE node_nodegroups.node_id = nodes.node_id), '{}') AS nodegroup_ids, +COALESCE((SELECT slice_ids FROM node_slices WHERE node_slices.node_id = nodes.node_id), '{}') AS slice_ids, +COALESCE((SELECT pcu_ids FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS pcu_ids, +COALESCE((SELECT ports FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS ports, +COALESCE((SELECT conf_file_ids FROM node_conf_files WHERE node_conf_files.node_id = nodes.node_id), '{}') AS conf_file_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_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 +FROM sites +LEFT JOIN peer_site USING (site_id); + +---------- deletions +--dont need to drop this colum it doesn't exit anymore +-----ALTER TABLE events DROP COLUMN object_type; + +---------- bump subversion + +UPDATE plc_db_version SET subversion = 5; +SELECT subversion from plc_db_version; diff --git a/migrations/006-down-slice-attribute-nodegroup.sql b/migrations/006-down-slice-attribute-nodegroup.sql new file mode 100644 index 0000000..0dc4bae --- /dev/null +++ b/migrations/006-down-slice-attribute-nodegroup.sql @@ -0,0 +1,25 @@ +---------- view changes + +DROP VIEW view_slice_attributes; + +CREATE OR REPLACE VIEW view_slice_attributes AS +SELECT +slice_attribute.slice_attribute_id, +slice_attribute.slice_id, +slice_attribute.node_id, +slice_attribute_types.attribute_type_id, +slice_attribute_types.name, +slice_attribute_types.description, +slice_attribute_types.min_role_id, +slice_attribute.value +FROM slice_attribute +INNER JOIN slice_attribute_types USING (attribute_type_id); + +---------- deletions +DROP INDEX slice_attribute_nodegroup_id_idx; +ALTER TABLE slice_attribute DROP COLUMN nodegroup_id; + +---------- revert subversion + +UPDATE plc_db_version SET subversion = 5; +SELECT subversion from plc_db_version; diff --git a/migrations/006-up-slice-attribute-nodegroup.sql b/migrations/006-up-slice-attribute-nodegroup.sql new file mode 100644 index 0000000..801f2a0 --- /dev/null +++ b/migrations/006-up-slice-attribute-nodegroup.sql @@ -0,0 +1,29 @@ +---------- creations + +ALTER TABLE slice_attribute ADD nodegroup_id integer REFERENCES nodegroups; + +CREATE INDEX slice_attribute_nodegroup_id_idx ON slice_attribute (nodegroup_id); + +---------- view changes + +DROP VIEW view_slice_attributes; + +CREATE OR REPLACE VIEW view_slice_attributes AS +SELECT +slice_attribute.slice_attribute_id, +slice_attribute.slice_id, +slice_attribute.node_id, +slice_attribute.nodegroup_id, +slice_attribute_types.attribute_type_id, +slice_attribute_types.name, +slice_attribute_types.description, +slice_attribute_types.min_role_id, +slice_attribute.value +FROM slice_attribute +INNER JOIN slice_attribute_types USING (attribute_type_id); + + +---------- bump subversion + +UPDATE plc_db_version SET subversion = 6; +SELECT subversion from plc_db_version; diff --git a/migrations/007-down-event-objects-view.sql b/migrations/007-down-event-objects-view.sql new file mode 100644 index 0000000..09b86f3 --- /dev/null +++ b/migrations/007-down-event-objects-view.sql @@ -0,0 +1,11 @@ +-- +-- migration 007 - revert +-- + +DROP VIEW view_event_objects; + +---------- revert subversion + +UPDATE plc_db_version SET subversion = 6; +SELECT subversion from plc_db_version; + diff --git a/migrations/007-up-event-objects-view.sql b/migrations/007-up-event-objects-view.sql new file mode 100644 index 0000000..2ed47ee --- /dev/null +++ b/migrations/007-up-event-objects-view.sql @@ -0,0 +1,26 @@ +-- +-- migration 007 +-- change the way event objects are fetched, use a view for that purpose +-- + + +CREATE OR REPLACE VIEW view_event_objects AS +SELECT +events.event_id, +events.person_id, +events.node_id, +events.fault_code, +events.call_name, +events.call, +events.message, +events.runtime, +CAST(date_part('epoch', events.time) AS bigint) AS time, +event_object.object_id, +event_object.object_type +FROM events LEFT JOIN event_object USING (event_id); + + +---------- bump subversion + +UPDATE plc_db_version SET subversion = 7; +SELECT subversion from plc_db_version; diff --git a/migrations/008-down-import-aug-2007.sql b/migrations/008-down-import-aug-2007.sql new file mode 100644 index 0000000..9a3eaab --- /dev/null +++ b/migrations/008-down-import-aug-2007.sql @@ -0,0 +1,42 @@ + +DELETE from slice_instantiations WHERE instantiation='nm-controller'; + + + +DROP VIEW view_nodes; +DROP VIEW node_slices_whitelist; +DROP TABLE node_slice_whitelist; + +CREATE OR REPLACE VIEW view_nodes AS +SELECT +nodes.node_id, +nodes.hostname, +nodes.site_id, +nodes.boot_state, +nodes.deleted, +nodes.model, +nodes.boot_nonce, +nodes.version, +nodes.ssh_rsa_key, +nodes.key, +CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created, +CAST(date_part('epoch', nodes.last_updated) AS bigint) AS last_updated, +CAST(date_part('epoch', nodes.last_contact) AS bigint) AS last_contact, +peer_node.peer_id, +peer_node.peer_node_id, +COALESCE((SELECT nodenetwork_ids FROM node_nodenetworks WHERE node_nodenetworks.node_id = nodes.node_id), '{}') AS nodenetwork_ids, +COALESCE((SELECT nodegroup_ids FROM node_nodegroups WHERE node_nodegroups.node_id = nodes.node_id), '{}') AS nodegroup_ids, +COALESCE((SELECT slice_ids FROM node_slices WHERE node_slices.node_id = nodes.node_id), '{}') AS slice_ids, +COALESCE((SELECT pcu_ids FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS pcu_ids, +COALESCE((SELECT ports FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS ports, +COALESCE((SELECT conf_file_ids FROM node_conf_files WHERE node_conf_files.node_id = nodes.node_id), '{}') AS conf_file_ids, +node_session.session_id AS session +FROM nodes +LEFT JOIN peer_node USING (node_id) +LEFT JOIN node_session USING (node_id); + + +---------- revert subversion + +UPDATE plc_db_version SET subversion = 7; +SELECT subversion from plc_db_version; diff --git a/migrations/008-up-import-aug-2007.sql b/migrations/008-up-import-aug-2007.sql new file mode 100644 index 0000000..2220f8b --- /dev/null +++ b/migrations/008-up-import-aug-2007.sql @@ -0,0 +1,64 @@ +-- +-- migration 008 +-- import from Princeton codebase on august 2007 28 +-- slice instantiation 'nm-controller' +-- * white lists +-- + +INSERT INTO slice_instantiations (instantiation) VALUES ('nm-controller'); -- NM Controller + +-------------------------------------------------------------------------------- +-- Slice whitelist +-------------------------------------------------------------------------------- +-- slice whitelist on nodes +CREATE TABLE node_slice_whitelist ( + node_id integer REFERENCES nodes NOT NULL, -- Node id of whitelist + slice_id integer REFERENCES slices NOT NULL, -- Slice id thats allowd on this node + PRIMARY KEY (node_id, slice_id) +) WITH OIDS; +CREATE INDEX node_slice_whitelist_node_id_idx ON node_slice_whitelist (node_id); +CREATE INDEX node_slice_whitelist_slice_id_idx ON node_slice_whitelist (slice_id); + +-- Slices on each node +CREATE VIEW node_slices_whitelist AS +SELECT node_id, +array_accum(slice_id) AS slice_ids_whitelist +FROM node_slice_whitelist +GROUP BY node_id; + +DROP VIEW view_nodes; + + +CREATE OR REPLACE VIEW view_nodes AS +SELECT +nodes.node_id, +nodes.hostname, +nodes.site_id, +nodes.boot_state, +nodes.deleted, +nodes.model, +nodes.boot_nonce, +nodes.version, +nodes.ssh_rsa_key, +nodes.key, +CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created, +CAST(date_part('epoch', nodes.last_updated) AS bigint) AS last_updated, +CAST(date_part('epoch', nodes.last_contact) AS bigint) AS last_contact, +peer_node.peer_id, +peer_node.peer_node_id, +COALESCE((SELECT nodenetwork_ids FROM node_nodenetworks WHERE node_nodenetworks.node_id = nodes.node_id), '{}') AS nodenetwork_ids, +COALESCE((SELECT nodegroup_ids FROM node_nodegroups WHERE node_nodegroups.node_id = nodes.node_id), '{}') AS nodegroup_ids, +COALESCE((SELECT slice_ids FROM node_slices WHERE node_slices.node_id = nodes.node_id), '{}') AS slice_ids, +COALESCE((SELECT slice_ids_whitelist FROM node_slices_whitelist WHERE node_slices_whitelist.node_id = nodes.node_id), '{}') AS slice_ids_whitelist, +COALESCE((SELECT pcu_ids FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS pcu_ids, +COALESCE((SELECT ports FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS ports, +COALESCE((SELECT conf_file_ids FROM node_conf_files WHERE node_conf_files.node_id = nodes.node_id), '{}') AS conf_file_ids, +node_session.session_id AS session +FROM nodes +LEFT JOIN peer_node USING (node_id) +LEFT JOIN node_session USING (node_id); + +---------- bump subversion + +UPDATE plc_db_version SET subversion = 8; +SELECT subversion from plc_db_version; -- 2.43.0