Added PLCAPI support for Dummynet Boxes.
authorMarta Carbone <marta@prova.iet.unipi.it>
Thu, 16 Apr 2009 12:22:40 +0000 (12:22 +0000)
committerMarta Carbone <marta@prova.iet.unipi.it>
Thu, 16 Apr 2009 12:22:40 +0000 (12:22 +0000)
PLC/Accessors/Accessors_dummynetbox.py [new file with mode: 0644]
PLC/Accessors/Accessors_standard.py
PLC/Methods/AddDummynetBox.py [new file with mode: 0644]
PLC/Methods/DeleteDummynetBox.py [new file with mode: 0644]
PLC/Methods/DeleteEmulationLink.py [new file with mode: 0644]
PLC/Methods/GetDummyBoxUsers.py [new file with mode: 0644]
PLC/Methods/UpdateEmulationLink.py
PLC/Methods/__init__.py

diff --git a/PLC/Accessors/Accessors_dummynetbox.py b/PLC/Accessors/Accessors_dummynetbox.py
new file mode 100644 (file)
index 0000000..b75bf2a
--- /dev/null
@@ -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')
+
index 6a97d41..db7858f 100644 (file)
@@ -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_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", 
 # 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 (file)
index 0000000..6d81aa7
--- /dev/null
@@ -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 (file)
index 0000000..9a9576c
--- /dev/null
@@ -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 (file)
index 0000000..b8ae20e
--- /dev/null
@@ -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 (file)
index 0000000..d3a8fe8
--- /dev/null
@@ -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:
+# 
+#      <key_id> <node_ip> <slicename>
+#
+# 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
index 046f881..007faba 100644 (file)
@@ -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.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):
     """
 
 class UpdateEmulationLink(Method):
     """
@@ -80,7 +80,7 @@ class UpdateEmulationLink(Method):
                 raise PLCPermissionDenied, "Not allowed to manage on this link"
 
         # Add the dummynetbox
                 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
         emulation_link.call(auth, node_id, dummynet_id)
 
         return 1
index 66ab7a7..0414099 100644 (file)
@@ -6,6 +6,7 @@ AddBootState
 AddConfFile
 AddConfFileToNode
 AddConfFileToNodeGroup
 AddConfFile
 AddConfFileToNode
 AddConfFileToNodeGroup
+AddDummynetBox
 AddIlink
 AddInitScript
 AddInterface
 AddIlink
 AddInitScript
 AddInterface
@@ -51,6 +52,8 @@ DeleteBootState
 DeleteConfFile
 DeleteConfFileFromNode
 DeleteConfFileFromNodeGroup
 DeleteConfFile
 DeleteConfFileFromNode
 DeleteConfFileFromNodeGroup
+DeleteDummynetBox
+DeleteEmulationLink
 DeleteIlink
 DeleteInitScript
 DeleteInterface
 DeleteIlink
 DeleteInitScript
 DeleteInterface
@@ -88,6 +91,7 @@ GetAddresses
 GetBootMedium
 GetBootStates
 GetConfFiles
 GetBootMedium
 GetBootStates
 GetConfFiles
+GetDummyBoxUsers
 GetEventObjects
 GetEvents
 GetIlinks
 GetEventObjects
 GetEvents
 GetIlinks