Statistics
| Branch: | Tag: | Revision:

root / lib / confd / server.py @ f299ca21

History | View | Annotate | Download (5.4 kB)

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
    constants.CONFD_REQ_INSTANCES_IPS_LIST: querylib.InstancesIpsQuery,
58
    }
59

    
60
  def __init__(self):
61
    """Constructor for ConfdProcessor
62

63
    """
64
    self.disabled = True
65
    self.hmac_key = utils.ReadFile(constants.CONFD_HMAC_KEY)
66
    self.reader = None
67
    assert \
68
      not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
69
      "DISPATCH_TABLE is unaligned with CONFD_REQS"
70

    
71
  def Enable(self):
72
    try:
73
      self.reader = ssconf.SimpleConfigReader()
74
      self.disabled = False
75
    except errors.ConfigurationError:
76
      self.disabled = True
77
      raise
78

    
79
  def Disable(self):
80
    self.disabled = True
81
    self.reader = None
82

    
83
  def ExecQuery(self, payload_in, ip, port):
84
    """Process a single UDP request from a client.
85

86
    @type payload_in: string
87
    @param payload_in: request raw data
88
    @type ip: string
89
    @param ip: source ip address
90
    @param port: integer
91
    @type port: source port
92

93
    """
94
    if self.disabled:
95
      logging.debug('Confd is disabled. Ignoring query.')
96
      return
97
    try:
98
      request = self.ExtractRequest(payload_in)
99
      reply, rsalt = self.ProcessRequest(request)
100
      payload_out = self.PackReply(reply, rsalt)
101
      return payload_out
102
    except errors.ConfdRequestError, err:
103
      logging.info('Ignoring broken query from %s:%d: %s', ip, port, err)
104
      return None
105

    
106
  def ExtractRequest(self, payload):
107
    """Extracts a ConfdRequest object from a serialized hmac signed string.
108

109
    This functions also performs signature/timestamp validation.
110

111
    """
112
    current_time = time.time()
113
    logging.debug("Extracting request with size: %d", len(payload))
114
    try:
115
      (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
116
    except errors.SignatureError, err:
117
      msg = "invalid signature: %s" % err
118
      raise errors.ConfdRequestError(msg)
119
    try:
120
      message_timestamp = int(salt)
121
    except (ValueError, TypeError):
122
      msg = "non-integer timestamp: %s" % salt
123
      raise errors.ConfdRequestError(msg)
124

    
125
    skew = abs(current_time - message_timestamp)
126
    if skew > constants.CONFD_MAX_CLOCK_SKEW:
127
      msg = "outside time range (skew: %d)" % skew
128
      raise errors.ConfdRequestError(msg)
129

    
130
    try:
131
      request = objects.ConfdRequest.FromDict(message)
132
    except AttributeError, err:
133
      raise errors.ConfdRequestError('%s' % err)
134

    
135
    return request
136

    
137
  def ProcessRequest(self, request):
138
    """Process one ConfdRequest request, and produce an answer
139

140
    @type request: L{objects.ConfdRequest}
141
    @rtype: (L{objects.ConfdReply}, string)
142
    @return: tuple of reply and salt to add to the signature
143

144
    """
145
    logging.debug("Processing request: %s", request)
146
    if request.protocol != constants.CONFD_PROTOCOL_VERSION:
147
      msg = "wrong protocol version %d" % request.protocol
148
      raise errors.ConfdRequestError(msg)
149

    
150
    if request.type not in constants.CONFD_REQS:
151
      msg = "wrong request type %d" % request.type
152
      raise errors.ConfdRequestError(msg)
153

    
154
    rsalt = request.rsalt
155
    if not rsalt:
156
      msg = "missing requested salt"
157
      raise errors.ConfdRequestError(msg)
158

    
159
    query_object = self.DISPATCH_TABLE[request.type](self.reader)
160
    status, answer = query_object.Exec(request.query)
161
    reply = objects.ConfdReply(
162
              protocol=constants.CONFD_PROTOCOL_VERSION,
163
              status=status,
164
              answer=answer,
165
              serial=self.reader.GetConfigSerialNo(),
166
              )
167

    
168
    logging.debug("Sending reply: %s", reply)
169

    
170
    return (reply, rsalt)
171

    
172
  def PackReply(self, reply, rsalt):
173
    """Serialize and sign the given reply, with salt rsalt
174

175
    @type reply: L{objects.ConfdReply}
176
    @type rsalt: string
177

178
    """
179
    return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)