4 # Copyright (C) 2009, Google Inc.
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.
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.
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
22 """Ganeti configuration daemon server library.
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.
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
39 from ganeti.confd import querylib
42 class ConfdProcessor(object):
43 """A processor for confd requests.
45 @ivar reader: confd SimpleConfigReader
46 @ivar disabled: whether confd serving is disabled
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,
60 """Constructor for ConfdProcessor
64 self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
67 not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
68 "DISPATCH_TABLE is unaligned with CONFD_REQS"
72 self.reader = ssconf.SimpleConfigReader()
74 except errors.ConfigurationError:
82 def ExecQuery(self, payload_in, ip, port):
83 """Process a single UDP request from a client.
85 @type payload_in: string
86 @param payload_in: request raw data
88 @param ip: source ip address
90 @type port: source port
94 logging.debug('Confd is disabled. Ignoring query.')
97 request = self.ExtractRequest(payload_in)
98 reply, rsalt = self.ProcessRequest(request)
99 payload_out = self.PackReply(reply, rsalt)
101 except errors.ConfdRequestError, err:
102 logging.info('Ignoring broken query from %s:%d: %s' % (ip, port, err))
105 def ExtractRequest(self, payload):
106 """Extracts a ConfdRequest object from a serialized hmac signed string.
108 This functions also performs signature/timestamp validation.
111 current_time = time.time()
112 logging.debug("Extracting request with size: %d" % (len(payload)))
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)
119 message_timestamp = int(salt)
120 except (ValueError, TypeError):
121 msg = "non-integer timestamp: %s" % salt
122 raise errors.ConfdRequestError(msg)
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)
130 request = objects.ConfdRequest.FromDict(message)
131 except AttributeError, err:
132 raise errors.ConfdRequestError('%s' % err)
136 def ProcessRequest(self, request):
137 """Process one ConfdRequest request, and produce an answer
139 @type request: L{objects.ConfdRequest}
140 @rtype: (L{objects.ConfdReply}, string)
141 @return: tuple of reply and salt to add to the signature
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)
149 if request.type not in constants.CONFD_REQS:
150 msg = "wrong request type %d" % request.type
151 raise errors.ConfdRequestError(msg)
153 rsalt = request.rsalt
155 msg = "missing requested salt"
156 raise errors.ConfdRequestError(msg)
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,
164 serial=self.reader.GetConfigSerialNo(),
167 logging.debug("Sending reply: %s" % reply)
169 return (reply, rsalt)
171 def PackReply(self, reply, rsalt):
172 """Serialize and sign the given reply, with salt rsalt
174 @type reply: L{objects.ConfdReply}
178 return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)