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 # Thierry -- we use delete on interfaces so the related InterfaceSettings get deleted too
42 join_tables = [ 'slice_node', 'peer_node', 'slice_attribute',
43 'node_session', 'node_slice_whitelist',
44 'node_tag', 'conf_file_node', 'pcu_node', ]
46 'node_id': Parameter(int, "Node identifier"),
47 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
48 'site_id': Parameter(int, "Site at which this node is located"),
49 'boot_state': Parameter(str, "Boot state", max = 20),
50 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
51 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
52 'version': Parameter(str, "Apparent Boot CD version", max = 64),
53 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
54 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
55 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
56 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
57 'key': Parameter(str, "(Admin only) Node key", max = 256),
58 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
59 'interface_ids': Parameter([int], "List of network interfaces that this node has"),
60 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
61 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
62 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
63 'slice_ids': Parameter([int], "List of slices on this node"),
64 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
65 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
66 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
67 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
68 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
69 'tag_ids' : Parameter ([int], "List of tags attached to this node"),
72 'interfaces': [Mixed(Parameter(int, "Interface identifier"),
73 Filter(Interface.fields))],
74 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
75 Parameter(str, "NodeGroup name"))],
76 'conf_files': [Parameter(int, "ConfFile identifier")],
77 'slices': [Mixed(Parameter(int, "Slice identifier"),
78 Parameter(str, "Slice name"))],
79 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
80 Parameter(str, "Slice name"))]
83 class_key = 'hostname'
84 foreign_fields = ['boot_state','model','version']
85 # forget about these ones, they are read-only anyway
86 # handling them causes Cache to re-sync all over again
87 # 'date_created','last_updated'
89 # in this case, we dont need the 'table' but Cache will look it up, so...
90 {'field' : 'site_id' , 'class' : 'Site' , 'table' : 'unused-on-direct-refs' } ,
93 def validate_hostname(self, hostname):
94 if not valid_hostname(hostname):
95 raise PLCInvalidArgument, "Invalid hostname"
97 conflicts = Nodes(self.api, [hostname])
98 for node in conflicts:
99 if 'node_id' not in self or self['node_id'] != node['node_id']:
100 raise PLCInvalidArgument, "Hostname already in use"
104 def validate_boot_state(self, boot_state):
105 boot_states = [row['boot_state'] for row in BootStates(self.api)]
106 if boot_state not in boot_states:
107 raise PLCInvalidArgument, "Invalid boot state"
111 validate_date_created = Row.validate_timestamp
112 validate_last_updated = Row.validate_timestamp
113 validate_last_contact = Row.validate_timestamp
115 def update_last_contact(self, commit = True):
117 Update last_contact field with current time
120 assert 'node_id' in self
121 assert self.table_name
123 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
124 " where node_id = %d" % ( self['node_id']) )
128 def update_last_updated(self, commit = True):
130 Update last_updated field with current time
133 assert 'node_id' in self
134 assert self.table_name
136 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
137 " where node_id = %d" % (self['node_id']) )
140 def associate_interfaces(self, auth, field, value):
142 Delete interfaces not found in value list (using DeleteNodeNetwor)k
143 Add interfaces found in value list (using AddInterface)
144 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
147 assert 'interfacep_ids' in self
148 assert 'node_id' in self
149 assert isinstance(value, list)
151 (interface_ids, blank, interfaces) = self.separate_types(value)
153 if self['interface_ids'] != interface_ids:
154 from PLC.Methods.DeleteInterface import DeleteInterface
156 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
158 for stale_interface in stale_interfaces:
159 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
161 def associate_nodegroups(self, auth, field, value):
163 Add node to nodegroups found in value list (AddNodeToNodegroup)
164 Delete node from nodegroup not found in value list (DeleteNodeFromNodegroup)
167 from PLC.NodeGroups import NodeGroups
169 assert 'nodegroup_ids' in self
170 assert 'node_id' in self
171 assert isinstance(value, list)
173 (nodegroup_ids, nodegroup_names) = self.separate_types(value)[0:2]
176 nodegroups = NodeGroups(self.api, nodegroup_names, ['nodegroup_id']).dict('nodegroup_id')
177 nodegroup_ids += nodegroups.keys()
179 if self['nodegroup_ids'] != nodegroup_ids:
180 from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
181 from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
183 new_nodegroups = set(nodegroup_ids).difference(self['nodegroup_ids'])
184 stale_nodegroups = set(self['nodegroup_ids']).difference(nodegroup_ids)
186 for new_nodegroup in new_nodegroups:
187 AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, self['node_id'], new_nodegroup)
188 for stale_nodegroup in stale_nodegroups:
189 DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, self['node_id'], stale_nodegroup)
193 def associate_conf_files(self, auth, field, value):
195 Add conf_files found in value list (AddConfFileToNode)
196 Delets conf_files not found in value list (DeleteConfFileFromNode)
199 assert 'conf_file_ids' in self
200 assert 'node_id' in self
201 assert isinstance(value, list)
203 conf_file_ids = self.separate_types(value)[0]
205 if self['conf_file_ids'] != conf_file_ids:
206 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
207 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
208 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
209 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
211 for new_conf_file in new_conf_files:
212 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
213 for stale_conf_file in stale_conf_files:
214 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
217 def associate_slices(self, auth, field, value):
219 Add slices found in value list to (AddSliceToNode)
220 Delete slices not found in value list (DeleteSliceFromNode)
223 from PLC.Slices import Slices
225 assert 'slice_ids' in self
226 assert 'node_id' in self
227 assert isinstance(value, list)
229 (slice_ids, slice_names) = self.separate_types(value)[0:2]
232 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
233 slice_ids += slices.keys()
235 if self['slice_ids'] != slice_ids:
236 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
237 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
238 new_slices = set(slice_ids).difference(self['slice_ids'])
239 stale_slices = set(self['slice_ids']).difference(slice_ids)
241 for new_slice in new_slices:
242 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
243 for stale_slice in stale_slices:
244 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
246 def associate_slices_whitelist(self, auth, field, value):
248 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
249 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
252 from PLC.Slices import Slices
254 assert 'slice_ids_whitelist' in self
255 assert 'node_id' in self
256 assert isinstance(value, list)
258 (slice_ids, slice_names) = self.separate_types(value)[0:2]
261 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
262 slice_ids += slices.keys()
264 if self['slice_ids_whitelist'] != slice_ids:
265 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
266 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
267 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
268 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
270 for new_slice in new_slices:
271 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
272 for stale_slice in stale_slices:
273 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
276 def delete(self, commit = True):
278 Delete existing node.
281 assert 'node_id' in self
282 assert 'interface_ids' in self
284 # we need to clean up InterfaceSettings, so handling interfaces as part of join_tables does not work
285 for interface in Interfaces(self.api,self['interface_ids']):
288 # Clean up miscellaneous join tables
289 for table in self.join_tables:
290 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
291 (table, self['node_id']))
294 self['deleted'] = True
300 Representation of row(s) from the nodes table in the
304 def __init__(self, api, node_filter = None, columns = None):
305 Table.__init__(self, api, Node, columns)
307 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
308 ", ".join(self.columns)
310 if node_filter is not None:
311 if isinstance(node_filter, (list, tuple, set)):
312 # Separate the list into integers and strings
313 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
314 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
315 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
316 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
317 elif isinstance(node_filter, dict):
318 node_filter = Filter(Node.fields, node_filter)
319 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
320 elif isinstance (node_filter, StringTypes):
321 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
322 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
323 elif isinstance (node_filter, int):
324 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
325 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
327 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter