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 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 (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.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
index 66ab7a7..0414099 100644 (file)
@@ -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