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
11 from types import StringTypes
14 from PLC.Faults import *
15 from PLC.Parameter import Parameter, Mixed
16 from PLC.Filter import Filter
17 from PLC.Debug import profile
18 from PLC.Table import Row, Table
19 from PLC.NodeTypes import NodeTypes
20 from PLC.BootStates import BootStates
21 from PLC.Interfaces import Interface, Interfaces
23 def valid_hostname(hostname):
24 # 1. Each part begins and ends with a letter or number.
25 # 2. Each part except the last can contain letters, numbers, or hyphens.
26 # 3. Each part is between 1 and 64 characters, including the trailing dot.
27 # 4. At least two parts.
28 # 5. Last part can only contain between 2 and 6 letters.
29 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
32 re.match(good_hostname, hostname, re.IGNORECASE)
36 Representation of a row in the nodes table. To use, optionally
37 instantiate with a dict of values. Update as you would a
38 dict. Commit to the database with sync().
42 primary_key = 'node_id'
43 join_tables = [ 'slice_node', 'peer_node', 'slice_tag',
44 'node_session', 'node_slice_whitelist',
45 'node_tag', 'conf_file_node', 'pcu_node', ]
47 'node_id': Parameter(int, "Node identifier"),
48 'node_type': Parameter(str,"Node type",max=20),
49 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
50 'site_id': Parameter(int, "Site at which this node is located"),
51 'boot_state': Parameter(str, "Boot state", max = 20),
52 'run_level': Parameter(str, "Run level", max = 20),
53 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
54 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
55 'version': Parameter(str, "Apparent Boot CD version", max = 64),
56 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
57 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
58 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
59 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
60 'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False),
61 'key': Parameter(str, "(Admin only) Node key", max = 256),
62 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
63 'interface_ids': Parameter([int], "List of network interfaces that this node has"),
64 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
65 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
66 'slice_ids': Parameter([int], "List of slices on this node"),
67 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
68 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
69 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
70 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
71 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
72 'node_tag_ids' : Parameter ([int], "List of tags attached to this node"),
73 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
76 'interfaces': [Mixed(Parameter(int, "Interface identifier"),
77 Filter(Interface.fields))],
78 'conf_files': [Parameter(int, "ConfFile identifier")],
79 'slices': [Mixed(Parameter(int, "Slice identifier"),
80 Parameter(str, "Slice name"))],
81 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
82 Parameter(str, "Slice name"))]
85 view_tags_name = "view_node_tags"
86 # tags are used by the Add/Get/Update methods to expose tags
87 # this is initialized here and updated by the accessors factory
90 def validate_hostname(self, hostname):
91 if not valid_hostname(hostname):
92 raise PLCInvalidArgument, "Invalid hostname"
94 conflicts = Nodes(self.api, [hostname])
95 for node in conflicts:
96 if 'node_id' not in self or self['node_id'] != node['node_id']:
97 raise PLCInvalidArgument, "Hostname already in use"
101 def validate_node_type(self, node_type):
102 node_types = [row['node_type'] for row in NodeTypes(self.api)]
103 if node_type not in node_types:
104 raise PLCInvalidArgument, "Invalid node type %r"%node_type
107 def validate_boot_state(self, boot_state):
108 boot_states = [row['boot_state'] for row in BootStates(self.api)]
109 if boot_state not in boot_states:
110 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
113 validate_date_created = Row.validate_timestamp
114 validate_last_updated = Row.validate_timestamp
115 validate_last_contact = Row.validate_timestamp
117 def update_last_contact(self, commit = True):
119 Update last_contact field with current time
122 assert 'node_id' in self
123 assert self.table_name
125 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
126 " where node_id = %d" % ( self['node_id']) )
130 def update_last_updated(self, commit = True):
132 Update last_updated field with current time
135 assert 'node_id' in self
136 assert self.table_name
138 self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
139 " where node_id = %d" % (self['node_id']) )
142 def update_tags(self, tags):
143 from PLC.Shell import Shell
144 from PLC.NodeTags import NodeTags
145 from PLC.Methods.AddNodeTag import AddNodeTag
146 from PLC.Methods.UpdateNodeTag import UpdateNodeTag
148 for (tagname,value) in tags.iteritems():
149 # the tagtype instance is assumed to exist, just check that
150 if not TagTypes(self.api,{'tagname':tagname}):
151 raise PLCInvalidArgument,"No such TagType %s"%tagname
152 node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
154 AddNodeTag(self.api).__call__(shell.auth,node['node_id'],tagname,value)
156 UpdateNodeTag(self.api).__call__(shell.auth,node_tags[0]['node_tag_id'],value)
158 def associate_interfaces(self, auth, field, value):
160 Delete interfaces not found in value list (using DeleteInterface)
161 Add interfaces found in value list (using AddInterface)
162 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
165 assert 'interface_ids' in self
166 assert 'node_id' in self
167 assert isinstance(value, list)
169 (interface_ids, blank, interfaces) = self.separate_types(value)
171 if self['interface_ids'] != interface_ids:
172 from PLC.Methods.DeleteInterface import DeleteInterface
174 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
176 for stale_interface in stale_interfaces:
177 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
179 def associate_conf_files(self, auth, field, value):
181 Add conf_files found in value list (AddConfFileToNode)
182 Delets conf_files not found in value list (DeleteConfFileFromNode)
185 assert 'conf_file_ids' in self
186 assert 'node_id' in self
187 assert isinstance(value, list)
189 conf_file_ids = self.separate_types(value)[0]
191 if self['conf_file_ids'] != conf_file_ids:
192 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
193 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
194 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
195 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
197 for new_conf_file in new_conf_files:
198 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
199 for stale_conf_file in stale_conf_files:
200 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
202 def associate_slices(self, auth, field, value):
204 Add slices found in value list to (AddSliceToNode)
205 Delete slices not found in value list (DeleteSliceFromNode)
208 from PLC.Slices import Slices
210 assert 'slice_ids' in self
211 assert 'node_id' in self
212 assert isinstance(value, list)
214 (slice_ids, slice_names) = self.separate_types(value)[0:2]
217 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
218 slice_ids += slices.keys()
220 if self['slice_ids'] != slice_ids:
221 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
222 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
223 new_slices = set(slice_ids).difference(self['slice_ids'])
224 stale_slices = set(self['slice_ids']).difference(slice_ids)
226 for new_slice in new_slices:
227 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
228 for stale_slice in stale_slices:
229 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
231 def associate_slices_whitelist(self, auth, field, value):
233 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
234 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
237 from PLC.Slices import Slices
239 assert 'slice_ids_whitelist' in self
240 assert 'node_id' in self
241 assert isinstance(value, list)
243 (slice_ids, slice_names) = self.separate_types(value)[0:2]
246 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
247 slice_ids += slices.keys()
249 if self['slice_ids_whitelist'] != slice_ids:
250 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
251 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
252 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
253 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
255 for new_slice in new_slices:
256 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
257 for stale_slice in stale_slices:
258 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
261 def delete(self, commit = True):
263 Delete existing node.
266 assert 'node_id' in self
268 # we need to clean up InterfaceTags, so handling interfaces as part of join_tables does not work
269 # federated nodes don't have interfaces though so for smooth transition from 4.2 to 4.3
270 if 'peer_id' in self and self['peer_id']:
273 assert 'interface_ids' in self
274 for interface in Interfaces(self.api,self['interface_ids']):
277 # Clean up miscellaneous join tables
278 for table in self.join_tables:
279 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
280 (table, self['node_id']))
283 self['deleted'] = True
289 Representation of row(s) from the nodes table in the
293 def __init__(self, api, node_filter = None, columns = None):
294 Table.__init__(self, api, Node, columns)
296 # the view that we're selecting upon: start with view_nodes
298 # as many left joins as requested tags
299 for tagname in self.tag_columns:
300 view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),
303 sql = "SELECT %s FROM %s WHERE deleted IS False" % \
304 (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
306 if node_filter is not None:
307 if isinstance(node_filter, (list, tuple, set)):
308 # Separate the list into integers and strings
309 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
310 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
311 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
312 sql += " AND (%s) %s" % node_filter.sql(api, "OR")
313 elif isinstance(node_filter, dict):
314 allowed_fields=dict(Node.fields.items()+Node.tags.items())
315 node_filter = Filter(allowed_fields, node_filter)
316 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
317 elif isinstance (node_filter, StringTypes):
318 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
319 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
320 elif isinstance (node_filter, int):
321 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
322 sql += " AND (%s) %s" % node_filter.sql(api, "AND")
324 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter