From: Marta Carbone Date: Thu, 16 Apr 2009 12:22:40 +0000 (+0000) Subject: Added PLCAPI support for Dummynet Boxes. X-Git-Tag: PLCAPI-4.3-7~3 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=d8589a93a65141d26ddb04662f7393987c1b39a5;p=plcapi.git Added PLCAPI support for Dummynet Boxes. --- diff --git a/PLC/Accessors/Accessors_dummynetbox.py b/PLC/Accessors/Accessors_dummynetbox.py new file mode 100644 index 0000000..b75bf2a --- /dev/null +++ b/PLC/Accessors/Accessors_dummynetbox.py @@ -0,0 +1,21 @@ +# Marta Carbone - unipi +# $Id: $ + +from PLC.Nodes import Node +from PLC.Accessors.Factory import define_accessors, all_roles + +import sys +current_module = sys.modules[__name__] + +# XXX define possible subclasses +# define the dummynetbox SubClass strings +dbox_subclass = 'DummynetBox' + +# define the type of a node as a dummynetbox +define_accessors(current_module, Node, 'Subclass', 'subclass', 'node/config', + 'type of node definition', get_roles=all_roles, set_roles='pi') + +# define the dummynetbox connected to a node +define_accessors(current_module, Node, 'DummynetBox', 'dummynetbox_id', 'node/config', + 'dummynetbox connected to the node', get_roles=all_roles, set_roles='pi') + diff --git a/PLC/Accessors/Accessors_standard.py b/PLC/Accessors/Accessors_standard.py index 6a97d41..db7858f 100644 --- a/PLC/Accessors/Accessors_standard.py +++ b/PLC/Accessors/Accessors_standard.py @@ -27,9 +27,6 @@ define_accessors(current_module, Slice, "Vref", "vref", define_accessors(current_module, Node, "Arch", "arch", "node/config", "architecture name", get_roles=all_roles, set_roles=tech_roles, expose_in_api=True) -# define the dummynet connection to a node -define_accessors(current_module, Node, "Dummynet", "dummynet", "node/config", - "dummynet box connected to the node", get_roles=all_roles, set_roles="pi") # distribution to be deployed define_accessors(current_module, Node, "Pldistro", "pldistro", "node/config", "PlanetLab distribution", diff --git a/PLC/Methods/AddDummynetBox.py b/PLC/Methods/AddDummynetBox.py new file mode 100644 index 0000000..6d81aa7 --- /dev/null +++ b/PLC/Methods/AddDummynetBox.py @@ -0,0 +1,23 @@ +# Add a dummynet box using Accessors +# +# Marta Carbone - unipi +# $Id$ + +from PLC.Accessors.Accessors_dummynetbox import * # import dummynet accessors +from PLC.Methods.AddNode import AddNode + +class AddDummynetBox(AddNode): + """ + Adds a new dummynetbox, derived class from AddNode class. + """ + + def call(self, auth, site_id_or_login_base, node_fields): + node_fields.update({'node_type':'dummynet'}) + node_id = AddNode.call(self, auth, site_id_or_login_base, node_fields) + + # create a subclass object to have a handle to issue the call + subclass = SetNodeSubclass(self.api) + # set the subclass type + subclass.call(auth, int(node_id), dbox_subclass) + + return node_id diff --git a/PLC/Methods/DeleteDummynetBox.py b/PLC/Methods/DeleteDummynetBox.py new file mode 100644 index 0000000..9a9576c --- /dev/null +++ b/PLC/Methods/DeleteDummynetBox.py @@ -0,0 +1,17 @@ +# Delete a Dummynet box using Accessors +# +# Marta Carbone - unipi +# $Id$ + +from PLC.Accessors.Accessors_dummynetbox import * # import dummynet accessors +from PLC.Methods.DeleteNode import DeleteNode + +class DeleteDummynetBox(DeleteNode): + """ + Mark an existing dummynetbox as deleted, derived from DeleteNode. + XXX add type checks + """ + + def call(self, auth, node_id_or_hostname): + + return DeleteNode.call(self, auth, node_id_or_hostname) diff --git a/PLC/Methods/DeleteEmulationLink.py b/PLC/Methods/DeleteEmulationLink.py new file mode 100644 index 0000000..b8ae20e --- /dev/null +++ b/PLC/Methods/DeleteEmulationLink.py @@ -0,0 +1,60 @@ +# Delete an Emulation link +# +# Marta Carbone - unipi +# $Id$ + +from PLC.Faults import * +from PLC.Method import Method +from PLC.Parameter import Parameter, Mixed +from PLC.Nodes import Node, Nodes +from PLC.Auth import Auth +from PLC.Method import Method +from PLC.NodeTags import * +from PLC.Accessors.Accessors_dummynetbox import * # import dummynet accessors + +class DeleteEmulationLink(Method): + """ + Delete a connection between a node and a dummynetbox. + + Returns 1 if successful, faults otherwise. + + """ + roles = ['admin', 'pi', 'tech'] + + accepts = [ + Auth(), + Parameter(int, 'node_id'), + ] + + returns = Parameter(int, '1 is successful, 0 if not found, fault otherwise') + + def call(self, auth, node_id): + + assert self.caller is not None + + # check for node existence + nodes= Nodes(self.api, [node_id]) + if not nodes: + raise PLCInvalidArgument, "Node %s not found" % node_id + + # check for roles permission to call this method + if 'admin' not in self.caller['roles']: + if site not in self.caller['site_ids']: + raise PLCPermissionDenied, "Not allowed to delete this link" + + # check for the right subclass + subclass = GetNodeSubclass(self.api) + node_subclass = subclass.call(auth, node_id) + if node_subclass != None: + raise PLCInvalidArgument, "%s is not a node, subclass is %s" % (node_id, node_subclass) + + # Delete from the nodetags the entry with + # the given node_id and tagtype = 'dummynetbox' + nodetag = NodeTags(self.api, {'node_id':node_id, 'tagname':'dummynetbox_id'}) + + if not nodetag: + return 0 + + nodetag[0].delete() + + return 1 diff --git a/PLC/Methods/GetDummyBoxUsers.py b/PLC/Methods/GetDummyBoxUsers.py new file mode 100644 index 0000000..d3a8fe8 --- /dev/null +++ b/PLC/Methods/GetDummyBoxUsers.py @@ -0,0 +1,192 @@ +# +# Marta Carbone - UniPi +# $Id: GetDummyBoxUsers.py 10410 2008-09-04 16:29:48Z marta $ +# +# This Method returns a list of tuples formatted as follow: +# +# +# +# and an authorized_keys file, to be used on a dummynet box. +# + +from PLC.Method import Method # base class used to derive methods +from PLC.Parameter import Parameter, Mixed # define input parameters +from PLC.Auth import Auth # import the Auth parameter +from PLC.Faults import * # faults library +from PLC.Nodes import Node, Nodes # main class for Nodes +from PLC.Slices import Slice, Slices # main class for Slices +from PLC.Keys import Key, Keys # main class for Keys +from PLC.Persons import Person, Persons # main class for Persons +from PLC.Interfaces import * # get the node primary ip address +from PLC.NodeTags import * # get node connected to a dummynet box +from PLC.Accessors.Accessors_dummynetbox import * # import dummynet accessors + +# authorized file delimiter string +NEWFILE_MARK = "authorized_keys_mark" + +# Dummynet box private key +KEY="/usr/share/dummynet/dbox_key" + +class GetDummyBoxUsers(Method): + """ + Return a list of information about + slice, users, user keys, nodes and dummyboxes. + + This Methods is mean to be used by a DummyBox. + + Return keys, 0 if there are no users. + """ + + roles = ['admin', 'pi'] + + accepts = [ + Auth(), + Parameter(int, 'DummyBox id'), + ] + + returns = Parameter(str, "DummyBox files") + + def call(self, auth, dummybox_id = None): + """ + Get information about users on nodes connected to the DummyBox. + + Given a dummybox_id we get the list of connected nodes + For each node_id we get a list of connected slices + For each slice we get a list of connected users + For each user we get a list of keys that we return to the caller. + """ + + # These variables contain some text to be used + # to format the output files + # port-forwarding should be denied in the main sshd configuration file + ssh_command = "/home/user/dbox.cmd " + ssh_configuration = ",no-port-forwarding,no-agent-forwarding,no-X11-forwarding " + + # check for dummynet box existence and get dummyboxes information + dummyboxes = Nodes(self.api, {'node_id':dummybox_id, 'node_type':'dummynet'}, ['site_id']) + + if dummybox_id != None and not dummyboxes: + raise PLCInvalidArgument, "No such DummyBox %s" % dummybox_id + + dummybox = dummyboxes[0] + + # this method needs authentication + assert self.caller is not None + + # XXX check if we have rights to do this operation: + # - admins can retrive all information they want, + # - dummyboxes can retrive information regarding their site, + # nodes and slice account present on their nodes. + + # Given a dummybox_id we get the list of connected nodes + connected_nodes = NodeTags(self.api, {'value': dummybox_id}, ['node_id']) + + node_list = [] + for i in connected_nodes: + node_list.append(i['node_id']) + + + nodes = Nodes(self.api, node_list, ['node_id', 'hostname', 'slice_ids']) + if not nodes: return 0 + + # Here nodes should be an array of dict with 'node_id', 'hostname' and 'slice_ids' fields + + user_map = "# Permission file, check here if a user can configure a link\n" # store slice-node information + user_map+= "# Tuples are `owner slice_name' `hostname to reconfigure'\n" + authorized_keys_dict = {} # user's keys, dictionary + dbox_key_id = 0 # key_id used to identify users keys in the dummynetbox + + # For each node_id we get a list of connected slices + for node in nodes: + + # list of connected slices + slice_ids = node['slice_ids'] + if not slice_ids: continue + + # For each slice we get a list of connected users + for slice_id in slice_ids: + # field to return + slice_name = Slices(self.api, {'slice_id': slice_id}, ['name', 'person_ids']) + if not slice_name: continue + + # Given a slice we get a list of users + person_ids = slice_name[0]['person_ids'] + if not person_ids: continue + + # For each user we get a list of keys + for person_id in person_ids: + # Given a user we get a list of keys + person_list = Persons(self.api, {'person_id': person_id}, ['person_id','key_ids']) + person = person_list[0]['person_id'] + key_list = person_list[0]['key_ids'] + if not key_list: continue + + for key_id in key_list: + key = Keys(self.api, {'key_id': key_id}, ['key']) + + # Here we have all information we need + # to build the authorized key file and + # the user map file + + k = key[0]['key'] + + # split the key in type/ssh_key/comment + splitted_key = k.split(' ',2) + uniq_key = splitted_key[0]+" "+splitted_key[1] + + # retrieve/create a unique dbox_key_id for this ssh_key + if authorized_keys_dict.has_key(uniq_key): + dbox_key_id = authorized_keys_dict[uniq_key] + else: + dbox_key_id+=1 + authorized_keys_dict.update({uniq_key : dbox_key_id}) + + # get the node ip address + nodenetworks = Interfaces(self.api, \ + {'node_id':node['node_id'], 'is_primary':'t'}, ['ip']) + + # append user and slice data to the user_map file + item = str(dbox_key_id) + item +=" " + str(nodenetworks[0]['ip']) + item +=" " + str(slice_name[0]['name']) + "\n" + + user_map += item + + # format change for authorized_keys dict + authorized_keys_file="" + authorized_keys_file += "# generated automatically by GetUsersUpdate.py on the Central Site\n"; + authorized_keys_file += "# format file:\n"; + authorized_keys_file += '# command="command key_id $SSH_ORIGINAL_COMMAND",ssh_options key_type key comment\n' + authorized_keys_file += "# where command, key_id and ssh_options are filled by the Central Site script\n" + authorized_keys_file += "# and $SSH_ORIGINAL_COMMAND is the command line inserted by the node\n" + authorized_keys_file += "\n"; + + # read the central site key + # the dummynet public key is located under KEY + try: + pub_key=KEY+".pub" + dbox_key_file = open(pub_key, 'r') + dbox_key = dbox_key_file.readline() + + # upload the central site public key, used to + # send plcapi commands to the central site + # we use the special key_index = 0 value + authorized_keys_file += "# The Central Site key, it allows to jump some checks on the dbox\n" + authorized_keys_file += "command=\"" + ssh_command + "0"; + authorized_keys_file += " $SSH_ORIGINAL_COMMAND\"" + ssh_configuration + dbox_key + "\n" + dbox_key_file.close() + except: + authorized_keys_file += "# The Central Site public key not found, this dummynet box\n"; + authorized_keys_file += "# will not accept configuration request coming from the Central Site\n"; + + authorized_keys_file += "\n"; + # upload the users keys + for i in authorized_keys_dict: + # index of the key + key_index = str(authorized_keys_dict[i]) + + # create the dummynet box command + authorized_keys_file += "command=\"" + ssh_command + key_index; + authorized_keys_file += " $SSH_ORIGINAL_COMMAND\"" + ssh_configuration + str(i) + "\n" + + return user_map+NEWFILE_MARK+"\n"+authorized_keys_file diff --git a/PLC/Methods/UpdateEmulationLink.py b/PLC/Methods/UpdateEmulationLink.py index 046f881..007faba 100644 --- a/PLC/Methods/UpdateEmulationLink.py +++ b/PLC/Methods/UpdateEmulationLink.py @@ -10,7 +10,7 @@ from PLC.Nodes import Node, Nodes from PLC.NodeGroups import NodeGroup, NodeGroups from PLC.Sites import Site, Sites from PLC.Auth import Auth -from PLC.Accessors.Accessors_standard import * # import dummynet accessors +from PLC.Accessors.Accessors_dummynetbox import * # import dummynet accessors class UpdateEmulationLink(Method): """ @@ -80,7 +80,7 @@ class UpdateEmulationLink(Method): raise PLCPermissionDenied, "Not allowed to manage on this link" # Add the dummynetbox - emulation_link = SetNodeDummynet(self.api) + emulation_link = SetNodeDummynetBox(self.api) emulation_link.call(auth, node_id, dummynet_id) return 1 diff --git a/PLC/Methods/__init__.py b/PLC/Methods/__init__.py index 66ab7a7..0414099 100644 --- a/PLC/Methods/__init__.py +++ b/PLC/Methods/__init__.py @@ -6,6 +6,7 @@ AddBootState AddConfFile AddConfFileToNode AddConfFileToNodeGroup +AddDummynetBox AddIlink AddInitScript AddInterface @@ -51,6 +52,8 @@ DeleteBootState DeleteConfFile DeleteConfFileFromNode DeleteConfFileFromNodeGroup +DeleteDummynetBox +DeleteEmulationLink DeleteIlink DeleteInitScript DeleteInterface @@ -88,6 +91,7 @@ GetAddresses GetBootMedium GetBootStates GetConfFiles +GetDummyBoxUsers GetEventObjects GetEvents GetIlinks