601b95759972cc6bdbf0ed28c8bdbb09ddb5c294
[ganeti-local] / lib / confd / server.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2009, Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Ganeti configuration daemon server library.
23
24 Ganeti-confd is a daemon to query master candidates for configuration values.
25 It uses UDP+HMAC for authentication with a global cluster key.
26
27 """
28
29 import logging
30 import time
31
32 from ganeti import constants
33 from ganeti import objects
34 from ganeti import errors
35 from ganeti import utils
36 from ganeti import serializer
37 from ganeti import ssconf
38
39 from ganeti.confd import querylib
40
41
42 class ConfdProcessor(object):
43   """A processor for confd requests.
44
45   @ivar reader: confd SimpleConfigReader
46   @ivar disabled: whether confd serving is disabled
47
48   """
49   DISPATCH_TABLE = {
50       constants.CONFD_REQ_PING: querylib.PingQuery,
51       constants.CONFD_REQ_NODE_ROLE_BYNAME: querylib.NodeRoleQuery,
52       constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP:
53         querylib.InstanceIpToNodePrimaryIpQuery,
54   }
55
56   def __init__(self):
57     """Constructor for ConfdProcessor
58
59     """
60     self.disabled = True
61     self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
62     self.reader = None
63     assert \
64       not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
65       "DISPATCH_TABLE is unaligned with CONFD_REQS"
66
67   def Enable(self):
68     try:
69       self.reader = ssconf.SimpleConfigReader()
70       self.disabled = False
71     except errors.ConfigurationError:
72       self.disabled = True
73       raise
74
75   def Disable(self):
76     self.disabled = True
77     self.reader = None
78
79   def ExecQuery(self, payload_in, ip, port):
80     """Process a single UDP request from a client.
81
82     @type payload_in: string
83     @param payload_in: request raw data
84     @type ip: string
85     @param ip: source ip address
86     @param port: integer
87     @type port: source port
88
89     """
90     if self.disabled:
91       logging.debug('Confd is disabled. Ignoring query.')
92       return
93     try:
94       request = self.ExtractRequest(payload_in)
95       reply, rsalt = self.ProcessRequest(request)
96       payload_out = self.PackReply(reply, rsalt)
97       return payload_out
98     except errors.ConfdRequestError, err:
99       logging.info('Ignoring broken query from %s:%d: %s' % (ip, port, err))
100       return None
101
102   def ExtractRequest(self, payload):
103     """Extracts a ConfdRequest object from a serialized hmac signed string.
104
105     This functions also performs signature/timestamp validation.
106
107     """
108     current_time = time.time()
109     logging.debug("Extracting request with size: %d" % (len(payload)))
110     try:
111       (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
112     except errors.SignatureError, err:
113       msg = "invalid signature: %s" % err
114       raise errors.ConfdRequestError(msg)
115     try:
116       message_timestamp = int(salt)
117     except (ValueError, TypeError):
118       msg = "non-integer timestamp: %s" % salt
119       raise errors.ConfdRequestError(msg)
120
121     skew = abs(current_time - message_timestamp)
122     if skew > constants.CONFD_MAX_CLOCK_SKEW:
123       msg = "outside time range (skew: %d)" % skew
124       raise errors.ConfdRequestError(msg)
125
126     try:
127       request = objects.ConfdRequest.FromDict(message)
128     except AttributeError, err:
129       raise errors.ConfdRequestError('%s' % err)
130
131     return request
132
133   def ProcessRequest(self, request):
134     """Process one ConfdRequest request, and produce an answer
135
136     @type request: L{objects.ConfdRequest}
137     @rtype: (L{objects.ConfdReply}, string)
138     @return: tuple of reply and salt to add to the signature
139
140     """
141     logging.debug("Processing request: %s" % request)
142     if request.protocol != constants.CONFD_PROTOCOL_VERSION:
143       msg = "wrong protocol version %d" % request.protocol
144       raise errors.ConfdRequestError(msg)
145
146     if request.type not in constants.CONFD_REQS:
147       msg = "wrong request type %d" % request.type
148       raise errors.ConfdRequestError(msg)
149
150     rsalt = request.rsalt
151     if not rsalt:
152       msg = "missing requested salt"
153       raise errors.ConfdRequestError(msg)
154
155     query_object = self.DISPATCH_TABLE[request.type](self.reader)
156     status, answer = query_object.Exec(request.query)
157     reply = objects.ConfdReply(
158               protocol=constants.CONFD_PROTOCOL_VERSION,
159               status=status,
160               answer=answer,
161               serial=self.reader.GetConfigSerialNo(),
162               )
163
164     logging.debug("Sending reply: %s" % reply)
165
166     return (reply, rsalt)
167
168   def PackReply(self, reply, rsalt):
169     """Serialize and sign the given reply, with salt rsalt
170
171     @type reply: L{objects.ConfdReply}
172     @type rsalt: string
173
174     """
175     return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)
176