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
8 from types import StringTypes
11 from PLC.Faults import *
12 from PLC.Parameter import Parameter, Mixed
13 from PLC.Filter import Filter
14 from PLC.Debug import profile
15 from PLC.Storage.AlchemyObj import AlchemyObj
16 from PLC.NodeTypes import NodeTypes
17 from PLC.BootStates import BootStates
18 from PLC.Interfaces import Interface, Interfaces
20 def valid_hostname(hostname):
21 # 1. Each part begins and ends with a letter or number.
22 # 2. Each part except the last can contain letters, numbers, or hyphens.
23 # 3. Each part is between 1 and 64 characters, including the trailing dot.
24 # 4. At least two parts.
25 # 5. Last part can only contain between 2 and 6 letters.
26 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
29 re.match(good_hostname, hostname, re.IGNORECASE)
31 class Node(AlchemyObj):
33 Representation of a row in the nodes table. To use, optionally
34 instantiate with a dict of values. Update as you would a
35 dict. Commit to the database with sync().
39 join_tables = [ 'slice_node', 'peer_node', 'slice_tag',
40 'node_session', 'node_slice_whitelist',
41 'node_tag', 'conf_file_node', 'pcu_node', 'leases', ]
43 'node_id': Parameter(int, "Node identifier", primary_key=True),
44 'node_type': Parameter(str,"Node type",max=20),
45 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
46 'site_id': Parameter(int, "Site at which this node is located"),
47 'boot_state': Parameter(str, "Boot state", max = 20),
48 'run_level': Parameter(str, "Run level", max = 20),
49 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
50 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
51 'version': Parameter(str, "Apparent Boot CD version", max = 64),
52 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
53 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
54 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
55 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
56 'last_boot': Parameter(int, "Date and time when node last booted", ro = True),
57 'last_download': Parameter(int, "Date and time when node boot image was created", ro = True),
58 'last_pcu_reboot': Parameter(int, "Date and time when PCU reboot was attempted", ro = True),
59 'last_pcu_confirmation': Parameter(int, "Date and time when PCU reboot was confirmed", ro = True),
60 'last_time_spent_online': Parameter(int, "Length of time the node was last online before shutdown/failure", ro = True),
61 'last_time_spent_offline': Parameter(int, "Length of time the node was last offline after failure and before reboot", ro = True),
62 'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False),
63 'key': Parameter(str, "(Admin only) Node key", max = 256),
64 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
65 'interface_ids': Parameter([int], "List of network interfaces that this node has", joined=True),
66 'conf_file_ids': Parameter([int], "List of configuration files specific to this node", joined=True),
67 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
68 'slice_ids': Parameter([int], "List of slices on this node", joined=True),
69 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node", joined=True),
70 'pcu_ids': Parameter([int], "List of PCUs that control this node", joined=True),
71 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
72 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
73 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
74 'node_tag_ids' : Parameter ([int], "List of tags attached to this node", joined=True),
75 'nodegroup_ids': Parameter([int], "List of node groups that this node is in", joined=True),
79 def validate_hostname(self, hostname):
80 hostname = hostname.lower()
81 if not valid_hostname(hostname):
82 raise PLCInvalidArgument, "Invalid hostname"
84 conflicts = Nodes(self.api, [hostname])
85 for node in conflicts:
86 if 'node_id' not in self or self['node_id'] != node['node_id']:
87 raise PLCInvalidArgument, "Hostname already in use"
91 def validate_node_type(self, node_type):
92 node_types = [row['node_type'] for row in NodeTypes(self.api)]
93 if node_type not in node_types:
94 raise PLCInvalidArgument, "Invalid node type %r"%node_type
97 def validate_boot_state(self, boot_state):
98 boot_states = [row['boot_state'] for row in BootStates(self.api)]
99 if boot_state not in boot_states:
100 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
103 validate_date_created = AlchemyObj.validate_timestamp
104 validate_last_updated = AlchemyObj.validate_timestamp
105 validate_last_contact = AlchemyObj.validate_timestamp
106 validate_last_boot = AlchemyObj.validate_timestamp
107 validate_last_download = AlchemyObj.validate_timestamp
108 validate_last_pcu_reboot = AlchemyObj.validate_timestamp
109 validate_last_pcu_confirmation = AlchemyObj.validate_timestamp
111 def update_readonly_int(self, col_name, commit = True):
113 assert 'node_id' in self
114 assert self.table_name
116 self.api.db.do("UPDATE %s SET %s = %s" % (self.table_name, col_name, self[col_name]) + \
117 " where node_id = %d" % (self['node_id']) )
120 def update_timestamp(self, col_name, commit = True):
122 Update col_name field with current time
125 assert 'node_id' in self
126 assert self.table_name
128 self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
129 " where node_id = %d" % (self['node_id']) )
132 def update_last_boot(self, commit = True):
133 self.update_timestamp('last_boot', commit)
134 def update_last_download(self, commit = True):
135 self.update_timestamp('last_download', commit)
136 def update_last_pcu_reboot(self, commit = True):
137 self.update_timestamp('last_pcu_reboot', commit)
138 def update_last_pcu_confirmation(self, commit = True):
139 self.update_timestamp('last_pcu_confirmation', commit)
141 def update_last_contact(self, commit = True):
142 self.update_timestamp('last_contact', commit)
143 def update_last_updated(self, commit = True):
144 self.update_timestamp('last_updated', commit)
146 def update_tags(self, tags):
147 from PLC.Shell import Shell
148 from PLC.NodeTags import NodeTags
149 from PLC.Methods.AddNodeTag import AddNodeTag
150 from PLC.Methods.UpdateNodeTag import UpdateNodeTag
152 for (tagname,value) in tags.iteritems():
153 # the tagtype instance is assumed to exist, just check that
154 if not TagTypes(self.api,{'tagname':tagname}):
155 raise PLCInvalidArgument,"No such TagType %s"%tagname
156 node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
158 AddNodeTag(self.api).__call__(shell.auth,node['node_id'],tagname,value)
160 UpdateNodeTag(self.api).__call__(shell.auth,node_tags[0]['node_tag_id'],value)
162 def associate_interfaces(self, auth, field, value):
164 Delete interfaces not found in value list (using DeleteInterface)
165 Add interfaces found in value list (using AddInterface)
166 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
169 assert 'interface_ids' in self
170 assert 'node_id' in self
171 assert isinstance(value, list)
173 (interface_ids, blank, interfaces) = self.separate_types(value)
175 if self['interface_ids'] != interface_ids:
176 from PLC.Methods.DeleteInterface import DeleteInterface
178 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
180 for stale_interface in stale_interfaces:
181 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
183 def associate_conf_files(self, auth, field, value):
185 Add conf_files found in value list (AddConfFileToNode)
186 Delets conf_files not found in value list (DeleteConfFileFromNode)
189 assert 'conf_file_ids' in self
190 assert 'node_id' in self
191 assert isinstance(value, list)
193 conf_file_ids = self.separate_types(value)[0]
195 if self['conf_file_ids'] != conf_file_ids:
196 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
197 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
198 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
199 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
201 for new_conf_file in new_conf_files:
202 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
203 for stale_conf_file in stale_conf_files:
204 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
206 def associate_slices(self, auth, field, value):
208 Add slices found in value list to (AddSliceToNode)
209 Delete slices not found in value list (DeleteSliceFromNode)
212 from PLC.Slices import Slices
214 assert 'slice_ids' in self
215 assert 'node_id' in self
216 assert isinstance(value, list)
218 (slice_ids, slice_names) = self.separate_types(value)[0:2]
221 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
222 slice_ids += slices.keys()
224 if self['slice_ids'] != slice_ids:
225 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
226 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
227 new_slices = set(slice_ids).difference(self['slice_ids'])
228 stale_slices = set(self['slice_ids']).difference(slice_ids)
230 for new_slice in new_slices:
231 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
232 for stale_slice in stale_slices:
233 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
235 def associate_slices_whitelist(self, auth, field, value):
237 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
238 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
241 from PLC.Slices import Slices
243 assert 'slice_ids_whitelist' in self
244 assert 'node_id' in self
245 assert isinstance(value, list)
247 (slice_ids, slice_names) = self.separate_types(value)[0:2]
250 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
251 slice_ids += slices.keys()
253 if self['slice_ids_whitelist'] != slice_ids:
254 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
255 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
256 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
257 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
259 for new_slice in new_slices:
260 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
261 for stale_slice in stale_slices:
262 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
266 def sync(self, insert=False, validate=True):
267 AlchemyObj.sync(self, insert, validate)
268 if insert == True or 'node_id' not in self:
269 AlchemyObj.insert(self, dict(self))
271 AlchemyObj.update(self, dict(self))
273 def delete(self, commit = True):
275 Delete existing node.
278 assert 'node_id' in self
279 assert 'interface_ids' in self
280 Interface().delete(filter={'interface_id': self['interface_ids']})
281 AlchemyObj.delete(self, dict(self)
285 Representation of row(s) from the nodes table in the
289 def __init__(self, api, node_filter = None, columns = None):
290 # as many left joins as requested tags
292 nodes = Node().select()
293 elif isinstance(node_filter, (list, tuple, set)):
294 # Separate the list into integers and strings
295 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
296 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
297 nodes = Node().select(filter={'node_id': ints, 'hostname': strs})
298 elif isinstance(node_filter, dict):
299 nodes = Node().select(filter={'node_id': ints, 'hostname': strs})
300 elif isinstance (node_filter, StringTypes):
301 nodes = Node().select(filter={'hostname': strs})
302 elif isinstance (node_filter, (int, long)):
303 nodes = Node().select(filter={'node_id': ints})
305 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter