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
7 # $Id: Nodes.py 5654 2007-11-06 03:43:55Z tmack $
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 'uuid': Parameter(str, "Universal Unique Identifier"),
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 'nodenetwork_ids': Parameter([int], "List of network interfaces that this node has"),
59 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
60 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
61 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
62 'slice_ids': Parameter([int], "List of slices on this node"),
63 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
64 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
65 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
66 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
67 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
70 'nodenetworks': [Mixed(Parameter(int, "NodeNetwork identifier"),
71 Filter(NodeNetwork.fields))],
72 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
73 Parameter(str, "NodeGroup name"))],
74 'conf_files': [Parameter(int, "ConfFile identifier")],
75 'slices': [Mixed(Parameter(int, "Slice identifier"),
76 Parameter(str, "Slice name"))],
77 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
78 Parameter(str, "Slice name"))]
81 class_key = 'hostname'
82 foreign_fields = ['boot_state','model','version','uuid']
83 # forget about these ones, they are read-only anyway
84 # handling them causes Cache to re-sync all over again
85 # 'date_created','last_updated'
87 # in this case, we dont need the 'table' but Cache will look it up, so...
88 {'field' : 'site_id' , 'class' : 'Site' , 'table' : 'unused-on-direct-refs' } ,
91 def validate_hostname(self, hostname):
92 if not valid_hostname(hostname):
93 raise PLCInvalidArgument, "Invalid hostname"
95 conflicts = Nodes(self.api, [hostname])
96 for node in conflicts:
97 if 'node_id' not in self or self['node_id'] != node['node_id']:
98 raise PLCInvalidArgument, "Hostname already in use"
102 def validate_boot_state(self, boot_state):
103 boot_states = [row['boot_state'] for row in BootStates(self.api)]
104 if boot_state not in boot_states:
105 raise PLCInvalidArgument, "Invalid boot state"
109 validate_date_created = Row.validate_timestamp
110 validate_last_updated = Row.validate_timestamp
111 validate_last_contact = Row.validate_timestamp
113 def update_last_contact(self, commit = True):
115 Update last_contact field with current time
118 assert 'node_id' in self
119 assert self.table_name
121 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
122 " where node_id = %d" % ( self['node_id']) )
126 def update_last_updated(self, commit = True):
128 Update last_updated field with current time
131 assert 'node_id' in self
132 assert self.table_name
134 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
135 " where node_id = %d" % (self['node_id']) )
138 def associate_nodenetworks(self, auth, field, value):
140 Delete nodenetworks not found in value list (using DeleteNodeNetwor)k
141 Add nodenetworks found in value list (using AddNodeNetwork)
142 Updates nodenetworks found w/ nodenetwork_id in value list (using UpdateNodeNetwork)
145 assert 'nodenetworkp_ids' in self
146 assert 'node_id' in self
147 assert isinstance(value, list)
149 (nodenetwork_ids, blank, nodenetworks) = self.separate_types(value)
151 if self['nodenetwork_ids'] != nodenetwork_ids:
152 from PLC.Methods.DeleteNodeNetwork import DeleteNodeNetwork
154 stale_nodenetworks = set(self['nodenetwork_ids']).difference(nodenetwork_ids)
156 for stale_nodenetwork in stale_nodenetworks:
157 DeleteNodeNetwork.__call__(DeleteNodeNetwork(self.api), auth, stale_nodenetwork['nodenetwork_id'])
159 def associate_nodegroups(self, auth, field, value):
161 Add node to nodegroups found in value list (AddNodeToNodegroup)
162 Delete node from nodegroup not found in value list (DeleteNodeFromNodegroup)
165 from PLC.NodeGroups import NodeGroups
167 assert 'nodegroup_ids' in self
168 assert 'node_id' in self
169 assert isinstance(value, list)
171 (nodegroup_ids, nodegroup_names) = self.separate_types(value)[0:2]
174 nodegroups = NodeGroups(self.api, nodegroup_names, ['nodegroup_id']).dict('nodegroup_id')
175 nodegroup_ids += nodegroups.keys()
177 if self['nodegroup_ids'] != nodegroup_ids:
178 from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
179 from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
181 new_nodegroups = set(nodegroup_ids).difference(self['nodegroup_ids'])
182 stale_nodegroups = set(self['nodegroup_ids']).difference(nodegroup_ids)
184 for new_nodegroup in new_nodegroups:
185 AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, self['node_id'], new_nodegroup)
186 for stale_nodegroup in stale_nodegroups:
187 DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, self['node_id'], stale_nodegroup)
191 def associate_conf_files(self, auth, field, value):
193 Add conf_files found in value list (AddConfFileToNode)
194 Delets conf_files not found in value list (DeleteConfFileFromNode)
197 assert 'conf_file_ids' in self
198 assert 'node_id' in self
199 assert isinstance(value, list)
201 conf_file_ids = self.separate_types(value)[0]
203 if self['conf_file_ids'] != conf_file_ids:
204 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
205 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
206 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
207 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
209 for new_conf_file in new_conf_files:
210 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
211 for stale_conf_file in stale_conf_files:
212 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
215 def associate_slices(self, auth, field, value):
217 Add slices found in value list to (AddSliceToNode)
218 Delete slices not found in value list (DeleteSliceFromNode)
221 from PLC.Slices import Slices
223 assert 'slice_ids' in self
224 assert 'node_id' in self
225 assert isinstance(value, list)
227 (slice_ids, slice_names) = self.separate_types(value)[0:2]
230 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
231 slice_ids += slices.keys()
233 if self['slice_ids'] != slice_ids:
234 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
235 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
236 new_slices = set(slice_ids).difference(self['slice_ids'])
237 stale_slices = set(self['slice_ids']).difference(slice_ids)
239 for new_slice in new_slices:
240 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
241 for stale_slice in stale_slices:
242 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
244 def associate_slices_whitelist(self, auth, field, value):
246 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
247 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
250 from PLC.Slices import Slices
252 assert 'slice_ids_whitelist' in self
253 assert 'node_id' in self
254 assert isinstance(value, list)
256 (slice_ids, slice_names) = self.separate_types(value)[0:2]
259 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
260 slice_ids += slices.keys()
262 if self['slice_ids_whitelist'] != slice_ids:
263 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
264 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
265 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
266 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
268 for new_slice in new_slices:
269 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
270 for stale_slice in stale_slices:
271 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
274 def delete(self, commit = True):
276 Delete existing node.
279 assert 'node_id' in self
280 assert 'nodenetwork_ids' in self
282 # we need to clean up NodeNetworkSettings, so handling nodenetworks as part of join_tables does not work
283 for nodenetwork in NodeNetworks(self.api,self['nodenetwork_ids']):
286 # Clean up miscellaneous join tables
287 for table in self.join_tables:
288 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
289 (table, self['node_id']))
292 self['deleted'] = True
298 Representation of row(s) from the nodes table in the
302 def __init__(self, api, node_filter = None, columns = None):
303 Table.__init__(self, api, Node, columns)
305 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
306 ", ".join(self.columns)
308 if node_filter is not None:
309 if isinstance(node_filter, (list, tuple, set)):
310 # Separate the list into integers and strings
311 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
312 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
313 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
314 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
315 elif isinstance(node_filter, dict):
316 node_filter = Filter(Node.fields, node_filter)
317 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
318 elif isinstance (node_filter, StringTypes):
319 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
320 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
321 elif isinstance (node_filter, int):
322 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
323 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
325 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter