2 # Functions for interacting with the interfaces table in the database
4 # Mark Huang <mlhuang@cs.princeton.edu>
5 # Copyright (C) 2006 The Trustees of Princeton University
8 from types import StringTypes
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
23 ip = socket.inet_ntoa(socket.inet_aton(ip))
28 def in_same_network(address1, address2, netmask):
30 Returns True if two IPv4 addresses are in the same network. Faults
31 if an address is invalid.
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]
38 return (address1 & netmask) == (address2 & netmask)
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().
47 table_name = 'interfaces'
48 primary_key = 'interface_id'
49 join_tables = ['interface_tag']
51 'interface_id': Parameter(int, "Node interface identifier"),
52 'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
53 'type': Parameter(str, "Address type (e.g., 'ipv4')"),
54 'ip': Parameter(str, "IP address", nullok = True),
55 'mac': Parameter(str, "MAC address", nullok = True),
56 'gateway': Parameter(str, "IP address of primary gateway", nullok = True),
57 'network': Parameter(str, "Subnet address", nullok = True),
58 'broadcast': Parameter(str, "Network broadcast address", nullok = True),
59 'netmask': Parameter(str, "Subnet mask", nullok = True),
60 'dns1': Parameter(str, "IP address of primary DNS server", nullok = True),
61 'dns2': Parameter(str, "IP address of secondary DNS server", nullok = True),
62 'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
63 'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
64 'node_id': Parameter(int, "Node associated with this interface"),
65 'is_primary': Parameter(bool, "Is the primary interface for this node"),
66 'interface_tag_ids' : Parameter([int], "List of interface settings"),
67 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
70 view_tags_name = "view_interface_tags"
73 def validate_method(self, method):
74 network_methods = [row['method'] for row in NetworkMethods(self.api)]
75 if method not in network_methods:
76 raise PLCInvalidArgument, "Invalid addressing method %s"%method
79 def validate_type(self, type):
80 network_types = [row['type'] for row in NetworkTypes(self.api)]
81 if type not in network_types:
82 raise PLCInvalidArgument, "Invalid address type %s"%type
85 def validate_ip(self, ip):
86 if ip and not valid_ip(ip):
87 raise PLCInvalidArgument, "Invalid IP address %s"%ip
90 def validate_mac(self, mac):
95 bytes = mac.split(":")
98 for i, byte in enumerate(bytes):
100 if byte < 0 or byte > 255:
102 bytes[i] = "%02x" % byte
103 mac = ":".join(bytes)
105 raise PLCInvalidArgument, "Invalid MAC address %s"%mac
109 validate_gateway = validate_ip
110 validate_network = validate_ip
111 validate_broadcast = validate_ip
112 validate_netmask = validate_ip
113 validate_dns1 = validate_ip
114 validate_dns2 = validate_ip
116 def validate_bwlimit(self, bwlimit):
121 raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
125 def validate_hostname(self, hostname):
130 if not PLC.Nodes.valid_hostname(hostname):
131 raise PLCInvalidArgument, "Invalid hostname %s"%hostname
135 def validate_node_id(self, node_id):
136 nodes = PLC.Nodes.Nodes(self.api, [node_id])
138 raise PLCInvalidArgument, "No such node %d"%node_id
142 def validate_is_primary(self, is_primary):
144 Set this interface to be the primary one.
148 nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
150 raise PLCInvalidArgument, "No such node %d"%node_id
153 if node['interface_ids']:
154 conflicts = Interfaces(self.api, node['interface_ids'])
155 for interface in conflicts:
156 if ('interface_id' not in self or \
157 self['interface_id'] != interface['interface_id']) and \
158 interface['is_primary']:
159 raise PLCInvalidArgument, "Can only set one primary interface per node"
165 Flush changes back to the database.
171 assert 'method' in self
172 method = self['method']
174 if method == "proxy" or method == "tap":
175 if 'mac' in self and self['mac']:
176 raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
177 if 'ip' not in self or not self['ip']:
178 raise PLCInvalidArgument, "For %s method, ip is required" % method
179 if method == "tap" and ('gateway' not in self or not self['gateway']):
180 raise PLCInvalidArgument, "For tap method, gateway is required and should be " \
181 "the IP address of the node that proxies for this address"
182 # Should check that the proxy address is reachable, but
183 # there's no way to tell if the only primary interface is
186 elif method == "static":
187 for key in ['gateway', 'dns1']:
188 if key not in self or not self[key]:
189 if 'is_primary' in self and self['is_primary'] is True:
190 raise PLCInvalidArgument, "For static method primary network, %s is required" % key
192 globals()[key] = self[key]
193 for key in ['ip', 'network', 'broadcast', 'netmask']:
194 if key not in self or not self[key]:
195 raise PLCInvalidArgument, "For static method, %s is required" % key
196 globals()[key] = self[key]
197 if not in_same_network(ip, network, netmask):
198 raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
199 (ip, network, netmask)
200 if not in_same_network(broadcast, network, netmask):
201 raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
202 (broadcast, network, netmask)
203 if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
204 raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
205 (gateway, ip, netmask)
207 elif method == "ipmi":
208 if 'ip' not in self or not self['ip']:
209 raise PLCInvalidArgument, "For ipmi method, ip is required"
211 validate_last_updated = Row.validate_timestamp
213 def update_timestamp(self, col_name, commit = True):
215 Update col_name field with current time
218 assert 'interface_id' in self
219 assert self.table_name
221 self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
222 " where interface_id = %d" % (self['interface_id']) )
225 def update_last_updated(self, commit = True):
226 self.update_timestamp('last_updated', commit)
228 def delete(self,commit=True):
229 ### need to cleanup ilinks
230 self.api.db.do("DELETE FROM ilink WHERE src_interface_id=%d OR dst_interface_id=%d" % \
231 (self['interface_id'],self['interface_id']))
235 class Interfaces(Table):
237 Representation of row(s) from the interfaces table in the
241 def __init__(self, api, interface_filter = None, columns = None):
242 Table.__init__(self, api, Interface, columns)
244 # the view that we're selecting upon: start with view_nodes
245 view = "view_interfaces"
246 # as many left joins as requested tags
247 for tagname in self.tag_columns:
248 view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
249 Interface.primary_key)
251 sql = "SELECT %s FROM %s WHERE True" % \
252 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
254 if interface_filter is not None:
255 if isinstance(interface_filter, (list, tuple, set)):
256 # Separate the list into integers and strings
257 ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
258 strs = filter(lambda x: isinstance(x, StringTypes), interface_filter)
259 interface_filter = Filter(Interface.fields, {'interface_id': ints, 'ip': strs})
260 sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
261 elif isinstance(interface_filter, dict):
262 allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
263 interface_filter = Filter(allowed_fields, interface_filter)
264 sql += " AND (%s) %s" % interface_filter.sql(api)
265 elif isinstance(interface_filter, int):
266 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
267 sql += " AND (%s) %s" % interface_filter.sql(api)
268 elif isinstance (interface_filter, StringTypes):
269 interface_filter = Filter(Interface.fields, {'ip':[interface_filter]})
270 sql += " AND (%s) %s" % interface_filter.sql(api, "AND")
272 raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter