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
7 from datetime import datetime
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.AlchemyObject import AlchemyObj
16 from PLC.NodeTypes import NodeTypes
17 from PLC.BootStates import BootStates
18 from PLC.Interfaces import Interface, Interfaces
19 from PLC.ConfFileNodes import ConfFileNode
20 from PLC.SliceNodes import SliceNode
21 from PLC.SliceNodeWhitelists import SliceNodeWhitelist
22 from PLC.PCUNodes import PCUNode
23 from PLC.PCUNodePorts import PCUNodePort
24 from PLC.NodeTags import NodeTag
25 from PLC.NodeGroups import NodeGroup
27 def valid_hostname(hostname):
28 # 1. Each part begins and ends with a letter or number.
29 # 2. Each part except the last can contain letters, numbers, or hyphens.
30 # 3. Each part is between 1 and 64 characters, including the trailing dot.
31 # 4. At least two parts.
32 # 5. Last part can only contain between 2 and 6 letters.
33 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
36 re.match(good_hostname, hostname, re.IGNORECASE)
38 class Node(AlchemyObj):
40 Representation of a row in the nodes table. To use, optionally
41 instantiate with a dict of values. Update as you would a
42 dict. Commit to the database with sync().
46 join_tables = [ 'slice_node', 'peer_node', 'slice_tag',
47 'node_session', 'node_slice_whitelist',
48 'node_tag', 'conf_file_node', 'pcu_node', 'leases', ]
50 'node_id': Parameter(int, "Node identifier", primary_key=True),
51 'node_type': Parameter(str,"Node type",max=20),
52 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
53 'site_id': Parameter(int, "Site at which this node is located"),
54 'boot_state': Parameter(str, "Boot state", max = 20, nullok=True),
55 'run_level': Parameter(str, "Run level", max = 20, nullok=True),
56 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
57 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128, nullok=True),
58 'version': Parameter(str, "Apparent Boot CD version", max = 64, nullok=True),
59 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024, nullok=True),
60 'date_created': Parameter(datetime, "Date and time when node entry was created", ro = True),
61 'last_updated': Parameter(datetime, "Date and time when node entry was created", ro = True),
62 'last_contact': Parameter(datetime, "Date and time when node last contacted plc", ro = True, nullok=True),
63 'last_boot': Parameter(datetime, "Date and time when node last booted", ro = True, nullok=True),
64 'last_download': Parameter(datetime, "Date and time when node boot image was created", ro = True, nullok=True),
65 'last_pcu_reboot': Parameter(datetime, "Date and time when PCU reboot was attempted", ro = True, nullok=True),
66 'last_pcu_confirmation': Parameter(datetime, "Date and time when PCU reboot was confirmed", ro = True, nullok=True),
67 'last_time_spent_online': Parameter(datetime, "Length of time the node was last online before shutdown/failure", ro = True, nullok=True),
68 'last_time_spent_offline': Parameter(datetime, "Length of time the node was last offline after failure and before reboot", ro = True, nullok=True),
69 'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False, nullok=True),
70 'key': Parameter(str, "(Admin only) Node key", max = 256, nullok=True),
71 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True, nullok=True),
72 'interface_ids': Parameter([int], "List of network interfaces that this node has", joined=True),
73 'conf_file_ids': Parameter([int], "List of configuration files specific to this node", joined=True),
74 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
75 'slice_ids': Parameter([int], "List of slices on this node", joined=True),
76 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node", joined=True),
77 'pcu_ids': Parameter([int], "List of PCUs that control this node", joined=True),
78 'ports': Parameter([int], "List of PCU ports that this node is connected to", joined=True),
79 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
80 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
81 'node_tag_ids' : Parameter ([int], "List of tags attached to this node", joined=True),
82 'nodegroup_ids': Parameter([int], "List of node groups that this node is in", joined=True),
86 def validate_hostname(self, hostname):
87 hostname = hostname.lower()
88 if not valid_hostname(hostname):
89 raise PLCInvalidArgument, "Invalid hostname"
91 conflicts = Node().select(filter={'hostname': 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 # Make sure node type does not alredy exist
100 conflicts = NodeTypes(self.api, [name])
102 raise PLCInvalidArgument, "Invalid node_type"
105 def validate_boot_state(self, boot_state):
106 boot_states = [row['boot_state'] for row in BootStates(self.api)]
107 if boot_state not in boot_states:
108 raise PLCInvalidArgument, "Invalid boot state %r"%boot_state
111 validate_date_created = AlchemyObj.validate_timestamp
112 validate_last_updated = AlchemyObj.validate_timestamp
113 validate_last_contact = AlchemyObj.validate_timestamp
114 validate_last_boot = AlchemyObj.validate_timestamp
115 validate_last_download = AlchemyObj.validate_timestamp
116 validate_last_pcu_reboot = AlchemyObj.validate_timestamp
117 validate_last_pcu_confirmation = AlchemyObj.validate_timestamp
119 def update_readonly_int(self, col_name, commit = True):
121 assert 'node_id' in self
122 assert self.table_name
124 self.api.db.do("UPDATE %s SET %s = %s" % (self.table_name, col_name, self[col_name]) + \
125 " where node_id = %d" % (self['node_id']) )
128 def update_timestamp(self, col_name, commit = True):
130 Update col_name field with current time
132 assert 'node_id' in self
133 self[col_name] = datetime.now()
135 'node_id': self['node_id'],
136 col_name: datetime.now()
138 Node(self.api, fields).sync()
140 def update_last_boot(self, commit = True):
141 self.update_timestamp('last_boot', commit)
142 def update_last_download(self, commit = True):
143 self.update_timestamp('last_download', commit)
144 def update_last_pcu_reboot(self, commit = True):
145 self.update_timestamp('last_pcu_reboot', commit)
146 def update_last_pcu_confirmation(self, commit = True):
147 self.update_timestamp('last_pcu_confirmation', commit)
149 def update_last_contact(self, commit = True):
150 self.update_timestamp('last_contact', commit)
151 def update_last_updated(self, commit = True):
152 self.update_timestamp('last_updated', commit)
154 def update_tags(self, tags):
155 from PLC.Shell import Shell
156 from PLC.NodeTags import NodeTags
157 from PLC.Methods.AddNodeTag import AddNodeTag
158 from PLC.Methods.UpdateNodeTag import UpdateNodeTag
160 for (tagname,value) in tags.iteritems():
161 # the tagtype instance is assumed to exist, just check that
162 if not TagTypes(self.api,{'tagname':tagname}):
163 raise PLCInvalidArgument,"No such TagType %s"%tagname
164 node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
166 AddNodeTag(self.api).__call__(shell.auth,node['node_id'],tagname,value)
168 UpdateNodeTag(self.api).__call__(shell.auth,node_tags[0]['node_tag_id'],value)
170 def associate_interfaces(self, auth, field, value):
172 Delete interfaces not found in value list (using DeleteInterface)
173 Add interfaces found in value list (using AddInterface)
174 Updates interfaces found w/ interface_id in value list (using UpdateInterface)
177 assert 'interface_ids' in self
178 assert 'node_id' in self
179 assert isinstance(value, list)
181 (interface_ids, blank, interfaces) = self.separate_types(value)
183 if self['interface_ids'] != interface_ids:
184 from PLC.Methods.DeleteInterface import DeleteInterface
186 stale_interfaces = set(self['interface_ids']).difference(interface_ids)
188 for stale_interface in stale_interfaces:
189 DeleteInterface.__call__(DeleteInterface(self.api), auth, stale_interface['interface_id'])
191 def associate_conf_files(self, auth, field, value):
193 Add conf_files found in value list (AddConfFileToNode)
194 Delets conf_files not found in value list (DeleteConfFileFromNode)
197 assert 'conf_file_ids' in self
198 assert 'node_id' in self
199 assert isinstance(value, list)
201 conf_file_ids = self.separate_types(value)[0]
203 if self['conf_file_ids'] != conf_file_ids:
204 from PLC.Methods.AddConfFileToNode import AddConfFileToNode
205 from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
206 new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
207 stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
209 for new_conf_file in new_conf_files:
210 AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
211 for stale_conf_file in stale_conf_files:
212 DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
214 def associate_slices(self, auth, field, value):
216 Add slices found in value list to (AddSliceToNode)
217 Delete slices not found in value list (DeleteSliceFromNode)
220 from PLC.Slices import Slices
222 assert 'slice_ids' in self
223 assert 'node_id' in self
224 assert isinstance(value, list)
226 (slice_ids, slice_names) = self.separate_types(value)[0:2]
229 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
230 slice_ids += slices.keys()
232 if self['slice_ids'] != slice_ids:
233 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
234 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
235 new_slices = set(slice_ids).difference(self['slice_ids'])
236 stale_slices = set(self['slice_ids']).difference(slice_ids)
238 for new_slice in new_slices:
239 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
240 for stale_slice in stale_slices:
241 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
243 def associate_slices_whitelist(self, auth, field, value):
245 Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
246 Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
249 from PLC.Slices import Slices
251 assert 'slice_ids_whitelist' in self
252 assert 'node_id' in self
253 assert isinstance(value, list)
255 (slice_ids, slice_names) = self.separate_types(value)[0:2]
258 slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
259 slice_ids += slices.keys()
261 if self['slice_ids_whitelist'] != slice_ids:
262 from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
263 from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
264 new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
265 stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
267 for new_slice in new_slices:
268 AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
269 for stale_slice in stale_slices:
270 DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
274 def sync(self, commit=True, validate=True):
275 AlchemyObj.sync(self, commit=commit, validate=validate)
277 self['last_updated'] = ts
278 if 'node_id' not in self:
279 self['date_created'] = ts
280 AlchemyObj.insert(self, dict(self))
282 AlchemyObj.update(self, {'node_id': self['node_id']}, dict(self))
284 def delete(self, commit = True):
286 Delete existing node.
289 assert 'node_id' in self
290 assert 'interface_ids' in self
291 Interface().delete(filter={'interface_id': self['interface_ids']})
292 AlchemyObj.delete(self, dict(self))
296 Representation of row(s) from the nodes table in the
300 def __init__(self, api, node_filter = None, columns = None):
303 # as many left joins as requested tags
305 nodes = Node().select()
306 elif isinstance(node_filter, (list, tuple, set)):
307 # Separate the list into integers and strings
308 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
309 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
310 nodes = Node().select(filter={'node_id': ints, 'hostname': strs})
311 elif isinstance(node_filter, dict):
312 nodes = Node().select(filter={'node_id': ints, 'hostname': strs})
313 elif isinstance (node_filter, StringTypes):
314 nodes = Node().select(filter={'hostname': strs})
315 elif isinstance (node_filter, (int, long)):
316 nodes = Node().select(filter={'node_id': ints})
318 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter
321 node = Node(api, object=node)
322 if not columns or 'interface_ids' in columns:
323 interfaces = Interface().select(filter={'node_id': node['node_id']})
324 node['interface_ids'] = [rec.interface_id for rec in interfaces]
325 if not columns or 'conf_file_ids' in columns:
326 conf_files = ConfFileNode().select(filter={'node_id': node['node_id']})
327 node['conf_file_ids'] = [rec.conf_file_id for rec in conf_files]
328 if not columns or 'slice_ids' in columns:
329 slice_nodes = SliceNode().select(filter={'node_id': node['node_id']})
330 node['slice_ids'] = [rec.slice_id for rec in slices]
331 if not columns or 'slice_ids_whitelist' in columns:
332 slice_whitelist = SliceNodeWhitelist().select(filter={'node_id': node['node_id']})
333 node['slice_ids_whitelist'] = [rec.slice_id for rec in slice_whitelist]
334 if not columns or 'pcu_ids' in columns:
335 pcus = PCUNode().select(filter={'node_id': node['node_id']})
336 node['pcu_ids'] = [rec.pcu_id in rec in pcus]
337 if not columns or 'pcu_ports' in columns:
338 pcu_ports = PCUNodePort().select(filter={'node_id': node['node_id']})
339 node['pcu_ports'] = [rec.port for rec in pcu_ports]
340 if not columns or 'node_tag_ids' in columns:
341 node_tags = NodeTag().select(filter={'node_id': node['node_id']})
342 node['node_tag_ids'] = [rec.node_tag_id for rec in node_tags]
343 if not columns or 'nodegroup_ids' in columns:
344 nodegroups = NodeGroup().select(filter={'node_id': node['node_id']})
345 node['nodegroup_ids'] = [rec.nodegroup_id for rec in nodegroups]
348 def refresh(self, api):
349 from PLC.Sites import Sites
350 default_site = Sites(api, site_filter={'login_base': 'default'})[0]
351 # get current list of compute nodes
352 hypervisors = api.client_shell.nova.hypervisors.list()
353 compute_hosts = [h.hypervisor_hostname for h in hypervisors]
355 nodes = Node().select()
356 hostnames = [node.hostname for node in nodes]
358 added_nodes = set(compute_hosts).difference(hostnames)
359 for added_node in added_nodes:
360 node = Node(api, {'hostname': added_node,
361 'node_type': 'regular',
362 'site_id': default_site['site_id']})