StringTypes has gone
[plcapi.git] / PLC / Interfaces.py
index 18016a6..5b0f70e 100644 (file)
@@ -4,10 +4,7 @@
 # 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
 import struct
 
 import socket
 import struct
 
@@ -20,25 +17,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 +69,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,24 +86,28 @@ 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:
     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
+            raise PLCInvalidArgument("Invalid addressing method %s"%method)
+        return method
 
     def validate_type(self, type):
         network_types = [row['type'] for row in NetworkTypes(self.api)]
         if type not in network_types:
 
     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
+            raise PLCInvalidArgument("Invalid address type %s"%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):
-            raise PLCInvalidArgument, "Invalid IP address %s"%ip
+            raise PLCInvalidArgument("Invalid IP address %s"%ip)
         return ip
 
     def validate_mac(self, mac):
         return ip
 
     def validate_mac(self, mac):
@@ -100,7 +125,7 @@ class Interface(Row):
                 bytes[i] = "%02x" % byte
             mac = ":".join(bytes)
         except:
                 bytes[i] = "%02x" % byte
             mac = ":".join(bytes)
         except:
-            raise PLCInvalidArgument, "Invalid MAC address %s"%mac
+            raise PLCInvalidArgument("Invalid MAC address %s"%mac)
 
         return mac
 
 
         return mac
 
@@ -112,13 +137,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
@@ -126,14 +151,14 @@ class Interface(Row):
             return hostname
 
         if not PLC.Nodes.valid_hostname(hostname):
             return hostname
 
         if not PLC.Nodes.valid_hostname(hostname):
-            raise PLCInvalidArgument, "Invalid hostname %s"%hostname
+            raise PLCInvalidArgument("Invalid hostname %s"%hostname)
 
         return hostname
 
     def validate_node_id(self, node_id):
         nodes = PLC.Nodes.Nodes(self.api, [node_id])
         if not nodes:
 
         return hostname
 
     def validate_node_id(self, node_id):
         nodes = PLC.Nodes.Nodes(self.api, [node_id])
         if not nodes:
-            raise PLCInvalidArgument, "No such node %d"%node_id
+            raise PLCInvalidArgument("No such node %d"%node_id)
 
         return node_id
 
 
         return node_id
 
@@ -145,7 +170,7 @@ class Interface(Row):
         if is_primary:
             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
             if not nodes:
         if is_primary:
             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
             if not nodes:
-                raise PLCInvalidArgument, "No such node %d"%node_id
+                raise PLCInvalidArgument("No such node %d"%node_id)
             node = nodes[0]
 
             if node['interface_ids']:
             node = nodes[0]
 
             if node['interface_ids']:
@@ -154,7 +179,7 @@ class Interface(Row):
                     if ('interface_id' not in self or \
                         self['interface_id'] != interface['interface_id']) and \
                        interface['is_primary']:
                     if ('interface_id' not in self or \
                         self['interface_id'] != interface['interface_id']) and \
                        interface['is_primary']:
-                        raise PLCInvalidArgument, "Can only set one primary interface per node"
+                        raise PLCInvalidArgument("Can only set one primary interface per node")
 
         return is_primary
 
 
         return is_primary
 
@@ -171,39 +196,69 @@ class Interface(Row):
 
         if method == "proxy" or method == "tap":
             if 'mac' in self and self['mac']:
 
         if method == "proxy" or method == "tap":
             if 'mac' in self and self['mac']:
-                raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
+                raise PLCInvalidArgument("For %s method, mac should not be specified" % method)
             if 'ip' not in self or not self['ip']:
             if 'ip' not in self or not self['ip']:
-                raise PLCInvalidArgument, "For %s method, ip is required" % method
+                raise PLCInvalidArgument("For %s method, ip is required" % method)
             if method == "tap" and ('gateway' not in self or not self['gateway']):
             if method == "tap" and ('gateway' not in self or not self['gateway']):
-                raise PLCInvalidArgument"For tap method, gateway is required and should be " \
-                      "the IP address of the node that proxies for this address"
+                raise PLCInvalidArgument("For tap method, gateway is required and should be " \
+                      "the IP address of the node that proxies for this address")
             # Should check that the proxy address is reachable, but
             # there's no way to tell if the only primary interface is
             # DHCP!
 
         elif method == "static":
             # Should check that the proxy address is reachable, but
             # there's no way to tell if the only primary interface is
             # 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']:
         elif method == "ipmi":
             if 'ip' not in self or not self['ip']:
-                raise PLCInvalidArgument, "For ipmi method, ip is required"
+                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):
     """
 
 class Interfaces(Table):
     """
@@ -214,18 +269,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(list(self.columns.keys())+list(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 = [x for x in interface_filter if isinstance(x, int)]
+                strs = [x for x in interface_filter if isinstance(x, str)]
+                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(list(Interface.fields.items())+list(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, str):
+                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)