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.NodeTypes import NodeTypes
19 from PLC.BootStates import BootStates
20 from PLC.Interfaces import Interface, Interfaces
22 def valid_hostname(hostname):
23 # 1. Each part begins and ends with a letter or number.
24 # 2. Each part except the last can contain letters, numbers, or hyphens.
25 # 3. Each part is between 1 and 64 characters, including the trailing dot.
26 # 4. At least two parts.
27 # 5. Last part can only contain between 2 and 6 letters.
28 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
31 re.match(good_hostname, hostname, re.IGNORECASE)
35 Representation of a row in the nodes table. To use, optionally
36 instantiate with a dict of values. Update as you would a
37 dict. Commit to the database with sync().
41 primary_key = 'node_id'
42 join_tables = [ 'slice_node', 'peer_node', 'slice_tag',
43 'node_session', 'node_slice_whitelist',
44 'node_tag', 'conf_file_node', 'pcu_node', ]
46 'node_id': Parameter(int, "Node identifier"),
47 'node_type': Parameter(str,"Node type",max=20),
48 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
49 'site_id': Parameter(int, "Site at which this node is located"),
50 'boot_state': Parameter(str, "Boot state", max = 20),
51 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
52 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
53 'version': Parameter(str, "Apparent Boot CD version", max = 64),
54 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
55 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
56 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
57 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
58 'key': Parameter(str, "(Admin only) Node key", max = 256),
59 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
60 'interface_ids': Parameter([int], "List of network interfaces that this node has"),
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"),
70 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
73 'interfaces': [Mixed(Parameter(int, "Interface identifier"),
74 Filter(Interface.fields))],
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 view_tags_name = "view_node_tags"
83 # tags declared here should also be defined as Accessors to ensure that the TagType is created
84 # type info not used yet
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_node_type(self, node_type):
105 node_types = [row['node_type'] for row in NodeTypes(self.api)]
106 if node_type not in node_types:
107 raise PLCInvalidArgument, "Invalid node type %r"%node_type
110 def validate_boot_state(self, boot_state):
111 boot_states = [row['boot_state'] for row in BootStates(self.api)]
112 if boot_state not in boot_states:
113 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
116 validate_date_created = Row.validate_timestamp
117 validate_last_updated = Row.validate_timestamp
118 validate_last_contact = Row.validate_timestamp
120 def update_last_contact(self, commit = True):
122 Update last_contact field with current time
125 assert 'node_id' in self
126 assert self.table_name
128 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
129 " where node_id = %d" % ( self['node_id']) )
133 def update_last_updated(self, commit = True):
135 Update last_updated field with current time
138 assert 'node_id' in self
139 assert self.table_name
141 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
142 " where node_id = %d" % (self['node_id']) )
145 def associate_interfaces(self, auth, field, value):
147 Delete interfaces not found in value list (using DeleteInterface)
148 Add interfaces found in value list (using AddInterface)
149 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
152 assert 'interface_ids' in self
153 assert 'node_id' in self
154 assert isinstance(value, list)
156 (interface_ids, blank, interfaces) = self.separate_types(value)
158 if self['interface_ids'] != interface_ids:
159 from PLC.Methods.DeleteInterface import DeleteInterface
161 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
163 for stale_interface in stale_interfaces:
164 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
166 def associate_conf_files(self, auth, field, value):
168 Add conf_files found in value list (AddConfFileToNode)
169 Delets conf_files not found in value list (DeleteConfFileFromNode)
172 assert 'conf_file_ids' in self
173 assert 'node_id' in self
174 assert isinstance(value, list)
176 conf_file_ids = self.separate_types(value)[0]
178 if self['conf_file_ids'] != conf_file_ids:
179 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
180 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
181 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
182 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
184 for new_conf_file in new_conf_files:
185 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
186 for stale_conf_file in stale_conf_files:
187 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
189 def associate_slices(self, auth, field, value):
191 Add slices found in value list to (AddSliceToNode)
192 Delete slices not found in value list (DeleteSliceFromNode)
195 from PLC.Slices import Slices
197 assert 'slice_ids' in self
198 assert 'node_id' in self
199 assert isinstance(value, list)
201 (slice_ids, slice_names) = self.separate_types(value)[0:2]
204 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
205 slice_ids += slices.keys()
207 if self['slice_ids'] != slice_ids:
208 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
209 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
210 new_slices = set(slice_ids).difference(self['slice_ids'])
211 stale_slices = set(self['slice_ids']).difference(slice_ids)
213 for new_slice in new_slices:
214 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
215 for stale_slice in stale_slices:
216 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
218 def associate_slices_whitelist(self, auth, field, value):
220 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
221 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
224 from PLC.Slices import Slices
226 assert 'slice_ids_whitelist' in self
227 assert 'node_id' in self
228 assert isinstance(value, list)
230 (slice_ids, slice_names) = self.separate_types(value)[0:2]
233 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
234 slice_ids += slices.keys()
236 if self['slice_ids_whitelist'] != slice_ids:
237 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
238 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
239 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
240 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
242 for new_slice in new_slices:
243 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
244 for stale_slice in stale_slices:
245 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
248 def delete(self, commit = True):
250 Delete existing node.
253 assert 'node_id' in self
254 assert 'interface_ids' in self
256 # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
257 for interface in Interfaces(self.api,self['interface_ids']):
260 # Clean up miscellaneous join tables
261 for table in self.join_tables:
262 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
263 (table, self['node_id']))
266 self['deleted'] = True
272 Representation of row(s) from the nodes table in the
276 def __init__(self, api, node_filter = None, columns = None):
277 Table.__init__(self, api, Node, columns)
279 # the view that we're selecting upon: start with view_nodes
281 # as many left joins as requested tags
282 for tagname in self.tag_columns:
283 view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),Node.primary_key)
285 sql = "SELECT %s FROM %s WHERE deleted IS False" % \
286 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
288 if node_filter is not None:
289 if isinstance(node_filter, (list, tuple, set)):
290 # Separate the list into integers and strings
291 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
292 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
293 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
294 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
295 elif isinstance(node_filter, dict):
296 allowed_fields=dict(Node.fields.items()+Node.tags.items())
297 node_filter = Filter(allowed_fields, node_filter)
298 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
299 elif isinstance (node_filter, StringTypes):
300 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
301 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
302 elif isinstance (node_filter, int):
303 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
304 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
306 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter