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") |