Statistics
| Branch: | Tag: | Revision:

root / lib / confd / server.py @ e369f21d

History | View | Annotate | Download (5.2 kB)

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