====
[plcapi.git] / PLC / Interfaces.py
index 18016a6..0e6c728 100644 (file)
@@ -4,8 +4,6 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id$
-#
 
 from types import StringTypes
 import socket
 
 from types import StringTypes
 import socket
@@ -20,25 +18,49 @@ from PLC.NetworkTypes import NetworkType, NetworkTypes
 from PLC.NetworkMethods import NetworkMethod, NetworkMethods
 import PLC.Nodes
 
 from PLC.NetworkMethods import NetworkMethod, NetworkMethods
 import PLC.Nodes
 
-def valid_ip(ip):
+def valid_ipv4(ip):
     try:
         ip = socket.inet_ntoa(socket.inet_aton(ip))
         return True
     except socket.error:
         return False
 
     try:
         ip = socket.inet_ntoa(socket.inet_aton(ip))
         return True
     except socket.error:
         return False
 
-def in_same_network(address1, address2, netmask):
+def valid_ipv6(ip):
+    try:
+        ip = socket.inet_ntop(socket.AF_INET6, socket.inet_pton(socket.AF_INET6, ip))
+        return True
+    except socket.error:
+        return False   
+
+def valid_ip(ip):
+    return valid_ipv4(ip) or valid_ipv6(ip)
+
+def in_same_network_ipv4(address1, address2, netmask):
     """
     Returns True if two IPv4 addresses are in the same network. Faults
     if an address is invalid.
     """
     """
     Returns True if two IPv4 addresses are in the same network. Faults
     if an address is invalid.
     """
-
     address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
     address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
     netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
 
     return (address1 & netmask) == (address2 & netmask)
 
     address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
     address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
     netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
 
     return (address1 & netmask) == (address2 & netmask)
 
+def in_same_network_ipv6(address1, address2, netmask):
+    """
+    Returns True if two IPv6 addresses are in the same network. Faults
+    if an address is invalid.
+    """
+    address1 = struct.unpack('>2Q', socket.inet_pton(socket.AF_INET6, address1))[0]
+    address2 = struct.unpack('>2Q', socket.inet_pton(socket.AF_INET6, address2))[0]
+    netmask = struct.unpack('>2Q', socket.inet_pton(socket.AF_INET6, netmask))[0]
+
+    return (address1 & netmask) == (address2 & netmask)
+
+def in_same_network(address1, address2, netmask):
+    return in_same_network_ipv4(address1, address2, netmask) or \
+           in_same_network_ipv6(address1, address2, netmask) 
+
 class Interface(Row):
     """
     Representation of a row in the interfaces table. To use, optionally
 class Interface(Row):
     """
     Representation of a row in the interfaces table. To use, optionally
@@ -48,7 +70,7 @@ class Interface(Row):
 
     table_name = 'interfaces'
     primary_key = 'interface_id'
 
     table_name = 'interfaces'
     primary_key = 'interface_id'
-    join_tables = ['interface_setting']
+    join_tables = ['interface_tag']
     fields = {
         'interface_id': Parameter(int, "Node interface identifier"),
         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
     fields = {
         'interface_id': Parameter(int, "Node interface identifier"),
         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
@@ -65,20 +87,24 @@ class Interface(Row):
         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
         'node_id': Parameter(int, "Node associated with this interface"),
         'is_primary': Parameter(bool, "Is the primary interface for this node"),
         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
         'node_id': Parameter(int, "Node associated with this interface"),
         'is_primary': Parameter(bool, "Is the primary interface for this node"),
-        'setting_ids' : Parameter([int], "List of interface settings"),
+        'interface_tag_ids' : Parameter([int], "List of interface settings"),
+        'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
         }
 
         }
 
+    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:
             raise PLCInvalidArgument, "Invalid addressing method %s"%method
     def validate_method(self, method):
         network_methods = [row['method'] for row in NetworkMethods(self.api)]
         if method not in network_methods:
             raise PLCInvalidArgument, "Invalid addressing method %s"%method
-       return method
+        return method
 
     def validate_type(self, type):
         network_types = [row['type'] for row in NetworkTypes(self.api)]
         if type not in network_types:
             raise PLCInvalidArgument, "Invalid address type %s"%type
 
     def validate_type(self, type):
         network_types = [row['type'] for row in NetworkTypes(self.api)]
         if type not in network_types:
             raise PLCInvalidArgument, "Invalid address type %s"%type
-       return type
+        return type
 
     def validate_ip(self, ip):
         if ip and not valid_ip(ip):
 
     def validate_ip(self, ip):
         if ip and not valid_ip(ip):
@@ -112,13 +138,13 @@ class Interface(Row):
     validate_dns2 = validate_ip
 
     def validate_bwlimit(self, bwlimit):
     validate_dns2 = validate_ip
 
     def validate_bwlimit(self, bwlimit):
-       if not bwlimit:
-           return bwlimit
+        if not bwlimit:
+            return bwlimit
 
 
-       if bwlimit < 500000:
-           raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
+        if bwlimit < 500000:
+            raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
 
 
-       return bwlimit  
+        return bwlimit
 
     def validate_hostname(self, hostname):
         # Optional
 
     def validate_hostname(self, hostname):
         # Optional
@@ -182,29 +208,59 @@ class Interface(Row):
             # DHCP!
 
         elif method == "static":
             # DHCP!
 
         elif method == "static":
-            if 'is_primary' in self and self['is_primary'] is True:
+            if self['type'] == 'ipv4':
                 for key in ['gateway', 'dns1']:
                     if key not in self or not self[key]:
                 for key in ['gateway', 'dns1']:
                     if key not in self or not self[key]:
-                        raise PLCInvalidArgument, "For static method primary network, %s is required" % key
+                        if 'is_primary' in self and self['is_primary'] is True:
+                            raise PLCInvalidArgument, "For static method primary network, %s is required" % key
+                    else:
+                        globals()[key] = self[key]
+                for key in ['ip', 'network', 'broadcast', 'netmask']:
+                    if key not in self or not self[key]:
+                        raise PLCInvalidArgument, "For static method, %s is required" % key
+                    globals()[key] = self[key]
+                if not in_same_network(ip, network, netmask):
+                    raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
+                          (ip, network, netmask)
+                if not in_same_network(broadcast, network, netmask):
+                    raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
+                          (broadcast, network, netmask)
+                if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
+                    raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
+                          (gateway, ip, netmask)
+            elif self['type'] == 'ipv6':
+                for key in ['ip', 'gateway']:
+                    if key not in self or not self[key]:
+                        raise PLCInvalidArgument, "For static ipv6 method, %s is required" % key
                     globals()[key] = self[key]
                     globals()[key] = self[key]
-            for key in ['ip', 'network', 'broadcast', 'netmask']:
-                if key not in self or not self[key]:
-                    raise PLCInvalidArgument, "For static method, %s is required" % key
-                globals()[key] = self[key]
-            if not in_same_network(ip, network, netmask):
-                raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
-                      (ip, network, netmask)
-            if not in_same_network(broadcast, network, netmask):
-                raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
-                      (broadcast, network, netmask)
-            if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
-                raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
-                      (gateway, ip, netmask)
-
         elif method == "ipmi":
             if 'ip' not in self or not self['ip']:
                 raise PLCInvalidArgument, "For ipmi method, ip is required"
 
         elif method == "ipmi":
             if 'ip' not in self or not self['ip']:
                 raise PLCInvalidArgument, "For ipmi method, ip is required"
 
+    validate_last_updated = Row.validate_timestamp
+
+    def update_timestamp(self, col_name, commit = True):
+        """
+        Update col_name field with current time
+        """
+
+        assert 'interface_id' in self
+        assert self.table_name
+
+        self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
+                       " where interface_id = %d" % (self['interface_id']) )
+        self.sync(commit)
+
+    def update_last_updated(self, commit = True):
+        self.update_timestamp('last_updated', commit)
+
+    def delete(self,commit=True):
+        ### need to cleanup ilinks
+        self.api.db.do("DELETE FROM ilink WHERE src_interface_id=%d OR dst_interface_id=%d" % \
+                           (self['interface_id'],self['interface_id']))
+        
+        Row.delete(self)
+
 class Interfaces(Table):
     """
     Representation of row(s) from the interfaces table in the
 class Interfaces(Table):
     """
     Representation of row(s) from the interfaces table in the
@@ -214,18 +270,34 @@ class Interfaces(Table):
     def __init__(self, api, interface_filter = None, columns = None):
         Table.__init__(self, api, Interface, columns)
 
     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)):
 
         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):
             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]})
             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:
             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)
 
         self.selectall(sql)