iteration 4 & last:
[plcapi.git] / PLC / Nodes.py
1 #
2 # Functions for interacting with the nodes table in the database
3 #
4 # Mark Huang <mlhuang@cs.princeton.edu>
5 # Copyright (C) 2006 The Trustees of Princeton University
6 #
7 # $Id: Nodes.py,v 1.20 2006/11/09 19:43:55 mlhuang Exp $
8 #
9
10 from types import StringTypes
11 import re
12
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
20
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])?\.)+' \
28                     r'[a-z]{2,6}$'
29     return hostname and \
30            re.match(good_hostname, hostname, re.IGNORECASE)
31
32 class Node(Row):
33     """
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().
37     """
38
39     table_name = 'nodes'
40     primary_key = 'node_id'
41     join_tables = ['nodegroup_node', 'conf_file_node', 'nodenetworks', 'pcu_node', 'slice_node', 'slice_attribute', 'node_session']
42     fields = {
43         'node_id': Parameter(int, "Node identifier"),
44         'hostname': Parameter(str, "Fully qualified hostname", max = 255),
45         'site_id': Parameter(int, "Site at which this node is located"),
46         'peer_id': Parameter(int, "Peer at which this node is managed", nullok = True),
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         'key': Parameter(str, "(Admin only) Node key", max = 256),
55         'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
56         'nodenetwork_ids': Parameter([int], "List of network interfaces that this node has"),
57         'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
58         'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
59         # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
60         'slice_ids': Parameter([int], "List of slices on this node"),
61         'pcu_ids': Parameter([int], "List of PCUs that control this node"),
62         'ports': Parameter([int], "List of PCU ports that this node is connected to"),
63         }
64
65     # foreign attributes management
66     # the key to track remote objects
67     foreign_key = 'hostname'
68     # the fields that get verbatim copied from foreign objects
69     foreign_fields = ['boot_state','model','version','date_created','last_updated']
70
71     def validate_hostname(self, hostname):
72         if not valid_hostname(hostname):
73             raise PLCInvalidArgument, "Invalid hostname"
74
75         conflicts = Nodes(self.api, [hostname])
76         for node in conflicts:
77             if 'node_id' not in self or self['node_id'] != node['node_id']:
78                 raise PLCInvalidArgument, "Hostname already in use"
79
80         return hostname
81
82     def validate_boot_state(self, boot_state):
83         boot_states = [row['boot_state'] for row in BootStates(self.api)]
84         if boot_state not in boot_states:
85             raise PLCInvalidArgument, "Invalid boot state"
86
87         return boot_state
88
89     def delete(self, commit = True):
90         """
91         Delete existing node.
92         """
93
94         assert 'node_id' in self
95
96         # Clean up miscellaneous join tables
97         for table in self.join_tables:
98             self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
99                            (table, self['node_id']))
100
101         # Mark as deleted
102         self['deleted'] = True
103         self.sync(commit)
104
105
106 class Nodes(Table):
107     """
108     Representation of row(s) from the nodes table in the
109     database.
110     """
111
112     def __init__(self, api, node_filter = None, columns = None, scope = 'all'):
113         Table.__init__(self, api, Node, columns)
114
115         sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
116               ", ".join(self.columns)
117
118         if node_filter is not None:
119             if isinstance(node_filter, (list, tuple, set)):
120                 # Separate the list into integers and strings
121                 ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
122                 strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
123                 node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
124                 sql += " AND (%s)" % node_filter.sql(api, "OR")
125             elif isinstance(node_filter, dict):
126                 node_filter = Filter(Node.fields, node_filter)
127                 sql += " AND (%s)" % node_filter.sql(api, "AND")
128
129         if scope == 'local':
130             sql += " AND (peer_id is NULL) "
131         elif scope == 'foreign':
132             sql += " AND (peer_id is NOT NULL) "
133
134         self.selectall(sql)
135