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 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
76 Parameter(str, "NodeGroup name"))],
77 'conf_files': [Parameter(int, "ConfFile identifier")],
78 'slices': [Mixed(Parameter(int, "Slice identifier"),
79 Parameter(str, "Slice name"))],
80 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
81 Parameter(str, "Slice name"))]
83 view_name = "view_nodes"
84 view_tags_name = "view_node_tags"
87 'arch': Parameter(str, "node/config", ro=True),
88 'deployment': Parameter(str, "node/operation"),
92 def validate_hostname(self, hostname):
93 if not valid_hostname(hostname):
94 raise PLCInvalidArgument, "Invalid hostname"
96 conflicts = Nodes(self.api, [hostname])
97 for node in conflicts:
98 if 'node_id' not in self or self['node_id'] != node['node_id']:
99 raise PLCInvalidArgument, "Hostname already in use"
103 def validate_node_type(self, node_type):
104 node_types = [row['node_type'] for row in NodeTypes(self.api)]
105 if node_type not in node_types:
106 raise PLCInvalidArgument, "Invalid node type %r"%node_type
109 def validate_boot_state(self, boot_state):
110 boot_states = [row['boot_state'] for row in BootStates(self.api)]
111 if boot_state not in boot_states:
112 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
115 validate_date_created = Row.validate_timestamp
116 validate_last_updated = Row.validate_timestamp
117 validate_last_contact = Row.validate_timestamp
119 def update_last_contact(self, commit = True):
121 Update last_contact field with current time
124 assert 'node_id' in self
125 assert self.table_name
127 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
128 " where node_id = %d" % ( self['node_id']) )
132 def update_last_updated(self, commit = True):
134 Update last_updated field with current time
137 assert 'node_id' in self
138 assert self.table_name
140 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
141 " where node_id = %d" % (self['node_id']) )
144 def associate_interfaces(self, auth, field, value):
146 Delete interfaces not found in value list (using DeleteInterface)
147 Add interfaces found in value list (using AddInterface)
148 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
151 assert 'interface_ids' in self
152 assert 'node_id' in self
153 assert isinstance(value, list)
155 (interface_ids, blank, interfaces) = self.separate_types(value)
157 if self['interface_ids'] != interface_ids:
158 from PLC.Methods.DeleteInterface import DeleteInterface
160 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
162 for stale_interface in stale_interfaces:
163 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
165 def associate_conf_files(self, auth, field, value):
167 Add conf_files found in value list (AddConfFileToNode)
168 Delets conf_files not found in value list (DeleteConfFileFromNode)
171 assert 'conf_file_ids' in self
172 assert 'node_id' in self
173 assert isinstance(value, list)
175 conf_file_ids = self.separate_types(value)[0]
177 if self['conf_file_ids'] != conf_file_ids:
178 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
179 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
180 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
181 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
183 for new_conf_file in new_conf_files:
184 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
185 for stale_conf_file in stale_conf_files:
186 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
188 def associate_slices(self, auth, field, value):
190 Add slices found in value list to (AddSliceToNode)
191 Delete slices not found in value list (DeleteSliceFromNode)
194 from PLC.Slices import Slices
196 assert 'slice_ids' in self
197 assert 'node_id' in self
198 assert isinstance(value, list)
200 (slice_ids, slice_names) = self.separate_types(value)[0:2]
203 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
204 slice_ids += slices.keys()
206 if self['slice_ids'] != slice_ids:
207 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
208 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
209 new_slices = set(slice_ids).difference(self['slice_ids'])
210 stale_slices = set(self['slice_ids']).difference(slice_ids)
212 for new_slice in new_slices:
213 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
214 for stale_slice in stale_slices:
215 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
217 def associate_slices_whitelist(self, auth, field, value):
219 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
220 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
223 from PLC.Slices import Slices
225 assert 'slice_ids_whitelist' in self
226 assert 'node_id' in self
227 assert isinstance(value, list)
229 (slice_ids, slice_names) = self.separate_types(value)[0:2]
232 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
233 slice_ids += slices.keys()
235 if self['slice_ids_whitelist'] != slice_ids:
236 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
237 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
238 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
239 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
241 for new_slice in new_slices:
242 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
243 for stale_slice in stale_slices:
244 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
247 def delete(self, commit = True):
249 Delete existing node.
252 assert 'node_id' in self
253 assert 'interface_ids' in self
255 # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
256 for interface in Interfaces(self.api,self['interface_ids']):
259 # Clean up miscellaneous join tables
260 for table in self.join_tables:
261 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
262 (table, self['node_id']))
265 self['deleted'] = True
271 Representation of row(s) from the nodes table in the
275 def __init__(self, api, node_filter = None, columns = None):
276 Table.__init__(self, api, Node, columns)
278 # the view that we're selecting upon: start with view_nodes
280 # as many left joins as requested tags
281 for tagname in self.tag_columns:
282 view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),Node.primary_key)
284 sql = "SELECT %s FROM %s WHERE deleted IS False" % \
285 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
287 if node_filter is not None:
288 if isinstance(node_filter, (list, tuple, set)):
289 # Separate the list into integers and strings
290 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
291 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
292 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
293 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
294 elif isinstance(node_filter, dict):
295 node_filter = Filter(Node.fields, node_filter)
296 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
297 elif isinstance (node_filter, StringTypes):
298 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
299 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
300 elif isinstance (node_filter, int):
301 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
302 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
304 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter