Techs can view slices that are running on nodes at their site
[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             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
191                 else:
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)
206
207         elif method == "ipmi":
208             if 'ip' not in self or not self['ip']:
209                 raise PLCInvalidArgument, "For ipmi method, ip is required"
210
211     validate_last_updated = Row.validate_timestamp
212
213     def update_timestamp(self, col_name, commit = True):
214         """
215         Update col_name field with current time
216         """
217
218         assert 'interface_id' in self
219         assert self.table_name
220
221         self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
222                        " where interface_id = %d" % (self['interface_id']) )
223         self.sync(commit)
224
225     def update_last_updated(self, commit = True):
226         self.update_timestamp('last_updated', commit)
227
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']))
232         
233         Row.delete(self)
234
235 class Interfaces(Table):
236     """
237     Representation of row(s) from the interfaces table in the
238     database.
239     """
240
241     def __init__(self, api, interface_filter = None, columns = None):
242         Table.__init__(self, api, Interface, columns)
243
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)
250
251         sql = "SELECT %s FROM %s WHERE True" % \
252             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
253
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")
271             else:
272                 raise PLCInvalidArgument, "Wrong interface filter %r"%interface_filter
273
274         self.selectall(sql)