Merge branch 'newinterface' of ssh://bakers@git.planet-lab.org/git/plcapi into newint...
[plcapi.git] / PLC / Interfaces.py
1 #
2 # Functions for interacting with the interfaces table in the database
3 #
4 # Mark Huang <mlhuang@cs.princeton.edu>
5 # Copyright (C) 2006 The Trustees of Princeton University
6 #
7
8 from types import StringTypes
9 import socket
10 import struct
11
12 from PLC.Faults import *
13 from PLC.Parameter import Parameter
14 from PLC.Filter import Filter
15 from PLC.Debug import profile
16 from PLC.Table import Row, Table
17 from PLC.NetworkTypes import NetworkType, NetworkTypes
18 from PLC.NetworkMethods import NetworkMethod, NetworkMethods
19 import PLC.Nodes
20
21 def valid_ip(ip):
22     try:
23         ip = socket.inet_ntoa(socket.inet_aton(ip))
24         return True
25     except socket.error:
26         return False
27
28 def in_same_network(address1, address2, netmask):
29     """
30     Returns True if two IPv4 addresses are in the same network. Faults
31     if an address is invalid.
32     """
33
34     address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
35     address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
36     netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
37
38     return (address1 & netmask) == (address2 & netmask)
39
40 class Interface(Row):
41     """
42     Representation of a row in the interfaces table. To use, optionally
43     instantiate with a dict of values. Update as you would a
44     dict. Commit to the database with sync().
45     """
46
47     table_name = 'interfaces'
48     primary_key = 'interface_id'
49     join_tables = ['interface_tag', 'ip_addresses', 'routes']
50     fields = {
51         'interface_id': Parameter(int, "Node interface identifier"),
52         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
53         'mac': Parameter(str, "MAC address", nullok = True),
54         'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
55         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
56         'node_id': Parameter(int, "Node associated with this interface"),
57         'is_primary': Parameter(bool, "Is the primary interface for this node"),
58         'if_name': Parameter(str, "Interface name", nullok = True),
59         'interface_tag_ids' : Parameter([int], "List of interface settings"),
60         'ip_address_ids': Parameter([int], "List of addresses"),
61         'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
62         }
63
64     view_tags_name = "view_interface_tags"
65     tags = {}
66
67     def validate_method(self, method):
68         network_methods = [row['method'] for row in NetworkMethods(self.api)]
69         if method not in network_methods:
70             raise PLCInvalidArgument, "Invalid addressing method %s"%method
71         return method
72
73     def validate_mac(self, mac):
74         if not mac:
75             return mac
76
77         try:
78             bytes = mac.split(":")
79             if len(bytes) < 6:
80                 raise Exception
81             for i, byte in enumerate(bytes):
82                 byte = int(byte, 16)
83                 if byte < 0 or byte > 255:
84                     raise Exception
85                 bytes[i] = "%02x" % byte
86             mac = ":".join(bytes)
87         except:
88             raise PLCInvalidArgument, "Invalid MAC address %s"%mac
89
90         return mac
91
92     def validate_bwlimit(self, bwlimit):
93         if not bwlimit:
94             return bwlimit
95
96         if bwlimit < 1:
97             raise PLCInvalidArgument, 'Minimum bw is 1 Mbps'
98
99         if bwlimit >= 1000000:
100             raise PLCInvalidArgument, 'Maximum bw must be less than 1000000'
101
102         return bwlimit
103
104     def validate_hostname(self, hostname):
105         # Optional
106         if not hostname:
107             return hostname
108
109         if not PLC.Nodes.valid_hostname(hostname):
110             raise PLCInvalidArgument, "Invalid hostname %s"%hostname
111
112         return hostname
113
114     def validate_node_id(self, node_id):
115         nodes = PLC.Nodes.Nodes(self.api, [node_id])
116         if not nodes:
117             raise PLCInvalidArgument, "No such node %d"%node_id
118
119         return node_id
120
121     def validate_is_primary(self, is_primary):
122         """
123         Set this interface to be the primary one.
124         """
125
126         if is_primary:
127             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
128             if not nodes:
129                 raise PLCInvalidArgument, "No such node %d"%node_id
130             node = nodes[0]
131
132             if node['interface_ids']:
133                 conflicts = Interfaces(self.api, node['interface_ids'])
134                 for interface in conflicts:
135                     if ('interface_id' not in self or \
136                         self['interface_id'] != interface['interface_id']) and \
137                        interface['is_primary']:
138                         raise PLCInvalidArgument, "Can only set one primary interface per node"
139
140         return is_primary
141
142     def validate(self):
143         """
144         Flush changes back to the database.
145         """
146
147         # Basic validation
148         Row.validate(self)
149
150         assert 'method' in self
151         method = self['method']
152
153     validate_last_updated = Row.validate_timestamp
154
155     def update_timestamp(self, col_name, commit = True):
156         """
157         Update col_name field with current time
158         """
159
160         assert 'interface_id' in self
161         assert self.table_name
162
163         self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
164                        " where interface_id = %d" % (self['interface_id']) )
165         self.sync(commit)
166
167     def update_last_updated(self, commit = True):
168         self.update_timestamp('last_updated', commit)
169
170     def delete(self,commit=True):
171         ### need to cleanup ilinks
172         self.api.db.do("DELETE FROM ilink WHERE src_interface_id=%d OR dst_interface_id=%d" % \
173                            (self['interface_id'],self['interface_id']))
174         
175         Row.delete(self)
176
177 class Interfaces(Table):
178     """
179     Representation of row(s) from the interfaces table in the
180     database.
181     """
182
183     def __init__(self, api, interface_filter = None, columns = None):
184         Table.__init__(self, api, Interface, columns)
185
186         # the view that we're selecting upon: start with view_nodes
187         view = "view_interfaces"
188         # as many left joins as requested tags
189         for tagname in self.tag_columns:
190             view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
191                                                 Interface.primary_key)
192
193         sql = "SELECT %s FROM %s WHERE True" % \
194             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
195
196         if interface_filter is not None:
197             # TODO: Deleted the ability here to filter by ipaddress; Need to make
198             #   sure that wasn't used anywhere.
199             if isinstance(interface_filter, (list, tuple, set)):
200                 # Separate the list into integers and strings
201                 ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
202                 interface_filter = Filter(Interface.fields, {'interface_id': ints})
203                 sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
204             elif isinstance(interface_filter, dict):
205                 allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
206                 interface_filter = Filter(allowed_fields, interface_filter)
207                 sql += " AND (%s) %s" % interface_filter.sql(api)
208             elif isinstance(interface_filter, int):
209                 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
210                 sql += " AND (%s) %s" % interface_filter.sql(api)
211             else:
212                 raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter
213
214         self.selectall(sql)