Statistics
| Branch: | Tag: | Revision:

root / lib / confd / server.py @ 05f1ebf3

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

47
  """
48
  DISPATCH_TABLE = {
49
      constants.CONFD_REQ_PING: querylib.PingQuery,
50
      constants.CONFD_REQ_NODE_ROLE_BYNAME: querylib.NodeRoleQuery,
51
      constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP:
52
        querylib.InstanceIpToNodePrimaryIpQuery,
53
  }
54

    
55
  def __init__(self):
56
    """Constructor for ConfdProcessor
57

58
    """
59
    self.reader = ssconf.SimpleConfigReader()
60
    self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
61
    assert \
62
      not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
63
      "DISPATCH_TABLE is unaligned with CONFD_REQS"
64

    
65
  def ExecQuery(self, payload_in, ip, port):
66
    """Process a single UDP request from a client.
67

68
    @type payload_in: string
69
    @param payload_in: request raw data
70
    @type ip: string
71
    @param ip: source ip address
72
    @param port: integer
73
    @type port: source port
74

75
    """
76
    try:
77
      request = self.ExtractRequest(payload_in)
78
      reply, rsalt = self.ProcessRequest(request)
79
      payload_out = self.PackReply(reply, rsalt)
80
      return payload_out
81
    except errors.ConfdRequestError, err:
82
      logging.info('Ignoring broken query from %s:%d: %s' % (ip, port, err))
83
      return None
84

    
85
  def ExtractRequest(self, payload):
86
    """Extracts a ConfdRequest object from a serialized hmac signed string.
87

88
    This functions also performs signature/timestamp validation.
89

90
    """
91
    current_time = time.time()
92
    logging.debug("Extracting request with size: %d" % (len(payload)))
93
    try:
94
      (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
95
    except errors.SignatureError, err:
96
      msg = "invalid signature: %s" % err
97
      raise errors.ConfdRequestError(msg)
98
    try:
99
      message_timestamp = int(salt)
100
    except (ValueError, TypeError):
101
      msg = "non-integer timestamp: %s" % salt
102
      raise errors.ConfdRequestError(msg)
103

    
104
    skew = abs(current_time - message_timestamp)
105
    if skew > constants.CONFD_MAX_CLOCK_SKEW:
106
      msg = "outside time range (skew: %d)" % skew
107
      raise errors.ConfdRequestError(msg)
108

    
109
    try:
110
      request = objects.ConfdRequest.FromDict(message)
111
    except AttributeError, err:
112
      raise errors.ConfdRequestError('%s' % err)
113

    
114
    return request
115

    
116
  def ProcessRequest(self, request):
117
    """Process one ConfdRequest request, and produce an answer
118

119
    @type request: L{objects.ConfdRequest}
120
    @rtype: (L{objects.ConfdReply}, string)
121
    @return: tuple of reply and salt to add to the signature
122

123
    """
124
    logging.debug("Processing request: %s" % request)
125
    if request.protocol != constants.CONFD_PROTOCOL_VERSION:
126
      msg = "wrong protocol version %d" % request.protocol
127
      raise errors.ConfdRequestError(msg)
128

    
129
    if request.type not in constants.CONFD_REQS:
130
      msg = "wrong request type %d" % request.type
131
      raise errors.ConfdRequestError(msg)
132

    
133
    rsalt = request.rsalt
134
    if not rsalt:
135
      msg = "missing requested salt"
136
      raise errors.ConfdRequestError(msg)
137

    
138
    query_object = self.DISPATCH_TABLE[request.type](self.reader)
139
    status, answer = query_object.Exec(request.query)
140
    reply = objects.ConfdReply(
141
              protocol=constants.CONFD_PROTOCOL_VERSION,
142
              status=status,
143
              answer=answer,
144
              serial=self.reader.GetConfigSerialNo(),
145
              )
146

    
147
    logging.debug("Sending reply: %s" % reply)
148

    
149
    return (reply, rsalt)
150

    
151
  def PackReply(self, reply, rsalt):
152
    """Serialize and sign the given reply, with salt rsalt
153

154
    @type reply: L{objects.ConfdReply}
155
    @type rsalt: string
156

157
    """
158
    return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)
159