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 # $Id: Nodes.py 800 2007-08-30 03:49:35Z thierry $
10 from types import StringTypes
13 from PLC.Faults import *
14 from PLC.Parameter import Parameter
15 from PLC.Filter import Filter
16 from PLC.Debug import profile
17 from PLC.Table import Row, Table
18 from PLC.NodeNetworks import NodeNetwork, NodeNetworks
19 from PLC.BootStates import BootStates
21 def valid_hostname(hostname):
22 # 1. Each part begins and ends with a letter or number.
23 # 2. Each part except the last can contain letters, numbers, or hyphens.
24 # 3. Each part is between 1 and 64 characters, including the trailing dot.
25 # 4. At least two parts.
26 # 5. Last part can only contain between 2 and 6 letters.
27 good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
30 re.match(good_hostname, hostname, re.IGNORECASE)
34 Representation of a row in the nodes table. To use, optionally
35 instantiate with a dict of values. Update as you would a
36 dict. Commit to the database with sync().
40 primary_key = 'node_id'
41 # Thierry -- we use delete on nodenetworks so the related NodeNetworkSettings get deleted too
42 join_tables = ['nodegroup_node', 'conf_file_node', 'pcu_node', 'slice_node', 'slice_attribute', 'node_session', 'peer_node','node_slice_whitelist']
44 'node_id': Parameter(int, "Node identifier"),
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 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
49 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
50 'version': Parameter(str, "Apparent Boot CD version", max = 64),
51 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
52 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
53 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
54 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
55 'key': Parameter(str, "(Admin only) Node key", max = 256),
56 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
57 'nodenetwork_ids': Parameter([int], "List of network interfaces that this node has"),
58 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
59 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
60 # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
61 'slice_ids': Parameter([int], "List of slices on this node"),
62 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
63 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
64 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
65 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
66 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
70 class_key = 'hostname'
71 foreign_fields = ['boot_state','model','version']
72 # forget about these ones, they are read-only anyway
73 # handling them causes Cache to re-sync all over again
74 # 'date_created','last_updated'
76 # in this case, we dont need the 'table' but Cache will look it up, so...
77 {'field' : 'site_id' , 'class' : 'Site' , 'table' : 'unused-on-direct-refs' } ,
80 def validate_hostname(self, hostname):
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_boot_state(self, boot_state):
92 boot_states = [row['boot_state'] for row in BootStates(self.api)]
93 if boot_state not in boot_states:
94 raise PLCInvalidArgument, "Invalid boot state"
98 validate_date_created = Row.validate_timestamp
99 validate_last_updated = Row.validate_timestamp
100 validate_last_contact = Row.validate_timestamp
102 def update_last_contact(self, commit = True):
104 Update last_contact field with current time
107 assert 'node_id' in self
108 assert self.table_name
110 self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
111 " where node_id = %d" % ( self['node_id']) )
114 def delete(self, commit = True):
116 Delete existing node.
119 assert 'node_id' in self
120 assert 'nodenetwork_ids' in self
122 # we need to clean up NodeNetworkSettings, so handling nodenetworks as part of join_tables does not work
123 for nodenetwork in NodeNetworks(self.api,self['nodenetwork_ids']):
126 # Clean up miscellaneous join tables
127 for table in self.join_tables:
128 self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
129 (table, self['node_id']))
132 self['deleted'] = True
138 Representation of row(s) from the nodes table in the
142 def __init__(self, api, node_filter = None, columns = None):
143 Table.__init__(self, api, Node, columns)
145 sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
146 ", ".join(self.columns)
148 if node_filter is not None:
149 if isinstance(node_filter, (list, tuple, set)):
150 # Separate the list into integers and strings
151 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
152 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
153 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
154 sql += " AND (%s)" % node_filter.sql(api, "OR")
155 elif isinstance(node_filter, dict):
156 node_filter = Filter(Node.fields, node_filter)
157 sql += " AND (%s)" % node_filter.sql(api, "AND")
158 elif isinstance (node_filter, StringTypes):
159 node_filter = Filter(Node.fields, {'hostname':[node_filter]})
160 sql += " AND (%s)" % node_filter.sql(api, "AND")
161 elif isinstance (node_filter, int):
162 node_filter = Filter(Node.fields, {'node_id':[node_filter]})
163 sql += " AND (%s)" % node_filter.sql(api, "AND")
165 raise PLCInvalidArgument, "Wrong node filter %r"%node_filter