Merge branch 'newinterface' of ssh://bakers@git.planet-lab.org/git/plcapi into newint...
[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     # needed for generating the doc and prevent conflicts in the xml ids
85     status = 'legacy'
86
87     def call(self, auth, node_id_or_hostname, node_fields):
88
89         # split provided fields
90         [native,related,tags,rejected] = Row.split_fields(node_fields,[legacy_node_fields,Node.related_fields,Node.tags])
91
92         # type checking
93         native = Row.check_fields (native, self.accepted_fields)
94         if rejected:
95             raise PLCInvalidArgument, "Cannot update Node column(s) %r"%rejected
96
97         # Authenticated function
98         assert self.caller is not None
99
100         # Remove admin only fields
101         if 'admin' not in self.caller['roles']:
102             for key in admin_only:
103                 if native.has_key(key):
104                     del native[key]
105
106         # Get account information
107         nodes = Nodes(self.api, [node_id_or_hostname])
108         if not nodes:
109             raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
110         node = nodes[0]
111
112         if node['peer_id'] is not None:
113             raise PLCInvalidArgument, "Not a local node %r"%node_id_or_hostname
114
115         # If we are not an admin, make sure that the caller is a
116         # member of the site at which the node is located.
117         if 'admin' not in self.caller['roles']:
118             if node['site_id'] not in self.caller['site_ids']:
119                 raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
120
121         # Make requested associations
122         for (k,v) in related.iteritems():
123             node.associate(auth, k,v)
124
125         node.update(native)
126         node.update_last_updated(commit=False)
127         node.sync(commit=True)
128
129         # if hostname was modifed make sure to update the hrn
130         # tag
131         if 'hostname' in native:
132             root_auth = self.api.config.PLC_HRN_ROOT
133             # sub auth is the login base of this node's site
134             sites = Sites(self.api, node['site_id'], ['login_base'])
135             site = sites[0]
136             login_base = site['login_base']
137             tags['hrn'] = hostname_to_hrn(root_auth, login_base, node['hostname'])
138
139         for (tagname,value) in tags.iteritems():
140             # the tagtype instance is assumed to exist, just check that
141             tag_types = TagTypes(self.api,{'tagname':tagname})
142             if not tag_types:
143                 raise PLCInvalidArgument,"No such TagType %s"%tagname
144             tag_type = tag_types[0]
145             node_tags=NodeTags(self.api,{'tagname':tagname,'node_id':node['node_id']})
146             if not node_tags:
147                 node_tag = NodeTag(self.api)
148                 node_tag['node_id'] = node['node_id']
149                 node_tag['tag_type_id'] = tag_type['tag_type_id']
150                 node_tag['tagname']  = tagname
151                 node_tag['value'] = value
152                 node_tag.sync()
153             else:
154                 node_tag = node_tags[0]
155                 node_tag['value'] = value
156                 node_tag.sync()
157         # Logging variables
158         self.event_objects = {'Node': [node['node_id']]}
159         if 'hostname' in node:
160             self.message = 'Node %s updated'%node['hostname']
161         else:
162             self.message = 'Node %d updated'%node['node_id']
163         self.message += " [%s]." % (", ".join(node_fields.keys()),)
164         if 'boot_state' in node_fields.keys():
165             self.message += ' boot_state updated to %s' % node_fields['boot_state']
166
167         return 1