bring over newinterface branch from Verivue
[plcapi.git] / PLC / Methods / Legacy / UpdateNode.py
1 # $Id$
2 # $URL$
3 from PLC.Faults import *
4 from PLC.Method import Method
5 from PLC.Parameter import Parameter, Mixed
6 from PLC.Table import Row
7 from PLC.Auth import Auth
8 from PLC.Namespace import hostname_to_hrn
9 from PLC.Peers import Peers
10 from PLC.Sites import Sites
11 from PLC.Nodes import Node, Nodes
12 from PLC.TagTypes import TagTypes
13 from PLC.NodeTags import NodeTags, NodeTag
14 from PLC.Methods.AddNodeTag import AddNodeTag
15 from PLC.Methods.UpdateNodeTag import UpdateNodeTag
16
17 admin_only = [ 'key', 'session', 'boot_nonce', 'site_id']
18 can_update = ['hostname', 'node_type', 'boot_state', 'model', 'version'] + admin_only
19
20 legacy_node_fields = {
21     'node_id': Parameter(int, "Node identifier"),
22     'node_type': Parameter(str,"Node type",max=20),
23     'hostname': Parameter(str, "Fully qualified hostname", max = 255),
24     'site_id': Parameter(int, "Site at which this node is located"),
25     'boot_state': Parameter(str, "Boot state", max = 20),
26     'run_level': Parameter(str, "Run level", max = 20),
27     'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
28     'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
29     'version': Parameter(str, "Apparent Boot CD version", max = 64),
30     'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
31     'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
32     'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
33     'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
34     'last_boot': Parameter(int, "Date and time when node last booted", ro = True),
35     'last_download': Parameter(int, "Date and time when node boot image was created", ro = True),
36     'last_pcu_reboot': Parameter(int, "Date and time when PCU reboot was attempted", ro = True),
37     'last_pcu_confirmation': Parameter(int, "Date and time when PCU reboot was confirmed", ro = True),
38     'last_time_spent_online': Parameter(int, "Length of time the node was last online before shutdown/failure", ro = True),
39     'last_time_spent_offline': Parameter(int, "Length of time the node was last offline after failure and before reboot", ro = True),
40     'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False),
41     'key': Parameter(str, "(Admin only) Node key", max = 256),
42     'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
43     'interface_ids': Parameter([int], "List of network interfaces that this node has"),
44     'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
45     # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
46     'slice_ids': Parameter([int], "List of slices on this node"),
47     'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
48     'pcu_ids': Parameter([int], "List of PCUs that control this node"),
49     'ports': Parameter([int], "List of PCU ports that this node is connected to"),
50     'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
51     'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
52     'node_tag_ids' : Parameter ([int], "List of tags attached to this node"),
53     'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
54     }
55
56
57 class UpdateNode(Method):
58     """
59     Updates a node. Only the fields specified in node_fields are
60     updated, all other fields are left untouched.
61
62     PIs and techs can update only the nodes at their sites. Only
63     admins can update the key, session, and boot_nonce fields.
64
65     Returns 1 if successful, faults otherwise.
66     """
67
68     roles = ['admin', 'pi', 'tech']
69
70     accepted_fields = Row.accepted_fields(can_update,legacy_node_fields)
71     # xxx check the related_fields feature
72     accepted_fields.update(Node.related_fields)
73     accepted_fields.update(Node.tags)
74
75     accepts = [
76         Auth(),
77         Mixed(Node.fields['node_id'],
78               Node.fields['hostname']),
79         accepted_fields
80         ]
81
82     returns = Parameter(int, '1 if successful')
83
84     def call(self, auth, node_id_or_hostname, node_fields):
85
86         # split provided fields
87         [native,related,tags,rejected] = Row.split_fields(node_fields,[legacy_node_fields,Node.related_fields,Node.tags])
88
89         # type checking
90         native = Row.check_fields (native, self.accepted_fields)
91         if rejected:
92             raise PLCInvalidArgument, "Cannot update Node column(s) %r"%rejected
93
94         # Authenticated function
95         assert self.caller is not None
96
97         # Remove admin only fields
98         if 'admin' not in self.caller['roles']:
99             for key in admin_only:
100                 if native.has_key(key):
101                     del native[key]
102
103         # Get account information
104         nodes = Nodes(self.api, [node_id_or_hostname])
105         if not nodes:
106             raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
107         node = nodes[0]
108
109         if node['peer_id'] is not None:
110             raise PLCInvalidArgument, "Not a local node %r"%node_id_or_hostname
111
112         # If we are not an admin, make sure that the caller is a
113         # member of the site at which the node is located.
114         if 'admin' not in self.caller['roles']:
115             if node['site_id'] not in self.caller['site_ids']:
116                 raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
117
118         # Make requested associations
119         for (k,v) in related.iteritems():
120             node.associate(auth, k,v)
121
122         node.update(native)
123         node.update_last_updated(commit=False)
124         node.sync(commit=True)
125
126         # if hostname was modifed make sure to update the hrn
127         # tag
128         if 'hostname' in native:
129             root_auth = self.api.config.PLC_HRN_ROOT
130             # sub auth is the login base of this node's site
131             sites = Sites(self.api, node['site_id'], ['login_base'])
132             site = sites[0]
133             login_base = site['login_base']
134             tags['hrn'] = hostname_to_hrn(root_auth, login_base, node['hostname'])
135
136         for (tagname,value) in tags.iteritems():
137             # the tagtype instance is assumed to exist, just check that
138             tag_types = TagTypes(self.api,{'tagname':tagname})
139             if not tag_types:
140                 raise PLCInvalidArgument,"No such TagType %s"%tagname
141             tag_type = tag_types[0]
142             node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
143             if not node_tags:
144                 node_tag = NodeTag(self.api)
145                 node_tag['node_id'] = node['node_id']
146                 node_tag['tag_type_id'] = tag_type['tag_type_id']
147                 node_tag['tagname']  = tagname
148                 node_tag['value'] = value
149                 node_tag.sync()
150             else:
151                 node_tag = node_tags[0]
152                 node_tag['value'] = value
153                 node_tag.sync()
154         # Logging variables
155         self.event_objects = {'Node': [node['node_id']]}
156         if 'hostname' in node:
157             self.message = 'Node %s updated'%node['hostname']
158         else:
159             self.message = 'Node %d updated'%node['node_id']
160         self.message += " [%s]." % (", ".join(node_fields.keys()),)
161         if 'boot_state' in node_fields.keys():
162             self.message += ' boot_state updated to %s' % node_fields['boot_state']
163
164         return 1