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
11 from types import StringTypes
15 from PLC.Faults import *
16 from PLC.Parameter import Parameter
17 from PLC.Filter import Filter
18 from PLC.Debug import profile
19 from PLC.Table import Row, Table
20 from PLC.NetworkTypes import NetworkType, NetworkTypes
21 from PLC.NetworkMethods import NetworkMethod, NetworkMethods
26 ip = socket.inet_ntoa(socket.inet_aton(ip))
31 def in_same_network(address1, address2, netmask):
33 Returns True if two IPv4 addresses are in the same network. Faults
34 if an address is invalid.
37 address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
38 address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
39 netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
41 return (address1 & netmask) == (address2 & netmask)
45 Representation of a row in the interfaces table. To use, optionally
46 instantiate with a dict of values. Update as you would a
47 dict. Commit to the database with sync().
50 table_name = 'interfaces'
51 primary_key = 'interface_id'
52 join_tables = ['interface_tag']
54 'interface_id': Parameter(int, "Node interface identifier"),
55 'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
56 'type': Parameter(str, "Address type (e.g., 'ipv4')"),
57 'ip': Parameter(str, "IP address", nullok = True),
58 'mac': Parameter(str, "MAC address", nullok = True),
59 'gateway': Parameter(str, "IP address of primary gateway", nullok = True),
60 'network': Parameter(str, "Subnet address", nullok = True),
61 'broadcast': Parameter(str, "Network broadcast address", nullok = True),
62 'netmask': Parameter(str, "Subnet mask", nullok = True),
63 'dns1': Parameter(str, "IP address of primary DNS server", nullok = True),
64 'dns2': Parameter(str, "IP address of secondary DNS server", nullok = True),
65 'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
66 'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
67 'node_id': Parameter(int, "Node associated with this interface"),
68 'is_primary': Parameter(bool, "Is the primary interface for this node"),
69 'interface_tag_ids' : Parameter([int], "List of interface settings"),
70 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
73 view_tags_name = "view_interface_tags"
76 def validate_method(self, method):
77 network_methods = [row['method'] for row in NetworkMethods(self.api)]
78 if method not in network_methods:
79 raise PLCInvalidArgument, "Invalid addressing method %s"%method
82 def validate_type(self, type):
83 network_types = [row['type'] for row in NetworkTypes(self.api)]
84 if type not in network_types:
85 raise PLCInvalidArgument, "Invalid address type %s"%type
88 def validate_ip(self, ip):
89 if ip and not valid_ip(ip):
90 raise PLCInvalidArgument, "Invalid IP address %s"%ip
93 def validate_mac(self, mac):
98 bytes = mac.split(":")
101 for i, byte in enumerate(bytes):
103 if byte < 0 or byte > 255:
105 bytes[i] = "%02x" % byte
106 mac = ":".join(bytes)
108 raise PLCInvalidArgument, "Invalid MAC address %s"%mac
112 validate_gateway = validate_ip
113 validate_network = validate_ip
114 validate_broadcast = validate_ip
115 validate_netmask = validate_ip
116 validate_dns1 = validate_ip
117 validate_dns2 = validate_ip
119 def validate_bwlimit(self, bwlimit):
124 raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
128 def validate_hostname(self, hostname):
133 if not PLC.Nodes.valid_hostname(hostname):
134 raise PLCInvalidArgument, "Invalid hostname %s"%hostname
138 def validate_node_id(self, node_id):
139 nodes = PLC.Nodes.Nodes(self.api, [node_id])
141 raise PLCInvalidArgument, "No such node %d"%node_id
145 def validate_is_primary(self, is_primary):
147 Set this interface to be the primary one.
151 nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
153 raise PLCInvalidArgument, "No such node %d"%node_id
156 if node['interface_ids']:
157 conflicts = Interfaces(self.api, node['interface_ids'])
158 for interface in conflicts:
159 if ('interface_id' not in self or \
160 self['interface_id'] != interface['interface_id']) and \
161 interface['is_primary']:
162 raise PLCInvalidArgument, "Can only set one primary interface per node"
168 Flush changes back to the database.
174 assert 'method' in self
175 method = self['method']
177 if method == "proxy" or method == "tap":
178 if 'mac' in self and self['mac']:
179 raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
180 if 'ip' not in self or not self['ip']:
181 raise PLCInvalidArgument, "For %s method, ip is required" % method
182 if method == "tap" and ('gateway' not in self or not self['gateway']):
183 raise PLCInvalidArgument, "For tap method, gateway is required and should be " \
184 "the IP address of the node that proxies for this address"
185 # Should check that the proxy address is reachable, but
186 # there's no way to tell if the only primary interface is
189 elif method == "static":
190 if 'is_primary' in self and self['is_primary'] is True:
191 for key in ['gateway', 'dns1']:
192 if key not in self or not self[key]:
193 raise PLCInvalidArgument, "For static method primary network, %s is required" % key
194 globals()[key] = self[key]
195 for key in ['ip', 'network', 'broadcast', 'netmask']:
196 if key not in self or not self[key]:
197 raise PLCInvalidArgument, "For static method, %s is required" % key
198 globals()[key] = self[key]
199 if not in_same_network(ip, network, netmask):
200 raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
201 (ip, network, netmask)
202 if not in_same_network(broadcast, network, netmask):
203 raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
204 (broadcast, network, netmask)
205 if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
206 raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
207 (gateway, ip, netmask)
209 elif method == "ipmi":
210 if 'ip' not in self or not self['ip']:
211 raise PLCInvalidArgument, "For ipmi method, ip is required"
213 validate_last_updated = Row.validate_timestamp
215 def update_timestamp(self, col_name, commit = True):
217 Update col_name field with current time
220 assert 'interface_id' in self
221 assert self.table_name
223 self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
224 " where interface_id = %d" % (self['interface_id']) )
227 def update_last_updated(self, commit = True):
228 self.update_timestamp('last_updated', commit)
231 class Interfaces(Table):
233 Representation of row(s) from the interfaces table in the
237 def __init__(self, api, interface_filter = None, columns = None):
238 Table.__init__(self, api, Interface, columns)
240 # the view that we're selecting upon: start with view_nodes
241 view = "view_interfaces"
242 # as many left joins as requested tags
243 for tagname in self.tag_columns:
244 view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
245 Interface.primary_key)
247 sql = "SELECT %s FROM %s WHERE True" % \
248 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
250 if interface_filter is not None:
251 if isinstance(interface_filter, (list, tuple, set)):
252 # Separate the list into integers and strings
253 ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
254 strs = filter(lambda x: isinstance(x, StringTypes), interface_filter)
255 interface_filter = Filter(Interface.fields, {'interface_id': ints, 'ip': strs})
256 sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
257 elif isinstance(interface_filter, dict):
258 allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
259 interface_filter = Filter(allowed_fields, interface_filter)
260 sql += " AND (%s) %s" % interface_filter.sql(api)
261 elif isinstance(interface_filter, int):
262 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
263 sql += " AND (%s) %s" % interface_filter.sql(api)
264 elif isinstance (interface_filter, StringTypes):
265 interface_filter = Filter(Interface.fields, {'ip':[interface_filter]})
266 sql += " AND (%s) %s" % interface_filter.sql(api, "AND")
268 raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter