reviewing the tags permission system
[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']
50     fields = {
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),
68         }
69
70     view_tags_name = "view_interface_tags"
71     tags = {}
72
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
77         return method
78
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
83         return type
84
85     def validate_ip(self, ip):
86         if ip and not valid_ip(ip):
87             raise PLCInvalidArgument, "Invalid IP address %s"%ip
88         return ip
89
90     def validate_mac(self, mac):
91         if not mac:
92             return mac
93
94         try:
95             bytes = mac.split(":")
96             if len(bytes) < 6:
97                 raise Exception
98             for i, byte in enumerate(bytes):
99                 byte = int(byte, 16)
100                 if byte < 0 or byte > 255:
101                     raise Exception
102                 bytes[i] = "%02x" % byte
103             mac = ":".join(bytes)
104         except:
105             raise PLCInvalidArgument, "Invalid MAC address %s"%mac
106
107         return mac
108
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
115
116     def validate_bwlimit(self, bwlimit):
117         if not bwlimit:
118             return bwlimit
119
120         if bwlimit < 500000:
121             raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
122
123         return bwlimit
124
125     def validate_hostname(self, hostname):
126         # Optional
127         if not hostname:
128             return hostname
129
130         if not PLC.Nodes.valid_hostname(hostname):
131             raise PLCInvalidArgument, "Invalid hostname %s"%hostname
132
133         return hostname
134
135     def validate_node_id(self, node_id):
136         nodes = PLC.Nodes.Nodes(self.api, [node_id])
137         if not nodes:
138             raise PLCInvalidArgument, "No such node %d"%node_id
139
140         return node_id
141
142     def validate_is_primary(self, is_primary):
143         """
144         Set this interface to be the primary one.
145         """
146
147         if is_primary:
148             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
149             if not nodes:
150                 raise PLCInvalidArgument, "No such node %d"%node_id
151             node = nodes[0]
152
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"
160
161         return is_primary
162
163     def validate(self):
164         """
165         Flush changes back to the database.
166         """
167
168         # Basic validation
169         Row.validate(self)
170
171         assert 'method' in self
172         method = self['method']
173
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
184             # DHCP!
185
186         elif method == "static":
187             if 'is_primary' in self and self['is_primary'] is True:
188                 for key in ['gateway', 'dns1']:
189                     if key not in self or not self[key]:
190                         raise PLCInvalidArgument, "For static method primary network, %s is required" % key
191                     globals()[key] = self[key]
192             for key in ['ip', 'network', 'broadcast', 'netmask']:
193                 if key not in self or not self[key]:
194                     raise PLCInvalidArgument, "For static method, %s is required" % key
195                 globals()[key] = self[key]
196             if not in_same_network(ip, network, netmask):
197                 raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
198                       (ip, network, netmask)
199             if not in_same_network(broadcast, network, netmask):
200                 raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
201                       (broadcast, network, netmask)
202             if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
203                 raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
204                       (gateway, ip, netmask)
205
206         elif method == "ipmi":
207             if 'ip' not in self or not self['ip']:
208                 raise PLCInvalidArgument, "For ipmi method, ip is required"
209
210     validate_last_updated = Row.validate_timestamp
211
212     def update_timestamp(self, col_name, commit = True):
213         """
214         Update col_name field with current time
215         """
216
217         assert 'interface_id' in self
218         assert self.table_name
219
220         self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
221                        " where interface_id = %d" % (self['interface_id']) )
222         self.sync(commit)
223
224     def update_last_updated(self, commit = True):
225         self.update_timestamp('last_updated', commit)
226
227     def delete(self,commit=True):
228         ### need to cleanup ilinks
229         self.api.db.do("DELETE FROM ilink WHERE src_interface_id=%d OR dst_interface_id=%d" % \
230                            (self['interface_id'],self['interface_id']))
231         
232         Row.delete(self)
233
234 class Interfaces(Table):
235     """
236     Representation of row(s) from the interfaces table in the
237     database.
238     """
239
240     def __init__(self, api, interface_filter = None, columns = None):
241         Table.__init__(self, api, Interface, columns)
242
243         # the view that we're selecting upon: start with view_nodes
244         view = "view_interfaces"
245         # as many left joins as requested tags
246         for tagname in self.tag_columns:
247             view= "%s left join %s using (%s)"%(view,Interface.tagvalue_view_name(tagname),
248                                                 Interface.primary_key)
249
250         sql = "SELECT %s FROM %s WHERE True" % \
251             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
252
253         if interface_filter is not None:
254             if isinstance(interface_filter, (list, tuple, set)):
255                 # Separate the list into integers and strings
256                 ints = filter(lambda x: isinstance(x, (int, long)), interface_filter)
257                 strs = filter(lambda x: isinstance(x, StringTypes), interface_filter)
258                 interface_filter = Filter(Interface.fields, {'interface_id': ints, 'ip': strs})
259                 sql += " AND (%s) %s" % interface_filter.sql(api, "OR")
260             elif isinstance(interface_filter, dict):
261                 allowed_fields=dict(Interface.fields.items()+Interface.tags.items())
262                 interface_filter = Filter(allowed_fields, interface_filter)
263                 sql += " AND (%s) %s" % interface_filter.sql(api)
264             elif isinstance(interface_filter, int):
265                 interface_filter = Filter(Interface.fields, {'interface_id': [interface_filter]})
266                 sql += " AND (%s) %s" % interface_filter.sql(api)
267             elif isinstance (interface_filter, StringTypes):
268                 interface_filter = Filter(Interface.fields, {'ip':[interface_filter]})
269                 sql += " AND (%s) %s" % interface_filter.sql(api, "AND")
270             else:
271                 raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter
272
273         self.selectall(sql)