2 # Functions for interacting with the nodes table in the database
4 # Mark Huang <mlhuang@cs.princeton.edu>
5 # Copyright (C) 2006 The Trustees of Princeton University
10 from types import StringTypes
13 from PLC.Faults import *
14 from PLC.Parameter import Parameter, Mixed
15 from PLC.Filter import Filter
16 from PLC.Debug import profile
17 from PLC.Table import Row, Table
18 from PLC.Interfaces import Interface, Interfaces
19 from PLC.BootStates import BootStates
21 def valid_hostname(hostname):
22 # 1. Each part begins and ends with a letter or number.
23 # 2. Each part except the last can contain letters, numbers, or hyphens.
24 # 3. Each part is between 1 and 64 characters, including the trailing dot.
25 # 4. At least two parts.
26 # 5. Last part can only contain between 2 and 6 letters.
27 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
30 re.match(good_hostname, hostname, re.IGNORECASE)
34 Representation of a row in the nodes table. To use, optionally
35 instantiate with a dict of values. Update as you would a
36 dict. Commit to the database with sync().
40 primary_key = 'node_id'
41 join_tables = [ 'slice_node', 'peer_node', 'slice_attribute',
42 'node_session', 'node_slice_whitelist',
43 'node_tag', 'conf_file_node', 'pcu_node', ]
45 'node_id': Parameter(int, "Node identifier"),
46 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
47 'site_id': Parameter(int, "Site at which this node is located"),
48 'boot_state': Parameter(str, "Boot state", max = 20),
49 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
50 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
51 'version': Parameter(str, "Apparent Boot CD version", max = 64),
52 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
53 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
54 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
55 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
56 'key': Parameter(str, "(Admin only) Node key", max = 256),
57 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
58 'interface_ids': Parameter([int], "List of network interfaces that this node has"),
59 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
60 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
61 'slice_ids': Parameter([int], "List of slices on this node"),
62 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
63 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
64 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
65 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
66 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
67 'tag_ids' : Parameter ([int], "List of tags attached to this node"),
68 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
71 'interfaces': [Mixed(Parameter(int, "Interface identifier"),
72 Filter(Interface.fields))],
73 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
74 Parameter(str, "NodeGroup name"))],
75 'conf_files': [Parameter(int, "ConfFile identifier")],
76 'slices': [Mixed(Parameter(int, "Slice identifier"),
77 Parameter(str, "Slice name"))],
78 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
79 Parameter(str, "Slice name"))]
82 def validate_hostname(self, hostname):
83 if not valid_hostname(hostname):
84 raise PLCInvalidArgument, "Invalid hostname"
86 conflicts = Nodes(self.api, [hostname])
87 for node in conflicts:
88 if 'node_id' not in self or self['node_id'] != node['node_id']:
89 raise PLCInvalidArgument, "Hostname already in use"
93 def validate_boot_state(self, boot_state):
94 boot_states = [row['boot_state'] for row in BootStates(self.api)]
95 if boot_state not in boot_states:
96 raise PLCInvalidArgument, "Invalid boot state"
100 validate_date_created = Row.validate_timestamp
101 validate_last_updated = Row.validate_timestamp
102 validate_last_contact = Row.validate_timestamp
104 def update_last_contact(self, commit = True):
106 Update last_contact field with current time
109 assert 'node_id' in self
110 assert self.table_name
112 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
113 " where node_id = %d" % ( self['node_id']) )
117 def update_last_updated(self, commit = True):
119 Update last_updated field with current time
122 assert 'node_id' in self
123 assert self.table_name
125 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
126 " where node_id = %d" % (self['node_id']) )
129 def associate_interfaces(self, auth, field, value):
131 Delete interfaces not found in value list (using DeleteInterface)
132 Add interfaces found in value list (using AddInterface)
133 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
136 assert 'interface_ids' in self
137 assert 'node_id' in self
138 assert isinstance(value, list)
140 (interface_ids, blank, interfaces) = self.separate_types(value)
142 if self['interface_ids'] != interface_ids:
143 from PLC.Methods.DeleteInterface import DeleteInterface
145 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
147 for stale_interface in stale_interfaces:
148 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
150 def associate_conf_files(self, auth, field, value):
152 Add conf_files found in value list (AddConfFileToNode)
153 Delets conf_files not found in value list (DeleteConfFileFromNode)
156 assert 'conf_file_ids' in self
157 assert 'node_id' in self
158 assert isinstance(value, list)
160 conf_file_ids = self.separate_types(value)[0]
162 if self['conf_file_ids'] != conf_file_ids:
163 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
164 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
165 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
166 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
168 for new_conf_file in new_conf_files:
169 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
170 for stale_conf_file in stale_conf_files:
171 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
173 def associate_slices(self, auth, field, value):
175 Add slices found in value list to (AddSliceToNode)
176 Delete slices not found in value list (DeleteSliceFromNode)
179 from PLC.Slices import Slices
181 assert 'slice_ids' in self
182 assert 'node_id' in self
183 assert isinstance(value, list)
185 (slice_ids, slice_names) = self.separate_types(value)[0:2]
188 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
189 slice_ids += slices.keys()
191 if self['slice_ids'] != slice_ids:
192 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
193 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
194 new_slices = set(slice_ids).difference(self['slice_ids'])
195 stale_slices = set(self['slice_ids']).difference(slice_ids)
197 for new_slice in new_slices:
198 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
199 for stale_slice in stale_slices:
200 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
202 def associate_slices_whitelist(self, auth, field, value):
204 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
205 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
208 from PLC.Slices import Slices
210 assert 'slice_ids_whitelist' in self
211 assert 'node_id' in self
212 assert isinstance(value, list)
214 (slice_ids, slice_names) = self.separate_types(value)[0:2]
217 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
218 slice_ids += slices.keys()
220 if self['slice_ids_whitelist'] != slice_ids:
221 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
222 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
223 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
224 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
226 for new_slice in new_slices:
227 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
228 for stale_slice in stale_slices:
229 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
232 def delete(self, commit = True):
234 Delete existing node.
237 assert 'node_id' in self
238 assert 'interface_ids' in self
240 # we need to clean up InterfaceSettings, so handling interfaces as part of join_tables does not work
241 for interface in Interfaces(self.api,self['interface_ids']):
244 # Clean up miscellaneous join tables
245 for table in self.join_tables:
246 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
247 (table, self['node_id']))
250 self['deleted'] = True
256 Representation of row(s) from the nodes table in the
260 def __init__(self, api, node_filter = None, columns = None):
261 Table.__init__(self, api, Node, columns)
263 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
264 ", ".join(self.columns)
266 if node_filter is not None:
267 if isinstance(node_filter, (list, tuple, set)):
268 # Separate the list into integers and strings
269 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
270 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
271 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
272 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
273 elif isinstance(node_filter, dict):
274 node_filter = Filter(Node.fields, node_filter)
275 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
276 elif isinstance (node_filter, StringTypes):
277 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
278 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
279 elif isinstance (node_filter, int):
280 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
281 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
283 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter