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.NodeNetworks import NodeNetwork, NodeNetworks
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 nodenetworks so the related NodeNetworkSettings get deleted too
42 join_tables = ['nodegroup_node', 'conf_file_node', 'pcu_node', 'slice_node', 'slice_attribute', 'node_session', 'peer_node','node_slice_whitelist']
44 'node_id': Parameter(int, "Node identifier"),
45 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
46 'site_id': Parameter(int, "Site at which this node is located"),
47 'boot_state': Parameter(str, "Boot state", max = 20),
48 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
49 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
50 'version': Parameter(str, "Apparent Boot CD version", max = 64),
51 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
52 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
53 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
54 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
55 'key': Parameter(str, "(Admin only) Node key", max = 256),
56 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
57 'nodenetwork_ids': Parameter([int], "List of network interfaces that this node has"),
58 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
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),
69 'nodenetworks': [Mixed(Parameter(int, "NodeNetwork identifier"),
70 Filter(NodeNetwork.fields))],
71 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
72 Parameter(str, "NodeGroup name"))],
73 'conf_files': [Parameter(int, "ConfFile identifier")],
74 'slices': [Mixed(Parameter(int, "Slice identifier"),
75 Parameter(str, "Slice name"))],
76 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
77 Parameter(str, "Slice name"))]
80 class_key = 'hostname'
81 foreign_fields = ['boot_state','model','version']
82 # forget about these ones, they are read-only anyway
83 # handling them causes Cache to re-sync all over again
84 # 'date_created','last_updated'
86 # in this case, we dont need the 'table' but Cache will look it up, so...
87 {'field' : 'site_id' , 'class' : 'Site' , 'table' : 'unused-on-direct-refs' } ,
90 def validate_hostname(self, hostname):
91 if not valid_hostname(hostname):
92 raise PLCInvalidArgument, "Invalid hostname"
94 conflicts = Nodes(self.api, [hostname])
95 for node in conflicts:
96 if 'node_id' not in self or self['node_id'] != node['node_id']:
97 raise PLCInvalidArgument, "Hostname already in use"
101 def validate_boot_state(self, boot_state):
102 boot_states = [row['boot_state'] for row in BootStates(self.api)]
103 if boot_state not in boot_states:
104 raise PLCInvalidArgument, "Invalid boot state"
108 validate_date_created = Row.validate_timestamp
109 validate_last_updated = Row.validate_timestamp
110 validate_last_contact = Row.validate_timestamp
112 def update_last_contact(self, commit = True):
114 Update last_contact field with current time
117 assert 'node_id' in self
118 assert self.table_name
120 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
121 " where node_id = %d" % ( self['node_id']) )
125 def update_last_updated(self, commit = True):
127 Update last_updated field with current time
130 assert 'node_id' in self
131 assert self.table_name
133 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
134 " where node_id = %d" % (self['node_id']) )
137 def associate_nodenetworks(self, auth, field, value):
139 Delete nodenetworks not found in value list (using DeleteNodeNetwor)k
140 Add nodenetworks found in value list (using AddNodeNetwork)
141 Updates nodenetworks found w/ nodenetwork_id in value list (using UpdateNodeNetwork)
144 assert 'nodenetworkp_ids' in self
145 assert 'node_id' in self
146 assert isinstance(value, list)
148 (nodenetwork_ids, blank, nodenetworks) = self.separate_types(value)
150 if self['nodenetwork_ids'] != nodenetwork_ids:
151 from PLC.Methods.DeleteNodeNetwork import DeleteNodeNetwork
153 stale_nodenetworks = set(self['nodenetwork_ids']).difference(nodenetwork_ids)
155 for stale_nodenetwork in stale_nodenetworks:
156 DeleteNodeNetwork.__call__(DeleteNodeNetwork(self.api), auth, stale_nodenetwork['nodenetwork_id'])
158 def associate_nodegroups(self, auth, field, value):
160 Add node to nodegroups found in value list (AddNodeToNodegroup)
161 Delete node from nodegroup not found in value list (DeleteNodeFromNodegroup)
164 from PLC.NodeGroups import NodeGroups
166 assert 'nodegroup_ids' in self
167 assert 'node_id' in self
168 assert isinstance(value, list)
170 (nodegroup_ids, nodegroup_names) = self.separate_types(value)[0:2]
173 nodegroups = NodeGroups(self.api, nodegroup_names, ['nodegroup_id']).dict('nodegroup_id')
174 nodegroup_ids += nodegroups.keys()
176 if self['nodegroup_ids'] != nodegroup_ids:
177 from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
178 from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
180 new_nodegroups = set(nodegroup_ids).difference(self['nodegroup_ids'])
181 stale_nodegroups = set(self['nodegroup_ids']).difference(nodegroup_ids)
183 for new_nodegroup in new_nodegroups:
184 AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, self['node_id'], new_nodegroup)
185 for stale_nodegroup in stale_nodegroups:
186 DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, self['node_id'], stale_nodegroup)
190 def associate_conf_files(self, auth, field, value):
192 Add conf_files found in value list (AddConfFileToNode)
193 Delets conf_files not found in value list (DeleteConfFileFromNode)
196 assert 'conf_file_ids' in self
197 assert 'node_id' in self
198 assert isinstance(value, list)
200 conf_file_ids = self.separate_types(value)[0]
202 if self['conf_file_ids'] != conf_file_ids:
203 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
204 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
205 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
206 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
208 for new_conf_file in new_conf_files:
209 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
210 for stale_conf_file in stale_conf_files:
211 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
214 def associate_slices(self, auth, field, value):
216 Add slices found in value list to (AddSliceToNode)
217 Delete slices not found in value list (DeleteSliceFromNode)
220 from PLC.Slices import Slices
222 assert 'slice_ids' in self
223 assert 'node_id' in self
224 assert isinstance(value, list)
226 (slice_ids, slice_names) = self.separate_types(value)[0:2]
229 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
230 slice_ids += slices.keys()
232 if self['slice_ids'] != slice_ids:
233 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
234 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
235 new_slices = set(slice_ids).difference(self['slice_ids'])
236 stale_slices = set(self['slice_ids']).difference(slice_ids)
238 for new_slice in new_slices:
239 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
240 for stale_slice in stale_slices:
241 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
243 def associate_slices_whitelist(self, auth, field, value):
245 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
246 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
249 from PLC.Slices import Slices
251 assert 'slice_ids_whitelist' in self
252 assert 'node_id' in self
253 assert isinstance(value, list)
255 (slice_ids, slice_names) = self.separate_types(value)[0:2]
258 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
259 slice_ids += slices.keys()
261 if self['slice_ids_whitelist'] != slice_ids:
262 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
263 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
264 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
265 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
267 for new_slice in new_slices:
268 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
269 for stale_slice in stale_slices:
270 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
273 def delete(self, commit = True):
275 Delete existing node.
278 assert 'node_id' in self
279 assert 'nodenetwork_ids' in self
281 # we need to clean up NodeNetworkSettings, so handling nodenetworks as part of join_tables does not work
282 for nodenetwork in NodeNetworks(self.api,self['nodenetwork_ids']):
285 # Clean up miscellaneous join tables
286 for table in self.join_tables:
287 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
288 (table, self['node_id']))
291 self['deleted'] = True
297 Representation of row(s) from the nodes table in the
301 def __init__(self, api, node_filter = None, columns = None):
302 Table.__init__(self, api, Node, columns)
304 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
305 ", ".join(self.columns)
307 if node_filter is not None:
308 if isinstance(node_filter, (list, tuple, set)):
309 # Separate the list into integers and strings
310 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
311 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
312 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
313 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
314 elif isinstance(node_filter, dict):
315 node_filter = Filter(Node.fields, node_filter)
316 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
317 elif isinstance (node_filter, StringTypes):
318 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
319 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
320 elif isinstance (node_filter, int):
321 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
322 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
324 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter