Interfaces can handle tags through Add/Get/Update
authorThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Thu, 11 Dec 2008 16:24:35 +0000 (16:24 +0000)
committerThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Thu, 11 Dec 2008 16:24:35 +0000 (16:24 +0000)
Interfaces can be referenced from their ip rather than just on interface_id
removed remaining references to 'node networks' in text/comment

18 files changed:
PLC/Accessors/Accessors_standard.py
PLC/Accessors/Factory.py
PLC/InterfaceTags.py
PLC/Interfaces.py
PLC/Methods/AddInterface.py
PLC/Methods/AddNode.py
PLC/Methods/BootUpdateNode.py
PLC/Methods/DeleteInterface.py
PLC/Methods/GenerateNodeConfFile.py
PLC/Methods/GetBootMedium.py
PLC/Methods/GetInterfaces.py
PLC/Methods/UpdateInterface.py
PLC/Methods/UpdateNode.py
PLC/Nodes.py
PLC/PostgreSQL.py
PLC/Slices.py
PLC/Table.py
planetlab5.sql

index 585f881..f158273 100644 (file)
@@ -48,7 +48,7 @@ define_accessors(current_module, Node, "PlainBootstrapfs", "plain-bootstrapfs",
 # xxx - don't expose yet in api interface and slices dont know how to use that yet
 define_accessors(current_module, Interface, "Ifname", "ifname", 
                  "interface/config", "linux name",
-                 get_roles=all_roles, set_roles=tech_roles, expose_in_api=False)
+                 get_roles=all_roles, set_roles=tech_roles, expose_in_api=True)
 define_accessors(current_module, Interface, "Driver", "driver", 
                  "interface/config", "driver name",
                  get_roles=all_roles, set_roles=tech_roles)
index 0f5eb54..cd526cc 100644 (file)
@@ -27,7 +27,7 @@ taggable_classes = { Node : {'table_class' : Nodes,
                              'secondary_key': 'hostname'},
                      Interface : {'table_class' : Interfaces, 
                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
-                                  },
+                                  'secondary_key' : 'ip'},
                      Slice: {'table_class' : Slices, 
                              'joins_class': SliceTags, 'join_class': SliceTag,
                              'secondary_key':'login_base'},
index 7ecc800..8858dbf 100644 (file)
@@ -9,6 +9,7 @@ from PLC.Parameter import Parameter
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
 from PLC.TagTypes import TagType, TagTypes
+from PLC.Interfaces import Interface
 
 class InterfaceTag(Row):
     """
@@ -20,7 +21,8 @@ class InterfaceTag(Row):
     primary_key = 'interface_tag_id'
     fields = {
         'interface_tag_id': Parameter(int, "Interface setting identifier"),
-        'interface_id': Parameter(int, "Interface identifier"),
+        'interface_id': Interface.fields['interface_id'],
+        'ip': Interface.fields['ip'],
         'tag_type_id': TagType.fields['tag_type_id'],
         'tagname': TagType.fields['tagname'],
         'description': TagType.fields['description'],
index fc06ea1..6701bef 100644 (file)
@@ -68,6 +68,9 @@ class Interface(Row):
         'interface_tag_ids' : Parameter([int], "List of interface settings"),
         }
 
+    view_tags_name = "view_interface_tags"
+    tags = {}
+
     def validate_method(self, method):
         network_methods = [row['method'] for row in NetworkMethods(self.api)]
         if method not in network_methods:
@@ -214,18 +217,34 @@ class Interfaces(Table):
     def __init__(self, api, interface_filter = None, columns = None):
         Table.__init__(self, api, Interface, columns)
 
-        sql = "SELECT %s FROM view_interfaces WHERE True" % \
-              ", ".join(self.columns)
+        # the view that we're selecting upon: start with view_nodes
+        view = "view_interfaces"
+        # as many left joins as requested tags
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
+                                                Interface.primary_key)
+            
+        sql = "SELECT %s FROM %s WHERE True" % \
+            (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if interface_filter is not None:
             if isinstance(interface_filter, (list, tuple, set)):
-                interface_filter = Filter(Interface.fields, {'interface_id': interface_filter})
+                # Separate the list into integers and strings
+                ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
+                strs = filter(lambda x: isinstance(x, StringTypes), interface_filter)
+                interface_filter = Filter(Interface.fields, {'interface_id': ints, 'ip': strs})
+                sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
             elif isinstance(interface_filter, dict):
-                interface_filter = Filter(Interface.fields, interface_filter)
+                allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
+                interface_filter = Filter(allowed_fields, interface_filter)
+                sql += " AND (%s) %s" % interface_filter.sql(api)
             elif isinstance(interface_filter, int):
                 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
+                sql += " AND (%s) %s" % interface_filter.sql(api)
+            elif isinstance (interface_filter, StringTypes):
+                interface_filter = Filter(Interface.fields, {'ip':[interface_filter]})
+                sql += " AND (%s) %s" % interface_filter.sql(api, "AND")
             else:
-                raise PLCInvalidArgument, "Wrong node network filter %r"%interface_filter
-            sql += " AND (%s) %s" % interface_filter.sql(api)
+                raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter
 
         self.selectall(sql)
index 7e77485..cd3911a 100644 (file)
@@ -1,12 +1,18 @@
 # $Id$
 from PLC.Faults import *
+from PLC.Auth import Auth
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
+from PLC.Table import Row
+
 from PLC.Nodes import Node, Nodes
 from PLC.Interfaces import Interface, Interfaces
-from PLC.Auth import Auth
+from PLC.TagTypes import TagTypes
+from PLC.InterfaceTags import InterfaceTags
+from PLC.Methods.AddInterfaceTag import AddInterfaceTag
+from PLC.Methods.UpdateInterfaceTag import UpdateInterfaceTag
 
-can_update = lambda (field, value): field not in ['interface_id', 'node_id']
+can_update = ['interface_id', 'node_id']
 
 class AddInterface(Method):
     """
@@ -29,25 +35,28 @@ class AddInterface(Method):
 
     roles = ['admin', 'pi', 'tech']
 
-    interface_fields = dict(filter(can_update, Interface.fields.items()))
+    accepted_fields = Row.accepted_fields(can_update, [Interface.fields,Interface.tags])
 
     accepts = [
         Auth(),
         Mixed(Node.fields['node_id'],
               Node.fields['hostname']),
-        interface_fields
+        accepted_fields
         ]
 
     returns = Parameter(int, 'New interface_id (> 0) if successful')
 
     
     def call(self, auth, node_id_or_hostname, interface_fields):
-        interface_fields = dict(filter(can_update, interface_fields.items()))
+
+        [native,tags,rejected]=Row.split_fields(interface_fields,[Interface.fields,Interface.tags])
+        if rejected:
+            raise PLCInvalidArgument, "Cannot add Interface with column(s) %r"%rejected
 
         # Check if node exists
         nodes = Nodes(self.api, [node_id_or_hostname])
         if not nodes:
-            raise PLCInvalidArgument, "No such node"
+            raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
        node = nodes[0]
 
         # Authenticated function
@@ -57,18 +66,29 @@ class AddInterface(Method):
         # member of the site where the node exists.
         if 'admin' not in self.caller['roles']:
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to add node network for specified node"
+                raise PLCPermissionDenied, "Not allowed to add an interface to the specified node"
 
-        # Add node network
-       interface = Interface(self.api, interface_fields)
+        # Add interface
+       interface = Interface(self.api, native)
         interface['node_id'] = node['node_id']
-       # if this is the first node network, make it primary
+       # if this is the first interface, make it primary
        if not node['interface_ids']:
                interface['is_primary'] = True
         interface.sync()
        
        # Logging variables
-       self.object_ids = [node['node_id'], interface['interface_id']]  
-       self.messgage = "Node network %d added" % interface['interface_id']
+       self.object_objects = { 'Node': [node['node_id']], 
+                                'Interface' : [interface['interface_id']] }
+       self.message = "Interface %d added" % interface['interface_id']
+
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            interface_tags=InterfaceTags(self.api,{'tagname':tagname,'interface_id':interface['interface_id']})
+            if not interface_tags:
+                AddInterfaceTag(self.api).__call__(auth,interface['interface_id'],tagname,value)
+            else:
+                UpdateInterfaceTag(self.api).__call__(auth,interface_tags[0]['interface_tag_id'],value)
 
         return interface['interface_id']
index 7354dfe..9e4a945 100644 (file)
@@ -1,12 +1,12 @@
 # $Id$
 from PLC.Faults import *
+from PLC.Auth import Auth
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Table import Row
-from PLC.Nodes import Node, Nodes
-from PLC.NodeGroups import NodeGroup, NodeGroups
+
 from PLC.Sites import Site, Sites
-from PLC.Auth import Auth
+from PLC.Nodes import Node, Nodes
 from PLC.TagTypes import TagTypes
 from PLC.NodeTags import NodeTags
 from PLC.Methods.AddNodeTag import AddNodeTag
index 6c8e827..9da550f 100644 (file)
@@ -38,22 +38,22 @@ class BootUpdateNode(Method):
         if node_fields.has_key('ssh_host_key'):
             self.caller['ssh_rsa_key'] = node_fields['ssh_host_key']
 
-        # Update primary node network state
+        # Update primary interface state
         if node_fields.has_key('primary_network'):
             primary_network = node_fields['primary_network'] 
 
             if 'interface_id' not in primary_network:
-                raise PLCInvalidArgument, "Node network not specified"
+                raise PLCInvalidArgument, "Interface not specified"
             if primary_network['interface_id'] not in self.caller['interface_ids']:
-                raise PLCInvalidArgument, "Node network not associated with calling node"
+                raise PLCInvalidArgument, "Interface not associated with calling node"
 
             interfaces = Interfaces(self.api, [primary_network['interface_id']])
             if not interfaces:
-                raise PLCInvalidArgument, "No such node network"
+                raise PLCInvalidArgument, "No such interface %r"%interface_id
             interface = interfaces[0]
 
             if not interface['is_primary']:
-                raise PLCInvalidArgument, "Not the primary node network on record"
+                raise PLCInvalidArgument, "Not the primary interface on record"
 
             interface_fields = dict(filter(can_update, primary_network.items()))
             interface.update(interface_fields)
index c8f6c82..bc6f80f 100644 (file)
@@ -8,10 +8,10 @@ from PLC.Interfaces import Interface, Interfaces
 
 class DeleteInterface(Method):
     """
-    Deletes an existing node network interface.
+    Deletes an existing interface.
 
-    Admins may delete any node network. PIs and techs may only delete
-    node network interfaces associated with nodes at their sites.
+    Admins may delete any interface. PIs and techs may only delete
+    interface interfaces associated with nodes at their sites.
 
     Returns 1 if successful, faults otherwise.
     """
@@ -28,16 +28,16 @@ class DeleteInterface(Method):
 
     def call(self, auth, interface_id):
 
-        # Get node network information
+        # Get interface information
         interfaces = Interfaces(self.api, [interface_id])
         if not interfaces:
-            raise PLCInvalidArgument, "No such node network"
+            raise PLCInvalidArgument, "No such interface %r"%interface_id
        interface = interfaces[0]
        
        # Get node information
        nodes = Nodes(self.api, [interface['node_id']])
        if not nodes:
-               raise PLCInvalidArgument, "No such node"
+               raise PLCInvalidArgument, "No such node %r"%node_id
        node = nodes[0]
 
         # Authenticated functino
@@ -47,12 +47,12 @@ class DeleteInterface(Method):
         # member of the site at which the node is located.
         if 'admin' not in self.caller['roles']:
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to delete this node network"
+                raise PLCPermissionDenied, "Not allowed to delete this interface"
 
         interface.delete()
 
        # Logging variables
        self.event_objects = {'Interface': [interface['interface_id']]}
-       self.message = "Node network %d deleted" % interface['interface_id']
+       self.message = "Interface %d deleted" % interface['interface_id']
 
         return 1
index 9689c04..a121264 100644 (file)
@@ -48,7 +48,7 @@ class GenerateNodeConfFile(Method):
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to generate a configuration file for that node"
 
-       # Get node networks for this node
+       # Get interfaces for this node
         primary = None
         interfaces = Interfaces(self.api, node['interface_ids'])
         for interface in interfaces:
index 083e08f..ccdb7ca 100644 (file)
@@ -152,7 +152,7 @@ class GetBootMedium(Method):
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to generate a configuration file for %s"%node['hostname']
 
-        # Get node networks for this node
+        # Get interface for this node
         primary = None
         interfaces = Interfaces(self.api, node['interface_ids'])
         for interface in interfaces:
index b12f4b5..7a3386c 100644 (file)
@@ -8,11 +8,11 @@ from PLC.Auth import Auth
 
 class GetInterfaces(Method):
     """
-    Returns an array of structs containing details about node network
-    interfacess. If interfaces_filter is specified and is an array
-    of node network identifiers, or a struct of node network
-    fields and values, only node network interfaces matching the filter
-    will be returned.
+    Returns an array of structs containing details about network
+    interfaces. If interfaces_filter is specified and is an array of
+    interface identifiers, or a struct of interface fields and
+    values, only interfaces matching the filter will be
+    returned.
 
     If return_fields is given, only the specified details will be returned.
     """
@@ -21,8 +21,10 @@ class GetInterfaces(Method):
 
     accepts = [
         Auth(),
-        Mixed([Interface.fields['interface_id']],
+        Mixed([Mixed(Interface.fields['interface_id'],
+                     Interface.fields['ip'])],
               Parameter (int, "interface id"),
+              Parameter (str, "ip address"),
               Filter(Interface.fields)),
         Parameter([str], "List of fields to return", nullok = True)
         ]
index 2406945..b7d7f30 100644 (file)
@@ -2,47 +2,56 @@
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
+from PLC.Table import Row
+from PLC.Auth import Auth
+
 from PLC.Nodes import Node, Nodes
+from PLC.TagTypes import TagTypes
+from PLC.InterfaceTags import InterfaceTags
 from PLC.Interfaces import Interface, Interfaces
-from PLC.Auth import Auth
+from PLC.Methods.AddInterfaceTag import AddInterfaceTag
+from PLC.Methods.UpdateInterfaceTag import UpdateInterfaceTag
 
-can_update = lambda (field, value): field not in \
-             ['interface_id','node_id']
+can_update = ['interface_id','node_id']
 
 class UpdateInterface(Method):
     """
-    Updates an existing node network. Any values specified in
+    Updates an existing interface network. Any values specified in
     interface_fields are used, otherwise defaults are
     used. Acceptable values for method are dhcp and static. If type is
     static, then ip, gateway, network, broadcast, netmask, and dns1
     must all be specified in interface_fields. If type is dhcp,
     these parameters, even if specified, are ignored.
     
-    PIs and techs may only update networks associated with their own
-    nodes. Admins may update any node network.
+    PIs and techs may only update interfaces associated with their own
+    nodes. Admins may update any interface network.
  
     Returns 1 if successful, faults otherwise.
     """
 
     roles = ['admin', 'pi', 'tech']
 
-    interface_fields = dict(filter(can_update, Interface.fields.items()))
+    accepted_fields = Row.accepted_fields(can_update, [Interface.fields,Interface.tags])
 
     accepts = [
         Auth(),
        Interface.fields['interface_id'],
-       interface_fields
+       accepted_fields
         ]
 
     returns = Parameter(int, '1 if successful')
 
     def call(self, auth, interface_id, interface_fields):
-        interface_fields = dict(filter(can_update, interface_fields.items()))
 
-       # Get node network information
+        [native,tags,rejected] = Row.split_fields(interface_fields,[Interface.fields,Interface.tags])
+
+        if rejected:
+            raise PLCInvalidArgument, "Cannot update Interface column(s) %r"%rejected
+
+       # Get interface information
        interfaces = Interfaces(self.api, [interface_id])
        if not interfaces:
-            raise PLCInvalidArgument, "No such node network"
+            raise PLCInvalidArgument, "No such interface"
 
        interface = interfaces[0]
                
@@ -54,17 +63,26 @@ class UpdateInterface(Method):
         if 'admin' not in self.caller['roles']:
             nodes = Nodes(self.api, [interface['node_id']])
             if not nodes:
-                raise PLCPermissionDenied, "Node network is not associated with a node"
+                raise PLCPermissionDenied, "Interface is not associated with a node"
             node = nodes[0]
             if node['site_id'] not in self.caller['site_ids']:
-                raise PLCPermissionDenied, "Not allowed to update node network"
+                raise PLCPermissionDenied, "Not allowed to update interface"
 
-       # Update node network
-       interface.update(interface_fields)
+       interface.update(native)
         interface.sync()
        
+        for (tagname,value) in tags.iteritems():
+            # the tagtype instance is assumed to exist, just check that
+            if not TagTypes(self.api,{'tagname':tagname}):
+                raise PLCInvalidArgument,"No such TagType %s"%tagname
+            interface_tags=InterfaceTags(self.api,{'tagname':tagname,'interface_id':interface['interface_id']})
+            if not interface_tags:
+                AddInterfaceTag(self.api).__call__(auth,interface['interface_id'],tagname,value)
+            else:
+                UpdateInterfaceTag(self.api).__call__(auth,interface_tags[0]['interface_tag_id'],value)
+
        self.event_objects = {'Interface': [interface['interface_id']]}
-       self.message = "Node network %d updated: %s " % \
+       self.message = "Interface %d updated: %s " % \
            (interface['interface_id'], ", ".join(interface_fields.keys()))
 
         return 1
index 46b7ad1..46e63be 100644 (file)
@@ -3,8 +3,9 @@ from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Table import Row
-from PLC.Nodes import Node, Nodes
 from PLC.Auth import Auth
+
+from PLC.Nodes import Node, Nodes
 from PLC.TagTypes import TagTypes
 from PLC.NodeTags import NodeTags
 from PLC.Methods.AddNodeTag import AddNodeTag
@@ -43,7 +44,7 @@ class UpdateNode(Method):
         [native,related,tags,rejected] = Row.split_fields(node_fields,[Node.fields,Node.related_fields,Node.tags])
 
         if rejected:
-            raise PLCInvalidArgument, "Cannot update column(s) %r"%rejected
+            raise PLCInvalidArgument, "Cannot update Node column(s) %r"%rejected
 
        # Remove admin only fields
        if 'admin' not in self.caller['roles']:
@@ -77,13 +78,6 @@ class UpdateNode(Method):
        node.update_last_updated(commit=False)
         node.sync(commit=True)
        
-       # Logging variables
-       self.event_objects = {'Node': [node['node_id']]}
-       self.message = 'Node %d updated: %s.' % \
-               (node['node_id'], ", ".join(node_fields.keys()))
-       if 'boot_state' in node_fields.keys():
-               self.message += ' boot_state updated to %s' %  node_fields['boot_state']
-
         for (tagname,value) in tags.iteritems():
             # the tagtype instance is assumed to exist, just check that
             if not TagTypes(self.api,{'tagname':tagname}):
@@ -94,4 +88,11 @@ class UpdateNode(Method):
             else:
                 UpdateNodeTag(self.api).__call__(auth,node_tags[0]['node_tag_id'],value)
 
+       # Logging variables
+       self.event_objects = {'Node': [node['node_id']]}
+       self.message = 'Node %d updated: %s.' % \
+               (node['node_id'], ", ".join(node_fields.keys()))
+       if 'boot_state' in node_fields.keys():
+               self.message += ' boot_state updated to %s' %  node_fields['boot_state']
+
         return 1
index 99b3ef9..20bc621 100644 (file)
@@ -274,7 +274,8 @@ class Nodes(Table):
         view = "view_nodes"
         # as many left joins as requested tags
         for tagname in self.tag_columns:
-            view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),Node.primary_key)
+            view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),
+                                                Node.primary_key)
             
         sql = "SELECT %s FROM %s WHERE deleted IS False" % \
               (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
index 98ef727..172b5c3 100644 (file)
@@ -52,6 +52,7 @@ class PostgreSQL:
     def __init__(self, api):
         self.api = api
         self.debug = False
+#        self.debug = True
         self.connection = None
 
     def cursor(self):
index 0e24f9c..55dc007 100644 (file)
@@ -48,6 +48,9 @@ class Slice(Row):
                        Parameter(str, "Fully qualified hostname"))]
        }
 
+    view_tags_name="view_slice_tags"
+    tags = {}
+
     def validate_name(self, name):
         # N.B.: Responsibility of the caller to ensure that login_base
         # portion of the slice name corresponds to a valid site, if
index 57f6c8b..f37f206 100644 (file)
@@ -284,7 +284,8 @@ class Row(dict):
         (*) column 2: actual tag value, renamed into tagname
         """
 
-        if not cls.view_tags_name: return ""
+        if not cls.view_tags_name: 
+            raise Exception, 'WARNING: class %s needs to set view_tags_name'%cls.__name__
 
         table_name=cls.table_name
         primary_key=cls.primary_key
index 710e436..c4ef7dc 100644 (file)
@@ -348,7 +348,7 @@ INSERT INTO network_methods (method) VALUES ('tap');
 INSERT INTO network_methods (method) VALUES ('ipmi');
 INSERT INTO network_methods (method) VALUES ('unknown');
 
--- Node network interfaces
+-- Network interfaces
 CREATE TABLE interfaces (
     -- Mandatory
     interface_id serial PRIMARY KEY,                   -- Network interface identifier
@@ -405,6 +405,7 @@ CREATE OR REPLACE VIEW view_interface_tags AS
 SELECT
 interface_tag.interface_tag_id,
 interface_tag.interface_id,
+interfaces.ip,
 tag_types.tag_type_id,
 tag_types.tagname,
 tag_types.description,
@@ -412,7 +413,8 @@ tag_types.category,
 tag_types.min_role_id,
 interface_tag.value
 FROM interface_tag
-INNER JOIN tag_types USING (tag_type_id);
+INNER JOIN tag_types USING (tag_type_id)
+INNER JOIN interfaces USING (interface_id);
 
 CREATE OR REPLACE VIEW view_interfaces AS
 SELECT