merge changes from HEAD
[plcapi.git] / PLC / NodeNetworks.py
1 #
2 # Functions for interacting with the nodenetworks table in the database
3 #
4 # Mark Huang <mlhuang@cs.princeton.edu>
5 # Copyright (C) 2006 The Trustees of Princeton University
6 #
7 # $Id: NodeNetworks.py,v 1.17 2007/05/11 20:22:55 tmack Exp $
8 #
9
10 from types import StringTypes
11 import socket
12 import struct
13
14 from PLC.Faults import *
15 from PLC.Parameter import Parameter
16 from PLC.Filter import Filter
17 from PLC.Debug import profile
18 from PLC.Table import Row, Table
19 from PLC.NetworkTypes import NetworkType, NetworkTypes
20 from PLC.NetworkMethods import NetworkMethod, NetworkMethods
21 import PLC.Nodes
22
23 def valid_ip(ip):
24     try:
25         ip = socket.inet_ntoa(socket.inet_aton(ip))
26         return True
27     except socket.error:
28         return False
29
30 def in_same_network(address1, address2, netmask):
31     """
32     Returns True if two IPv4 addresses are in the same network. Faults
33     if an address is invalid.
34     """
35
36     address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
37     address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
38     netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
39
40     return (address1 & netmask) == (address2 & netmask)
41
42 class NodeNetwork(Row):
43     """
44     Representation of a row in the nodenetworks table. To use, optionally
45     instantiate with a dict of values. Update as you would a
46     dict. Commit to the database with sync().
47     """
48
49     table_name = 'nodenetworks'
50     primary_key = 'nodenetwork_id'
51     fields = {
52         'nodenetwork_id': Parameter(int, "Node interface identifier"),
53         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
54         'type': Parameter(str, "Address type (e.g., 'ipv4')"),
55         'ip': Parameter(str, "IP address", nullok = True),
56         'mac': Parameter(str, "MAC address", nullok = True),
57         'gateway': Parameter(str, "IP address of primary gateway", nullok = True),
58         'network': Parameter(str, "Subnet address", nullok = True),
59         'broadcast': Parameter(str, "Network broadcast address", nullok = True),
60         'netmask': Parameter(str, "Subnet mask", nullok = True),
61         'dns1': Parameter(str, "IP address of primary DNS server", nullok = True),
62         'dns2': Parameter(str, "IP address of secondary DNS server", nullok = True),
63         'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
64         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
65         'node_id': Parameter(int, "Node associated with this interface"),
66         'is_primary': Parameter(bool, "Is the primary interface for this node"),
67         }
68
69     def validate_method(self, method):
70         network_methods = [row['method'] for row in NetworkMethods(self.api)]
71         if method not in network_methods:
72             raise PLCInvalidArgument, "Invalid addressing method %s"%method
73         return method
74
75     def validate_type(self, type):
76         network_types = [row['type'] for row in NetworkTypes(self.api)]
77         if type not in network_types:
78             raise PLCInvalidArgument, "Invalid address type %s"%type
79         return type
80
81     def validate_ip(self, ip):
82         if ip and not valid_ip(ip):
83             raise PLCInvalidArgument, "Invalid IP address %s"%ip
84         return ip
85
86     def validate_mac(self, mac):
87         if not mac:
88             return mac
89
90         try:
91             bytes = mac.split(":")
92             if len(bytes) < 6:
93                 raise Exception
94             for i, byte in enumerate(bytes):
95                 byte = int(byte, 16)
96                 if byte < 0 or byte > 255:
97                     raise Exception
98                 bytes[i] = "%02x" % byte
99             mac = ":".join(bytes)
100         except:
101             raise PLCInvalidArgument, "Invalid MAC address %s"%mac
102
103         return mac
104
105     validate_gateway = validate_ip
106     validate_network = validate_ip
107     validate_broadcast = validate_ip
108     validate_netmask = validate_ip
109     validate_dns1 = validate_ip
110     validate_dns2 = validate_ip
111
112     def validate_bwlimit(self, bwlimit):
113         if not bwlimit:
114             return bwlimit
115
116         if bwlimit < 500000:
117             raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
118
119         return bwlimit  
120
121     def validate_hostname(self, hostname):
122         # Optional
123         if not hostname:
124             return hostname
125
126         if not PLC.Nodes.valid_hostname(hostname):
127             raise PLCInvalidArgument, "Invalid hostname %s"%hostname
128
129         return hostname
130
131     def validate_node_id(self, node_id):
132         nodes = PLC.Nodes.Nodes(self.api, [node_id])
133         if not nodes:
134             raise PLCInvalidArgument, "No such node %d"%node_id
135
136         return node_id
137
138     def validate_is_primary(self, is_primary):
139         """
140         Set this interface to be the primary one.
141         """
142
143         if is_primary:
144             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
145             if not nodes:
146                 raise PLCInvalidArgument, "No such node %d"%node_id
147             node = nodes[0]
148
149             if node['nodenetwork_ids']:
150                 conflicts = NodeNetworks(self.api, node['nodenetwork_ids'])
151                 for nodenetwork in conflicts:
152                     if ('nodenetwork_id' not in self or \
153                         self['nodenetwork_id'] != nodenetwork['nodenetwork_id']) and \
154                        nodenetwork['is_primary']:
155                         raise PLCInvalidArgument, "Can only set one primary interface per node"
156
157         return is_primary
158
159     def validate(self):
160         """
161         Flush changes back to the database.
162         """
163
164         # Basic validation
165         Row.validate(self)
166
167         assert 'method' in self
168         method = self['method']
169
170         if method == "proxy" or method == "tap":
171             if 'mac' in self and self['mac']:
172                 raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
173             if 'ip' not in self or not self['ip']:
174                 raise PLCInvalidArgument, "For %s method, ip is required" % method
175             if method == "tap" and ('gateway' not in self or not self['gateway']):
176                 raise PLCInvalidArgument, "For tap method, gateway is required and should be " \
177                       "the IP address of the node that proxies for this address"
178             # Should check that the proxy address is reachable, but
179             # there's no way to tell if the only primary interface is
180             # DHCP!
181
182         elif method == "static":
183             for key in ['ip', 'gateway', 'network', 'broadcast', 'netmask', 'dns1']:
184                 if key not in self or not self[key]:
185                     raise PLCInvalidArgument, "For static method, %s is required" % key
186                 globals()[key] = self[key]
187             if not in_same_network(ip, network, netmask):
188                 raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
189                       (ip, network, netmask)
190             if not in_same_network(broadcast, network, netmask):
191                 raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
192                       (broadcast, network, netmask)
193             if not in_same_network(ip, gateway, netmask):
194                 raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
195                       (gateway, ip, netmask)
196
197         elif method == "ipmi":
198             if 'ip' not in self or not self['ip']:
199                 raise PLCInvalidArgument, "For ipmi method, ip is required"
200
201 class NodeNetworks(Table):
202     """
203     Representation of row(s) from the nodenetworks table in the
204     database.
205     """
206
207     def __init__(self, api, nodenetwork_filter = None, columns = None):
208         Table.__init__(self, api, NodeNetwork, columns)
209
210         sql = "SELECT %s FROM nodenetworks WHERE True" % \
211               ", ".join(self.columns)
212
213         if nodenetwork_filter is not None:
214             if isinstance(nodenetwork_filter, (list, tuple, set)):
215                 nodenetwork_filter = Filter(NodeNetwork.fields, {'nodenetwork_id': nodenetwork_filter})
216             elif isinstance(nodenetwork_filter, dict):
217                 nodenetwork_filter = Filter(NodeNetwork.fields, nodenetwork_filter)
218             sql += " AND (%s)" % nodenetwork_filter.sql(api)
219
220         self.selectall(sql)