Statistics
| Branch: | Tag: | Revision:

root / lib / confd / client.py @ 90469357

History | View | Annotate | Download (6.2 kB)

1 e4ccf6cd Guido Trotter
#
2 e4ccf6cd Guido Trotter
#
3 e4ccf6cd Guido Trotter
4 e4ccf6cd Guido Trotter
# Copyright (C) 2009 Google Inc.
5 e4ccf6cd Guido Trotter
#
6 e4ccf6cd Guido Trotter
# This program is free software; you can redistribute it and/or modify
7 e4ccf6cd Guido Trotter
# it under the terms of the GNU General Public License as published by
8 e4ccf6cd Guido Trotter
# the Free Software Foundation; either version 2 of the License, or
9 e4ccf6cd Guido Trotter
# (at your option) any later version.
10 e4ccf6cd Guido Trotter
#
11 e4ccf6cd Guido Trotter
# This program is distributed in the hope that it will be useful, but
12 e4ccf6cd Guido Trotter
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 e4ccf6cd Guido Trotter
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 e4ccf6cd Guido Trotter
# General Public License for more details.
15 e4ccf6cd Guido Trotter
#
16 e4ccf6cd Guido Trotter
# You should have received a copy of the GNU General Public License
17 e4ccf6cd Guido Trotter
# along with this program; if not, write to the Free Software
18 e4ccf6cd Guido Trotter
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 e4ccf6cd Guido Trotter
# 02110-1301, USA.
20 e4ccf6cd Guido Trotter
21 e4ccf6cd Guido Trotter
22 e4ccf6cd Guido Trotter
"""Ganeti confd client
23 e4ccf6cd Guido Trotter

24 e4ccf6cd Guido Trotter
"""
25 e4ccf6cd Guido Trotter
import socket
26 e4ccf6cd Guido Trotter
import time
27 e4ccf6cd Guido Trotter
import random
28 e4ccf6cd Guido Trotter
29 e4ccf6cd Guido Trotter
from ganeti import utils
30 e4ccf6cd Guido Trotter
from ganeti import constants
31 e4ccf6cd Guido Trotter
from ganeti import objects
32 e4ccf6cd Guido Trotter
from ganeti import serializer
33 e4ccf6cd Guido Trotter
from ganeti import daemon # contains AsyncUDPSocket
34 e4ccf6cd Guido Trotter
from ganeti import errors
35 e4ccf6cd Guido Trotter
from ganeti import confd
36 e4ccf6cd Guido Trotter
37 e4ccf6cd Guido Trotter
38 e4ccf6cd Guido Trotter
class ConfdAsyncUDPClient(daemon.AsyncUDPSocket):
39 e4ccf6cd Guido Trotter
  """Confd udp asyncore client
40 e4ccf6cd Guido Trotter

41 e4ccf6cd Guido Trotter
  This is kept separate from the main ConfdClient to make sure it's easy to
42 e4ccf6cd Guido Trotter
  implement a non-asyncore based client library.
43 e4ccf6cd Guido Trotter

44 e4ccf6cd Guido Trotter
  """
45 e4ccf6cd Guido Trotter
  def __init__(self, client):
46 e4ccf6cd Guido Trotter
    """Constructor for ConfdAsyncUDPClient
47 e4ccf6cd Guido Trotter

48 e4ccf6cd Guido Trotter
    @type client: L{ConfdClient}
49 e4ccf6cd Guido Trotter
    @param client: client library, to pass the datagrams to
50 e4ccf6cd Guido Trotter

51 e4ccf6cd Guido Trotter
    """
52 e4ccf6cd Guido Trotter
    daemon.AsyncUDPSocket.__init__(self)
53 e4ccf6cd Guido Trotter
    self.client = client
54 e4ccf6cd Guido Trotter
55 e4ccf6cd Guido Trotter
  # this method is overriding a daemon.AsyncUDPSocket method
56 e4ccf6cd Guido Trotter
  def handle_datagram(self, payload, ip, port):
57 e4ccf6cd Guido Trotter
    self.client.HandleResponse(payload, ip, port)
58 e4ccf6cd Guido Trotter
59 e4ccf6cd Guido Trotter
60 e4ccf6cd Guido Trotter
class ConfdClient:
61 e4ccf6cd Guido Trotter
  """Send queries to confd, and get back answers.
62 e4ccf6cd Guido Trotter

63 e4ccf6cd Guido Trotter
  Since the confd model works by querying multiple master candidates, and
64 e4ccf6cd Guido Trotter
  getting back answers, this is an asynchronous library. It can either work
65 e4ccf6cd Guido Trotter
  through asyncore or with your own handling.
66 e4ccf6cd Guido Trotter

67 e4ccf6cd Guido Trotter
  """
68 e4ccf6cd Guido Trotter
  def __init__(self, hmac_key, peers):
69 e4ccf6cd Guido Trotter
    """Constructor for ConfdClient
70 e4ccf6cd Guido Trotter

71 e4ccf6cd Guido Trotter
    @type hmac_key: string
72 e4ccf6cd Guido Trotter
    @param hmac_key: hmac key to talk to confd
73 e4ccf6cd Guido Trotter
    @type peers: list
74 e4ccf6cd Guido Trotter
    @param peers: list of peer nodes
75 e4ccf6cd Guido Trotter

76 e4ccf6cd Guido Trotter
    """
77 e4ccf6cd Guido Trotter
    if not isinstance(peers, list):
78 e4ccf6cd Guido Trotter
      raise errors.ProgrammerError("peers must be a list")
79 e4ccf6cd Guido Trotter
80 e4ccf6cd Guido Trotter
    self._peers = peers
81 e4ccf6cd Guido Trotter
    self._hmac_key = hmac_key
82 e4ccf6cd Guido Trotter
    self._socket = ConfdAsyncUDPClient(self)
83 e4ccf6cd Guido Trotter
    self._callbacks = {}
84 e4ccf6cd Guido Trotter
    self._expire_callbacks = []
85 e4ccf6cd Guido Trotter
    self._confd_port = utils.GetDaemonPort(constants.CONFD)
86 e4ccf6cd Guido Trotter
87 e4ccf6cd Guido Trotter
  def _PackRequest(self, request, now=None):
88 e4ccf6cd Guido Trotter
    """Prepare a request to be sent on the wire.
89 e4ccf6cd Guido Trotter

90 e4ccf6cd Guido Trotter
    This function puts a proper salt in a confd request, puts the proper salt,
91 e4ccf6cd Guido Trotter
    and adds the correct magic number.
92 e4ccf6cd Guido Trotter

93 e4ccf6cd Guido Trotter
    """
94 e4ccf6cd Guido Trotter
    if now is None:
95 e4ccf6cd Guido Trotter
      now = time.time()
96 e4ccf6cd Guido Trotter
    tstamp = '%d' % now
97 e4ccf6cd Guido Trotter
    req = serializer.DumpSignedJson(request.ToDict(), self._hmac_key, tstamp)
98 e4ccf6cd Guido Trotter
    return confd.PackMagic(req)
99 e4ccf6cd Guido Trotter
100 e4ccf6cd Guido Trotter
  def _UnpackReply(self, payload):
101 e4ccf6cd Guido Trotter
    in_payload = confd.UnpackMagic(payload)
102 e4ccf6cd Guido Trotter
    (answer, salt) = serializer.LoadSignedJson(in_payload, self._hmac_key)
103 e4ccf6cd Guido Trotter
    return answer, salt
104 e4ccf6cd Guido Trotter
105 e4ccf6cd Guido Trotter
  def _ExpireCallbacks(self):
106 e4ccf6cd Guido Trotter
    """Delete all the expired callbacks.
107 e4ccf6cd Guido Trotter

108 e4ccf6cd Guido Trotter
    """
109 e4ccf6cd Guido Trotter
    now = time.time()
110 e4ccf6cd Guido Trotter
    while self._expire_callbacks:
111 e4ccf6cd Guido Trotter
      expire_time, rsalt = self._expire_callbacks[0]
112 e4ccf6cd Guido Trotter
      if now >= expire_time:
113 e4ccf6cd Guido Trotter
        self._expire_callbacks.pop()
114 e4ccf6cd Guido Trotter
        del self._callbacks[rsalt]
115 e4ccf6cd Guido Trotter
      else:
116 e4ccf6cd Guido Trotter
        break
117 e4ccf6cd Guido Trotter
118 90469357 Guido Trotter
  def SendRequest(self, request, callback, args=None, coverage=None):
119 e4ccf6cd Guido Trotter
    """Send a confd request to some MCs
120 e4ccf6cd Guido Trotter

121 e4ccf6cd Guido Trotter
    @type request: L{objects.ConfdRequest}
122 e4ccf6cd Guido Trotter
    @param request: the request to send
123 e4ccf6cd Guido Trotter
    @type callback: f(answer, req_type, req_query, salt, ip, port, args)
124 e4ccf6cd Guido Trotter
    @param callback: answer callback
125 e4ccf6cd Guido Trotter
    @type args: tuple
126 90469357 Guido Trotter
    @keyword args: additional callback arguments
127 e4ccf6cd Guido Trotter
    @type coverage: integer
128 e4ccf6cd Guido Trotter
    @keyword coverage: number of remote nodes to contact
129 e4ccf6cd Guido Trotter

130 e4ccf6cd Guido Trotter
    """
131 e4ccf6cd Guido Trotter
    if coverage is None:
132 e4ccf6cd Guido Trotter
      coverage = min(len(self._peers), constants.CONFD_DEFAULT_REQ_COVERAGE)
133 e4ccf6cd Guido Trotter
134 e4ccf6cd Guido Trotter
    if not callable(callback):
135 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("callback must be callable")
136 e4ccf6cd Guido Trotter
137 e4ccf6cd Guido Trotter
    if coverage > len(self._peers):
138 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Not enough MCs known to provide the"
139 e4ccf6cd Guido Trotter
                                    " desired coverage")
140 e4ccf6cd Guido Trotter
141 e4ccf6cd Guido Trotter
    if not request.rsalt:
142 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Missing request rsalt")
143 e4ccf6cd Guido Trotter
144 e4ccf6cd Guido Trotter
    self._ExpireCallbacks()
145 e4ccf6cd Guido Trotter
    if request.rsalt in self._callbacks:
146 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Duplicate request rsalt")
147 e4ccf6cd Guido Trotter
148 e4ccf6cd Guido Trotter
    if request.type not in constants.CONFD_REQS:
149 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")
150 e4ccf6cd Guido Trotter
151 e4ccf6cd Guido Trotter
    random.shuffle(self._peers)
152 e4ccf6cd Guido Trotter
    targets = self._peers[:coverage]
153 e4ccf6cd Guido Trotter
154 e4ccf6cd Guido Trotter
    now = time.time()
155 e4ccf6cd Guido Trotter
    payload = self._PackRequest(request, now=now)
156 e4ccf6cd Guido Trotter
157 e4ccf6cd Guido Trotter
    for target in targets:
158 e4ccf6cd Guido Trotter
      try:
159 e4ccf6cd Guido Trotter
        self._socket.enqueue_send(target, self._confd_port, payload)
160 e4ccf6cd Guido Trotter
      except errors.UdpDataSizeError:
161 e4ccf6cd Guido Trotter
        raise errors.ConfdClientError("Request too big")
162 e4ccf6cd Guido Trotter
163 e4ccf6cd Guido Trotter
    self._callbacks[request.rsalt] = (callback, request.type,
164 e4ccf6cd Guido Trotter
                                      request.query, args)
165 e4ccf6cd Guido Trotter
    expire_time = now + constants.CONFD_CLIENT_EXPIRE_TIMEOUT
166 e4ccf6cd Guido Trotter
    self._expire_callbacks.append((expire_time, request.rsalt))
167 e4ccf6cd Guido Trotter
168 e4ccf6cd Guido Trotter
  def HandleResponse(self, payload, ip, port):
169 e4ccf6cd Guido Trotter
    """Asynchronous handler for a confd reply
170 e4ccf6cd Guido Trotter

171 e4ccf6cd Guido Trotter
    Call the relevant callback associated to the current request.
172 e4ccf6cd Guido Trotter

173 e4ccf6cd Guido Trotter
    """
174 e4ccf6cd Guido Trotter
    try:
175 e4ccf6cd Guido Trotter
      try:
176 e4ccf6cd Guido Trotter
        answer, salt = self._UnpackReply(payload)
177 e4ccf6cd Guido Trotter
      except (errors.SignatureError, errors.ConfdMagicError):
178 e4ccf6cd Guido Trotter
        return
179 e4ccf6cd Guido Trotter
180 e4ccf6cd Guido Trotter
      try:
181 e4ccf6cd Guido Trotter
        (callback, type, query, args) = self._callbacks[salt]
182 e4ccf6cd Guido Trotter
      except KeyError:
183 e4ccf6cd Guido Trotter
        # If the salt is unkown the answer is probably a replay of an old
184 e4ccf6cd Guido Trotter
        # expired query. Ignoring it.
185 e4ccf6cd Guido Trotter
        pass
186 e4ccf6cd Guido Trotter
      else:
187 e4ccf6cd Guido Trotter
        callback(answer, type, query, salt, ip, port, args)
188 e4ccf6cd Guido Trotter
189 e4ccf6cd Guido Trotter
    finally:
190 e4ccf6cd Guido Trotter
      self._ExpireCallbacks()
191 e4ccf6cd Guido Trotter
192 e4ccf6cd Guido Trotter
193 e4ccf6cd Guido Trotter
class ConfdClientRequest(objects.ConfdRequest):
194 e4ccf6cd Guido Trotter
  """This is the client-side version of ConfdRequest.
195 e4ccf6cd Guido Trotter

196 e4ccf6cd Guido Trotter
  This version of the class helps creating requests, on the client side, by
197 e4ccf6cd Guido Trotter
  filling in some default values.
198 e4ccf6cd Guido Trotter

199 e4ccf6cd Guido Trotter
  """
200 e4ccf6cd Guido Trotter
  def __init__(self, **kwargs):
201 e4ccf6cd Guido Trotter
    objects.ConfdRequest.__init__(self, **kwargs)
202 e4ccf6cd Guido Trotter
    if not self.rsalt:
203 e4ccf6cd Guido Trotter
      self.rsalt = utils.NewUUID()
204 e4ccf6cd Guido Trotter
    if not self.protocol:
205 e4ccf6cd Guido Trotter
      self.protocol = constants.CONFD_PROTOCOL_VERSION
206 e4ccf6cd Guido Trotter
    if self.type not in constants.CONFD_REQS:
207 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")