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 def validate_hostname(self, hostname):
84 if not valid_hostname(hostname):
85 raise PLCInvalidArgument, "Invalid hostname"
87 conflicts = Nodes(self.api, [hostname])
88 for node in conflicts:
89 if 'node_id' not in self or self['node_id'] != node['node_id']:
90 raise PLCInvalidArgument, "Hostname already in use"
94 def validate_boot_state(self, boot_state):
95 boot_states = [row['boot_state'] for row in BootStates(self.api)]
96 if boot_state not in boot_states:
97 raise PLCInvalidArgument, "Invalid boot state"
101 validate_date_created = Row.validate_timestamp
102 validate_last_updated = Row.validate_timestamp
103 validate_last_contact = Row.validate_timestamp
105 def update_last_contact(self, commit = True):
107 Update last_contact field with current time
110 assert 'node_id' in self
111 assert self.table_name
113 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
114 " where node_id = %d" % ( self['node_id']) )
118 def update_last_updated(self, commit = True):
120 Update last_updated field with current time
123 assert 'node_id' in self
124 assert self.table_name
126 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
127 " where node_id = %d" % (self['node_id']) )
130 def associate_interfaces(self, auth, field, value):
132 Delete interfaces not found in value list (using DeleteNodeNetwor)k
133 Add interfaces found in value list (using AddInterface)
134 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
137 assert 'interfacep_ids' in self
138 assert 'node_id' in self
139 assert isinstance(value, list)
141 (interface_ids, blank, interfaces) = self.separate_types(value)
143 if self['interface_ids'] != interface_ids:
144 from PLC.Methods.DeleteInterface import DeleteInterface
146 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
148 for stale_interface in stale_interfaces:
149 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
151 def associate_nodegroups(self, auth, field, value):
153 Add node to nodegroups found in value list (AddNodeToNodegroup)
154 Delete node from nodegroup not found in value list (DeleteNodeFromNodegroup)
157 from PLC.NodeGroups import NodeGroups
159 assert 'nodegroup_ids' in self
160 assert 'node_id' in self
161 assert isinstance(value, list)
163 (nodegroup_ids, nodegroup_names) = self.separate_types(value)[0:2]
166 nodegroups = NodeGroups(self.api, nodegroup_names, ['nodegroup_id']).dict('nodegroup_id')
167 nodegroup_ids += nodegroups.keys()
169 if self['nodegroup_ids'] != nodegroup_ids:
170 from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
171 from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
173 new_nodegroups = set(nodegroup_ids).difference(self['nodegroup_ids'])
174 stale_nodegroups = set(self['nodegroup_ids']).difference(nodegroup_ids)
176 for new_nodegroup in new_nodegroups:
177 AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, self['node_id'], new_nodegroup)
178 for stale_nodegroup in stale_nodegroups:
179 DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, self['node_id'], stale_nodegroup)
183 def associate_conf_files(self, auth, field, value):
185 Add conf_files found in value list (AddConfFileToNode)
186 Delets conf_files not found in value list (DeleteConfFileFromNode)
189 assert 'conf_file_ids' in self
190 assert 'node_id' in self
191 assert isinstance(value, list)
193 conf_file_ids = self.separate_types(value)[0]
195 if self['conf_file_ids'] != conf_file_ids:
196 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
197 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
198 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
199 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
201 for new_conf_file in new_conf_files:
202 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
203 for stale_conf_file in stale_conf_files:
204 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
207 def associate_slices(self, auth, field, value):
209 Add slices found in value list to (AddSliceToNode)
210 Delete slices not found in value list (DeleteSliceFromNode)
213 from PLC.Slices import Slices
215 assert 'slice_ids' in self
216 assert 'node_id' in self
217 assert isinstance(value, list)
219 (slice_ids, slice_names) = self.separate_types(value)[0:2]
222 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
223 slice_ids += slices.keys()
225 if self['slice_ids'] != slice_ids:
226 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
227 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
228 new_slices = set(slice_ids).difference(self['slice_ids'])
229 stale_slices = set(self['slice_ids']).difference(slice_ids)
231 for new_slice in new_slices:
232 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
233 for stale_slice in stale_slices:
234 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
236 def associate_slices_whitelist(self, auth, field, value):
238 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
239 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
242 from PLC.Slices import Slices
244 assert 'slice_ids_whitelist' in self
245 assert 'node_id' in self
246 assert isinstance(value, list)
248 (slice_ids, slice_names) = self.separate_types(value)[0:2]
251 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
252 slice_ids += slices.keys()
254 if self['slice_ids_whitelist'] != slice_ids:
255 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
256 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
257 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
258 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
260 for new_slice in new_slices:
261 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
262 for stale_slice in stale_slices:
263 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
266 def delete(self, commit = True):
268 Delete existing node.
271 assert 'node_id' in self
272 assert 'interface_ids' in self
274 # we need to clean up InterfaceSettings, so handling interfaces as part of join_tables does not work
275 for interface in Interfaces(self.api,self['interface_ids']):
278 # Clean up miscellaneous join tables
279 for table in self.join_tables:
280 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
281 (table, self['node_id']))
284 self['deleted'] = True
290 Representation of row(s) from the nodes table in the
294 def __init__(self, api, node_filter = None, columns = None):
295 Table.__init__(self, api, Node, columns)
297 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
298 ", ".join(self.columns)
300 if node_filter is not None:
301 if isinstance(node_filter, (list, tuple, set)):
302 # Separate the list into integers and strings
303 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
304 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
305 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
306 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
307 elif isinstance(node_filter, dict):
308 node_filter = Filter(Node.fields, node_filter)
309 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
310 elif isinstance (node_filter, StringTypes):
311 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
312 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
313 elif isinstance (node_filter, int):
314 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
315 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
317 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter