Statistics
| Branch: | Tag: | Revision:

root / lib / confd / server.py @ 4d5db19c

History | View | Annotate | Download (4.5 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

    
38

    
39
class ConfdProcessor(object):
40
  """A processor for confd requests.
41

42
  """
43
  DISPATCH_TABLE = {
44
  }
45

    
46
  def __init__(self, reader):
47
    """Constructor for ConfdAsyncUDPServer
48

49
    @type reader: L{ssconf.SimpleConfigReader}
50
    @param reader: ConfigReader to use to access the config
51

52
    """
53
    self.reader = reader
54
    self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
55

    
56
  def ExecQuery(self, payload_in, ip, port):
57
    """Process a single UDP request from a client.
58

59
    @type payload_in: string
60
    @param payload_in: request raw data
61
    @type ip: string
62
    @param ip: source ip address
63
    @param port: integer
64
    @type port: source port
65

66
    """
67
    try:
68
      request = self.ExtractRequest(payload_in)
69
      reply, rsalt = self.ProcessRequest(request)
70
      payload_out = self.PackReply(reply, rsalt)
71
      return payload_out
72
    except errors.ConfdRequestError, err:
73
      logging.info('Ignoring broken query from %s:%d: %s' % (ip, port, err))
74
      return None
75

    
76
  def ExtractRequest(self, payload):
77
    """Extracts a ConfdRequest object from a serialized hmac signed string.
78

79
    This functions also performs signature/timestamp validation.
80

81
    """
82
    current_time = time.time()
83
    logging.debug("Extracting request with size: %d" % (len(payload)))
84
    try:
85
      (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
86
    except errors.SignatureError, err:
87
      msg = "invalid signature: %s" % err
88
      raise errors.ConfdRequestError(msg)
89
    try:
90
      message_timestamp = int(salt)
91
    except (ValueError, TypeError):
92
      msg = "non-integer timestamp: %s" % salt
93
      raise errors.ConfdRequestError(msg)
94

    
95
    skew = abs(current_time - message_timestamp)
96
    if skew > constants.CONFD_MAX_CLOCK_SKEW:
97
      msg = "outside time range (skew: %d)" % skew
98
      raise errors.ConfdRequestError(msg)
99

    
100
    try:
101
      request = objects.ConfdRequest.FromDict(message)
102
    except AttributeError, err:
103
      raise errors.ConfdRequestError('%s' % err)
104

    
105
    return request
106

    
107
  def ProcessRequest(self, request):
108
    """Process one ConfdRequest request, and produce an answer
109

110
    @type request: L{objects.ConfdRequest}
111
    @rtype: (L{objects.ConfdReply}, string)
112
    @return: tuple of reply and salt to add to the signature
113

114
    """
115
    logging.debug("Processing request: %s" % request)
116
    if request.protocol != constants.CONFD_PROTOCOL_VERSION:
117
      msg = "wrong protocol version %d" % request.protocol
118
      raise errors.ConfdRequestError(msg)
119

    
120
    if request.type not in constants.CONFD_REQS:
121
      msg = "wrong request type %d" % request.type
122
      raise errors.ConfdRequestError(msg)
123

    
124
    rsalt = request.rsalt
125
    if not rsalt:
126
      msg = "missing requested salt"
127
      raise errors.ConfdRequestError(msg)
128

    
129
    if request.type not in self.DISPATCH_TABLE:
130
      answer = 'not implemented'
131
      status = constants.CONFD_REPL_STATUS_NOTIMPLEMENTED
132
      reply = objects.ConfdReply(
133
                protocol=constants.CONFD_PROTOCOL_VERSION,
134
                status=status,
135
                answer=answer,
136
                )
137
    else:
138
      # TODO: actually dispatch queries to some classes to handle them
139
      assert False, "DISPATCH_TABLE is populated but handler is not"
140

    
141
    logging.debug("Sending reply: %s" % reply)
142

    
143
    return (reply, rsalt)
144

    
145
  def PackReply(self, reply, rsalt):
146
    """Serialize and sign the given reply, with salt rsalt
147

148
    @type reply: L{objects.ConfdReply}
149
    @type rsalt: string
150

151
    """
152
    return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)
153