server.py 5.4 KB
Newer Older
Michael Hanselmann's avatar
Michael Hanselmann committed
1
#
Guido Trotter's avatar
Guido Trotter committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#

# Copyright (C) 2009, Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Ganeti configuration daemon server library.

Ganeti-confd is a daemon to query master candidates for configuration values.
It uses UDP+HMAC for authentication with a global cluster key.

"""

import logging
import time

from ganeti import constants
from ganeti import objects
from ganeti import errors
from ganeti import utils
from ganeti import serializer
37
from ganeti import ssconf
Guido Trotter's avatar
Guido Trotter committed
38

Guido Trotter's avatar
Guido Trotter committed
39
40
from ganeti.confd import querylib

Guido Trotter's avatar
Guido Trotter committed
41
42
43
44

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

45
  @ivar reader: confd SimpleConfigReader
46
  @ivar disabled: whether confd serving is disabled
47

Guido Trotter's avatar
Guido Trotter committed
48
49
  """
  DISPATCH_TABLE = {
Michael Hanselmann's avatar
Michael Hanselmann committed
50
51
52
53
54
    constants.CONFD_REQ_PING: querylib.PingQuery,
    constants.CONFD_REQ_NODE_ROLE_BYNAME: querylib.NodeRoleQuery,
    constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP:
      querylib.InstanceIpToNodePrimaryIpQuery,
    constants.CONFD_REQ_CLUSTER_MASTER: querylib.ClusterMasterQuery,
55
56
    constants.CONFD_REQ_NODE_PIP_LIST: querylib.NodesPipsQuery,
    constants.CONFD_REQ_MC_PIP_LIST: querylib.MasterCandidatesPipsQuery,
57
    constants.CONFD_REQ_INSTANCES_IPS_LIST: querylib.InstancesIpsQuery,
Michael Hanselmann's avatar
Michael Hanselmann committed
58
    }
Guido Trotter's avatar
Guido Trotter committed
59

60
  def __init__(self):
61
    """Constructor for ConfdProcessor
Guido Trotter's avatar
Guido Trotter committed
62
63

    """
64
    self.disabled = True
Guido Trotter's avatar
Guido Trotter committed
65
    self.hmac_key = utils.ReadFile(constants.HMAC_CLUSTER_KEY)
66
    self.reader = None
67
68
69
    assert \
      not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
      "DISPATCH_TABLE is unaligned with CONFD_REQS"
Guido Trotter's avatar
Guido Trotter committed
70

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

  def Disable(self):
    self.disabled = True
    self.reader = None

Guido Trotter's avatar
Guido Trotter committed
83
84
85
86
87
88
89
90
91
92
93
  def ExecQuery(self, payload_in, ip, port):
    """Process a single UDP request from a client.

    @type payload_in: string
    @param payload_in: request raw data
    @type ip: string
    @param ip: source ip address
    @param port: integer
    @type port: source port

    """
94
95
96
    if self.disabled:
      logging.debug('Confd is disabled. Ignoring query.')
      return
Guido Trotter's avatar
Guido Trotter committed
97
98
99
100
101
102
    try:
      request = self.ExtractRequest(payload_in)
      reply, rsalt = self.ProcessRequest(request)
      payload_out = self.PackReply(reply, rsalt)
      return payload_out
    except errors.ConfdRequestError, err:
Iustin Pop's avatar
Iustin Pop committed
103
      logging.info('Ignoring broken query from %s:%d: %s', ip, port, err)
Guido Trotter's avatar
Guido Trotter committed
104
105
106
107
108
109
110
111
112
      return None

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

    This functions also performs signature/timestamp validation.

    """
    current_time = time.time()
Iustin Pop's avatar
Iustin Pop committed
113
    logging.debug("Extracting request with size: %d", len(payload))
Guido Trotter's avatar
Guido Trotter committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    try:
      (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
    except errors.SignatureError, err:
      msg = "invalid signature: %s" % err
      raise errors.ConfdRequestError(msg)
    try:
      message_timestamp = int(salt)
    except (ValueError, TypeError):
      msg = "non-integer timestamp: %s" % salt
      raise errors.ConfdRequestError(msg)

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

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

    return request

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

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

    """
Iustin Pop's avatar
Iustin Pop committed
145
    logging.debug("Processing request: %s", request)
Guido Trotter's avatar
Guido Trotter committed
146
147
148
149
150
151
152
153
154
155
156
157
158
    if request.protocol != constants.CONFD_PROTOCOL_VERSION:
      msg = "wrong protocol version %d" % request.protocol
      raise errors.ConfdRequestError(msg)

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

    rsalt = request.rsalt
    if not rsalt:
      msg = "missing requested salt"
      raise errors.ConfdRequestError(msg)

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

Iustin Pop's avatar
Iustin Pop committed
168
    logging.debug("Sending reply: %s", reply)
Guido Trotter's avatar
Guido Trotter committed
169
170
171
172
173
174
175
176
177
178
179

    return (reply, rsalt)

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

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

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