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 'node_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 are used by the Add/Get/Update methods to expose tags
84 # this is initialized here and updated by the accessors factory
87 def validate_hostname(self, hostname):
88 if not valid_hostname(hostname):
89 raise PLCInvalidArgument, "Invalid hostname"
91 conflicts = Nodes(self.api, [hostname])
92 for node in conflicts:
93 if 'node_id' not in self or self['node_id'] != node['node_id']:
94 raise PLCInvalidArgument, "Hostname already in use"
98 def validate_node_type(self, node_type):
99 node_types = [row['node_type'] for row in NodeTypes(self.api)]
100 if node_type not in node_types:
101 raise PLCInvalidArgument, "Invalid node type %r"%node_type
104 def validate_boot_state(self, boot_state):
105 boot_states = [row['boot_state'] for row in BootStates(self.api)]
106 if boot_state not in boot_states:
107 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
110 validate_date_created = Row.validate_timestamp
111 validate_last_updated = Row.validate_timestamp
112 validate_last_contact = Row.validate_timestamp
114 def update_last_contact(self, commit = True):
116 Update last_contact field with current time
119 assert 'node_id' in self
120 assert self.table_name
122 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
123 " where node_id = %d" % ( self['node_id']) )
127 def update_last_updated(self, commit = True):
129 Update last_updated field with current time
132 assert 'node_id' in self
133 assert self.table_name
135 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
136 " where node_id = %d" % (self['node_id']) )
139 def associate_interfaces(self, auth, field, value):
141 Delete interfaces not found in value list (using DeleteInterface)
142 Add interfaces found in value list (using AddInterface)
143 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
146 assert 'interface_ids' in self
147 assert 'node_id' in self
148 assert isinstance(value, list)
150 (interface_ids, blank, interfaces) = self.separate_types(value)
152 if self['interface_ids'] != interface_ids:
153 from PLC.Methods.DeleteInterface import DeleteInterface
155 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
157 for stale_interface in stale_interfaces:
158 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
160 def associate_conf_files(self, auth, field, value):
162 Add conf_files found in value list (AddConfFileToNode)
163 Delets conf_files not found in value list (DeleteConfFileFromNode)
166 assert 'conf_file_ids' in self
167 assert 'node_id' in self
168 assert isinstance(value, list)
170 conf_file_ids = self.separate_types(value)[0]
172 if self['conf_file_ids'] != conf_file_ids:
173 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
174 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
175 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
176 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
178 for new_conf_file in new_conf_files:
179 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
180 for stale_conf_file in stale_conf_files:
181 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
183 def associate_slices(self, auth, field, value):
185 Add slices found in value list to (AddSliceToNode)
186 Delete slices not found in value list (DeleteSliceFromNode)
189 from PLC.Slices import Slices
191 assert 'slice_ids' in self
192 assert 'node_id' in self
193 assert isinstance(value, list)
195 (slice_ids, slice_names) = self.separate_types(value)[0:2]
198 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
199 slice_ids += slices.keys()
201 if self['slice_ids'] != slice_ids:
202 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
203 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
204 new_slices = set(slice_ids).difference(self['slice_ids'])
205 stale_slices = set(self['slice_ids']).difference(slice_ids)
207 for new_slice in new_slices:
208 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
209 for stale_slice in stale_slices:
210 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
212 def associate_slices_whitelist(self, auth, field, value):
214 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
215 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
218 from PLC.Slices import Slices
220 assert 'slice_ids_whitelist' in self
221 assert 'node_id' in self
222 assert isinstance(value, list)
224 (slice_ids, slice_names) = self.separate_types(value)[0:2]
227 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
228 slice_ids += slices.keys()
230 if self['slice_ids_whitelist'] != slice_ids:
231 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
232 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
233 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
234 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
236 for new_slice in new_slices:
237 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
238 for stale_slice in stale_slices:
239 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
242 def delete(self, commit = True):
244 Delete existing node.
247 assert 'node_id' in self
248 assert 'interface_ids' in self
250 # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
251 for interface in Interfaces(self.api,self['interface_ids']):
254 # Clean up miscellaneous join tables
255 for table in self.join_tables:
256 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
257 (table, self['node_id']))
260 self['deleted'] = True
266 Representation of row(s) from the nodes table in the
270 def __init__(self, api, node_filter = None, columns = None):
271 Table.__init__(self, api, Node, columns)
273 # the view that we're selecting upon: start with view_nodes
275 # as many left joins as requested tags
276 for tagname in self.tag_columns:
277 view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),
280 sql = "SELECT %s FROM %s WHERE deleted IS False" % \
281 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
283 if node_filter is not None:
284 if isinstance(node_filter, (list, tuple, set)):
285 # Separate the list into integers and strings
286 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
287 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
288 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
289 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
290 elif isinstance(node_filter, dict):
291 allowed_fields=dict(Node.fields.items()+Node.tags.items())
292 node_filter = Filter(allowed_fields, node_filter)
293 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
294 elif isinstance (node_filter, StringTypes):
295 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
296 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
297 elif isinstance (node_filter, int):
298 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
299 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
301 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter