Unify the “--backend-parameters” option
[ganeti-local] / lib / confd / server.py
1 #
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     constants.CONFD_REQ_CLUSTER_MASTER: querylib.ClusterMasterQuery,
55     constants.CONFD_REQ_NODE_PIP_LIST: querylib.NodesPipsQuery,
56     constants.CONFD_REQ_MC_PIP_LIST: querylib.MasterCandidatesPipsQuery,
57     }
58
59   def __init__(self):
60     """Constructor for ConfdProcessor
61
62     """
63     self.disabled = True
64     self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
65     self.reader = None
66     assert \
67       not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
68       "DISPATCH_TABLE is unaligned with CONFD_REQS"
69
70   def Enable(self):
71     try:
72       self.reader = ssconf.SimpleConfigReader()
73       self.disabled = False
74     except errors.ConfigurationError:
75       self.disabled = True
76       raise
77
78   def Disable(self):
79     self.disabled = True
80     self.reader = None
81
82   def ExecQuery(self, payload_in, ip, port):
83     """Process a single UDP request from a client.
84
85     @type payload_in: string
86     @param payload_in: request raw data
87     @type ip: string
88     @param ip: source ip address
89     @param port: integer
90     @type port: source port
91
92     """
93     if self.disabled:
94       logging.debug('Confd is disabled. Ignoring query.')
95       return
96     try:
97       request = self.ExtractRequest(payload_in)
98       reply, rsalt = self.ProcessRequest(request)
99       payload_out = self.PackReply(reply, rsalt)
100       return payload_out
101     except errors.ConfdRequestError, err:
102       logging.info('Ignoring broken query from %s:%d: %s' % (ip, port, err))
103       return None
104
105   def ExtractRequest(self, payload):
106     """Extracts a ConfdRequest object from a serialized hmac signed string.
107
108     This functions also performs signature/timestamp validation.
109
110     """
111     current_time = time.time()
112     logging.debug("Extracting request with size: %d" % (len(payload)))
113     try:
114       (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
115     except errors.SignatureError, err:
116       msg = "invalid signature: %s" % err
117       raise errors.ConfdRequestError(msg)
118     try:
119       message_timestamp = int(salt)
120     except (ValueError, TypeError):
121       msg = "non-integer timestamp: %s" % salt
122       raise errors.ConfdRequestError(msg)
123
124     skew = abs(current_time - message_timestamp)
125     if skew > constants.CONFD_MAX_CLOCK_SKEW:
126       msg = "outside time range (skew: %d)" % skew
127       raise errors.ConfdRequestError(msg)
128
129     try:
130       request = objects.ConfdRequest.FromDict(message)
131     except AttributeError, err:
132       raise errors.ConfdRequestError('%s' % err)
133
134     return request
135
136   def ProcessRequest(self, request):
137     """Process one ConfdRequest request, and produce an answer
138
139     @type request: L{objects.ConfdRequest}
140     @rtype: (L{objects.ConfdReply}, string)
141     @return: tuple of reply and salt to add to the signature
142
143     """
144     logging.debug("Processing request: %s" % request)
145     if request.protocol != constants.CONFD_PROTOCOL_VERSION:
146       msg = "wrong protocol version %d" % request.protocol
147       raise errors.ConfdRequestError(msg)
148
149     if request.type not in constants.CONFD_REQS:
150       msg = "wrong request type %d" % request.type
151       raise errors.ConfdRequestError(msg)
152
153     rsalt = request.rsalt
154     if not rsalt:
155       msg = "missing requested salt"
156       raise errors.ConfdRequestError(msg)
157
158     query_object = self.DISPATCH_TABLE[request.type](self.reader)
159     status, answer = query_object.Exec(request.query)
160     reply = objects.ConfdReply(
161               protocol=constants.CONFD_PROTOCOL_VERSION,
162               status=status,
163               answer=answer,
164               serial=self.reader.GetConfigSerialNo(),
165               )
166
167     logging.debug("Sending reply: %s" % reply)
168
169     return (reply, rsalt)
170
171   def PackReply(self, reply, rsalt):
172     """Serialize and sign the given reply, with salt rsalt
173
174     @type reply: L{objects.ConfdReply}
175     @type rsalt: string
176
177     """
178     return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)