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 join_tables = [ 'slice_node', 'peer_node', 'slice_tag',
42 'node_session', 'node_slice_whitelist',
43 'node_tag', 'conf_file_node', 'pcu_node', ]
45 'node_id': Parameter(int, "Node identifier"),
46 'node_type': Parameter(str,"Node type",max=20),
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 '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),
68 'tag_ids' : Parameter ([int], "List of tags attached to this node"),
69 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
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"))]
82 view_name = "view_nodes"
83 view_tags_name = "view_node_tags"
86 'arch': Parameter(str, "node/config", ro=True),
87 'deployment': Parameter(str, "node/operation"),
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_interfaces(self, auth, field, value):
140 Delete interfaces not found in value list (using DeleteInterface)
141 Add interfaces found in value list (using AddInterface)
142 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
145 assert 'interface_ids' in self
146 assert 'node_id' in self
147 assert isinstance(value, list)
149 (interface_ids, blank, interfaces) = self.separate_types(value)
151 if self['interface_ids'] != interface_ids:
152 from PLC.Methods.DeleteInterface import DeleteInterface
154 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
156 for stale_interface in stale_interfaces:
157 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
159 def associate_conf_files(self, auth, field, value):
161 Add conf_files found in value list (AddConfFileToNode)
162 Delets conf_files not found in value list (DeleteConfFileFromNode)
165 assert 'conf_file_ids' in self
166 assert 'node_id' in self
167 assert isinstance(value, list)
169 conf_file_ids = self.separate_types(value)[0]
171 if self['conf_file_ids'] != conf_file_ids:
172 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
173 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
174 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
175 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
177 for new_conf_file in new_conf_files:
178 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
179 for stale_conf_file in stale_conf_files:
180 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
182 def associate_slices(self, auth, field, value):
184 Add slices found in value list to (AddSliceToNode)
185 Delete slices not found in value list (DeleteSliceFromNode)
188 from PLC.Slices import Slices
190 assert 'slice_ids' in self
191 assert 'node_id' in self
192 assert isinstance(value, list)
194 (slice_ids, slice_names) = self.separate_types(value)[0:2]
197 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
198 slice_ids += slices.keys()
200 if self['slice_ids'] != slice_ids:
201 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
202 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
203 new_slices = set(slice_ids).difference(self['slice_ids'])
204 stale_slices = set(self['slice_ids']).difference(slice_ids)
206 for new_slice in new_slices:
207 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
208 for stale_slice in stale_slices:
209 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
211 def associate_slices_whitelist(self, auth, field, value):
213 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
214 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
217 from PLC.Slices import Slices
219 assert 'slice_ids_whitelist' in self
220 assert 'node_id' in self
221 assert isinstance(value, list)
223 (slice_ids, slice_names) = self.separate_types(value)[0:2]
226 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
227 slice_ids += slices.keys()
229 if self['slice_ids_whitelist'] != slice_ids:
230 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
231 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
232 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
233 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
235 for new_slice in new_slices:
236 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
237 for stale_slice in stale_slices:
238 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
241 def delete(self, commit = True):
243 Delete existing node.
246 assert 'node_id' in self
247 assert 'interface_ids' in self
249 # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
250 for interface in Interfaces(self.api,self['interface_ids']):
253 # Clean up miscellaneous join tables
254 for table in self.join_tables:
255 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
256 (table, self['node_id']))
259 self['deleted'] = True
265 Representation of row(s) from the nodes table in the
269 def __init__(self, api, node_filter = None, columns = None):
270 Table.__init__(self, api, Node, columns)
272 # the view that we're selecting upon: start with view_nodes
274 # as many left joins as requested tags
275 for tagname in self.tag_columns:
276 view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),Node.primary_key)
278 sql = "SELECT %s FROM %s WHERE deleted IS False" % \
279 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
281 if node_filter is not None:
282 if isinstance(node_filter, (list, tuple, set)):
283 # Separate the list into integers and strings
284 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
285 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
286 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
287 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
288 elif isinstance(node_filter, dict):
289 node_filter = Filter(Node.fields, node_filter)
290 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
291 elif isinstance (node_filter, StringTypes):
292 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
293 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
294 elif isinstance (node_filter, int):
295 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
296 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
298 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter