Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[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 5574 2007-10-25 20:33:17Z thierry $
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     join_tables = ['nodenetwork_setting']
52     fields = {
53         'nodenetwork_id': Parameter(int, "Node interface identifier"),
54         'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
55         'type': Parameter(str, "Address type (e.g., 'ipv4')"),
56         'ip': Parameter(str, "IP address", nullok = True),
57         'mac': Parameter(str, "MAC address", nullok = True),
58         'gateway': Parameter(str, "IP address of primary gateway", nullok = True),
59         'network': Parameter(str, "Subnet address", nullok = True),
60         'broadcast': Parameter(str, "Network broadcast address", nullok = True),
61         'netmask': Parameter(str, "Subnet mask", nullok = True),
62         'dns1': Parameter(str, "IP address of primary DNS server", nullok = True),
63         'dns2': Parameter(str, "IP address of secondary DNS server", nullok = True),
64         'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
65         'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
66         'node_id': Parameter(int, "Node associated with this interface"),
67         'is_primary': Parameter(bool, "Is the primary interface for this node"),
68         'nodenetwork_setting_ids' : Parameter([int], "List of nodenetwork settings"),
69         }
70
71     def validate_method(self, method):
72         network_methods = [row['method'] for row in NetworkMethods(self.api)]
73         if method not in network_methods:
74             raise PLCInvalidArgument, "Invalid addressing method %s"%method
75         return method
76
77     def validate_type(self, type):
78         network_types = [row['type'] for row in NetworkTypes(self.api)]
79         if type not in network_types:
80             raise PLCInvalidArgument, "Invalid address type %s"%type
81         return type
82
83     def validate_ip(self, ip):
84         if ip and not valid_ip(ip):
85             raise PLCInvalidArgument, "Invalid IP address %s"%ip
86         return ip
87
88     def validate_mac(self, mac):
89         if not mac:
90             return mac
91
92         try:
93             bytes = mac.split(":")
94             if len(bytes) < 6:
95                 raise Exception
96             for i, byte in enumerate(bytes):
97                 byte = int(byte, 16)
98                 if byte < 0 or byte > 255:
99                     raise Exception
100                 bytes[i] = "%02x" % byte
101             mac = ":".join(bytes)
102         except:
103             raise PLCInvalidArgument, "Invalid MAC address %s"%mac
104
105         return mac
106
107     validate_gateway = validate_ip
108     validate_network = validate_ip
109     validate_broadcast = validate_ip
110     validate_netmask = validate_ip
111     validate_dns1 = validate_ip
112     validate_dns2 = validate_ip
113
114     def validate_bwlimit(self, bwlimit):
115         if not bwlimit:
116             return bwlimit
117
118         if bwlimit < 500000:
119             raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
120
121         return bwlimit  
122
123     def validate_hostname(self, hostname):
124         # Optional
125         if not hostname:
126             return hostname
127
128         if not PLC.Nodes.valid_hostname(hostname):
129             raise PLCInvalidArgument, "Invalid hostname %s"%hostname
130
131         return hostname
132
133     def validate_node_id(self, node_id):
134         nodes = PLC.Nodes.Nodes(self.api, [node_id])
135         if not nodes:
136             raise PLCInvalidArgument, "No such node %d"%node_id
137
138         return node_id
139
140     def validate_is_primary(self, is_primary):
141         """
142         Set this interface to be the primary one.
143         """
144
145         if is_primary:
146             nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
147             if not nodes:
148                 raise PLCInvalidArgument, "No such node %d"%node_id
149             node = nodes[0]
150
151             if node['nodenetwork_ids']:
152                 conflicts = NodeNetworks(self.api, node['nodenetwork_ids'])
153                 for nodenetwork in conflicts:
154                     if ('nodenetwork_id' not in self or \
155                         self['nodenetwork_id'] != nodenetwork['nodenetwork_id']) and \
156                        nodenetwork['is_primary']:
157                         raise PLCInvalidArgument, "Can only set one primary interface per node"
158
159         return is_primary
160
161     def validate(self):
162         """
163         Flush changes back to the database.
164         """
165
166         # Basic validation
167         Row.validate(self)
168
169         assert 'method' in self
170         method = self['method']
171
172         if method == "proxy" or method == "tap":
173             if 'mac' in self and self['mac']:
174                 raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
175             if 'ip' not in self or not self['ip']:
176                 raise PLCInvalidArgument, "For %s method, ip is required" % method
177             if method == "tap" and ('gateway' not in self or not self['gateway']):
178                 raise PLCInvalidArgument, "For tap method, gateway is required and should be " \
179                       "the IP address of the node that proxies for this address"
180             # Should check that the proxy address is reachable, but
181             # there's no way to tell if the only primary interface is
182             # DHCP!
183
184         elif method == "static":
185             for key in ['ip', 'gateway', 'network', 'broadcast', 'netmask', 'dns1']:
186                 if key not in self or not self[key]:
187                     raise PLCInvalidArgument, "For static method, %s is required" % key
188                 globals()[key] = self[key]
189             if not in_same_network(ip, network, netmask):
190                 raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
191                       (ip, network, netmask)
192             if not in_same_network(broadcast, network, netmask):
193                 raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
194                       (broadcast, network, netmask)
195             if not in_same_network(ip, gateway, netmask):
196                 raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
197                       (gateway, ip, netmask)
198
199         elif method == "ipmi":
200             if 'ip' not in self or not self['ip']:
201                 raise PLCInvalidArgument, "For ipmi method, ip is required"
202
203 class NodeNetworks(Table):
204     """
205     Representation of row(s) from the nodenetworks table in the
206     database.
207     """
208
209     def __init__(self, api, nodenetwork_filter = None, columns = None):
210         Table.__init__(self, api, NodeNetwork, columns)
211
212         sql = "SELECT %s FROM view_nodenetworks WHERE True" % \
213               ", ".join(self.columns)
214
215         if nodenetwork_filter is not None:
216             if isinstance(nodenetwork_filter, (list, tuple, set)):
217                 nodenetwork_filter = Filter(NodeNetwork.fields, {'nodenetwork_id': nodenetwork_filter})
218             elif isinstance(nodenetwork_filter, dict):
219                 nodenetwork_filter = Filter(NodeNetwork.fields, nodenetwork_filter)
220             elif isinstance(nodenetwork_filter, int):
221                 nodenetwork_filter = Filter(NodeNetwork.fields, {'nodenetwork_id': [nodenetwork_filter]})
222             else:
223                 raise PLCInvalidArgument, "Wrong node network filter %r"%nodenetwork_filter
224             sql += " AND (%s) %s" % nodenetwork_filter.sql(api)
225
226         self.selectall(sql)