root / tools / confd-client @ 2cfbc784
History | View | Annotate | Download (8.5 kB)
1 |
#!/usr/bin/python |
---|---|
2 |
# |
3 |
|
4 |
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 |
# pylint: disable=C0103 |
22 |
|
23 |
"""confd client program |
24 |
|
25 |
This is can be used to test and debug confd daemon functionality. |
26 |
|
27 |
""" |
28 |
|
29 |
import sys |
30 |
import optparse |
31 |
import time |
32 |
|
33 |
from ganeti import constants |
34 |
from ganeti import cli |
35 |
from ganeti import utils |
36 |
|
37 |
from ganeti.confd import client as confd_client |
38 |
|
39 |
USAGE = ("\tconfd-client [--addr=host] [--hmac=key]") |
40 |
|
41 |
LOG_HEADERS = { |
42 |
0: "- ", |
43 |
1: "* ", |
44 |
2: "" |
45 |
} |
46 |
|
47 |
OPTIONS = [ |
48 |
cli.cli_option("--hmac", dest="hmac", default=None, |
49 |
help="Specify HMAC key instead of reading" |
50 |
" it from the filesystem", |
51 |
metavar="<KEY>"), |
52 |
cli.cli_option("-a", "--address", dest="mc", default="localhost", |
53 |
help="Server IP to query (default: 127.0.0.1)", |
54 |
metavar="<ADDRESS>"), |
55 |
cli.cli_option("-r", "--requests", dest="requests", default=100, |
56 |
help="Number of requests for the timing tests", |
57 |
type="int", metavar="<REQUESTS>"), |
58 |
] |
59 |
|
60 |
|
61 |
def Log(msg, *args, **kwargs): |
62 |
"""Simple function that prints out its argument. |
63 |
|
64 |
""" |
65 |
if args: |
66 |
msg = msg % args |
67 |
indent = kwargs.get("indent", 0) |
68 |
sys.stdout.write("%*s%s%s\n" % (2 * indent, "", |
69 |
LOG_HEADERS.get(indent, " "), msg)) |
70 |
sys.stdout.flush() |
71 |
|
72 |
|
73 |
def LogAtMost(msgs, count, **kwargs): |
74 |
"""Log at most count of given messages. |
75 |
|
76 |
""" |
77 |
for m in msgs[:count]: |
78 |
Log(m, **kwargs) |
79 |
if len(msgs) > count: |
80 |
Log("...", **kwargs) |
81 |
|
82 |
|
83 |
def Err(msg, exit_code=1): |
84 |
"""Simple error logging that prints to stderr. |
85 |
|
86 |
""" |
87 |
sys.stderr.write(msg + "\n") |
88 |
sys.stderr.flush() |
89 |
sys.exit(exit_code) |
90 |
|
91 |
|
92 |
def Usage(): |
93 |
"""Shows program usage information and exits the program.""" |
94 |
|
95 |
print >> sys.stderr, "Usage:" |
96 |
print >> sys.stderr, USAGE |
97 |
sys.exit(2) |
98 |
|
99 |
|
100 |
class TestClient(object): |
101 |
"""Confd test client.""" |
102 |
|
103 |
def __init__(self): |
104 |
"""Constructor.""" |
105 |
self.opts = None |
106 |
self.cluster_master = None |
107 |
self.instance_ips = None |
108 |
self.is_timing = False |
109 |
self.ParseOptions() |
110 |
|
111 |
def ParseOptions(self): |
112 |
"""Parses the command line options. |
113 |
|
114 |
In case of command line errors, it will show the usage and exit the |
115 |
program. |
116 |
|
117 |
""" |
118 |
parser = optparse.OptionParser(usage="\n%s" % USAGE, |
119 |
version=("%%prog (ganeti) %s" % |
120 |
constants.RELEASE_VERSION), |
121 |
option_list=OPTIONS) |
122 |
|
123 |
options, args = parser.parse_args() |
124 |
if args: |
125 |
Usage() |
126 |
|
127 |
if options.hmac is None: |
128 |
options.hmac = utils.ReadFile(constants.CONFD_HMAC_KEY) |
129 |
self.hmac_key = options.hmac |
130 |
|
131 |
self.mc_list = [options.mc] |
132 |
|
133 |
self.opts = options |
134 |
|
135 |
def ConfdCallback(self, reply): |
136 |
"""Callback for confd queries""" |
137 |
if reply.type == confd_client.UPCALL_REPLY: |
138 |
answer = reply.server_reply.answer |
139 |
reqtype = reply.orig_request.type |
140 |
if reply.server_reply.status != constants.CONFD_REPL_STATUS_OK: |
141 |
Log("Query %s gave non-ok status %s: %s" % (reply.orig_request, |
142 |
reply.server_reply.status, |
143 |
reply.server_reply)) |
144 |
if self.is_timing: |
145 |
Err("Aborting timing tests") |
146 |
if reqtype == constants.CONFD_REQ_CLUSTER_MASTER: |
147 |
Err("Cannot continue after master query failure") |
148 |
if reqtype == constants.CONFD_REQ_INSTANCES_IPS_LIST: |
149 |
Err("Cannot continue after instance IP list query failure") |
150 |
return |
151 |
if self.is_timing: |
152 |
return |
153 |
if reqtype == constants.CONFD_REQ_PING: |
154 |
Log("Ping: OK") |
155 |
elif reqtype == constants.CONFD_REQ_CLUSTER_MASTER: |
156 |
Log("Master: OK (%s)", answer) |
157 |
if self.cluster_master is None: |
158 |
# only assign the first time, in the plain query |
159 |
self.cluster_master = answer |
160 |
elif reqtype == constants.CONFD_REQ_NODE_ROLE_BYNAME: |
161 |
if answer == constants.CONFD_NODE_ROLE_MASTER: |
162 |
Log("Node role for master: OK",) |
163 |
else: |
164 |
Err("Node role for master: wrong: %s" % answer) |
165 |
elif reqtype == constants.CONFD_REQ_NODE_PIP_LIST: |
166 |
Log("Node primary ip query: OK") |
167 |
LogAtMost(answer, 5, indent=1) |
168 |
elif reqtype == constants.CONFD_REQ_MC_PIP_LIST: |
169 |
Log("Master candidates primary IP query: OK") |
170 |
LogAtMost(answer, 5, indent=1) |
171 |
elif reqtype == constants.CONFD_REQ_INSTANCES_IPS_LIST: |
172 |
Log("Instance primary IP query: OK") |
173 |
if not answer: |
174 |
Log("no IPs received", indent=1) |
175 |
else: |
176 |
LogAtMost(answer, 5, indent=1) |
177 |
self.instance_ips = answer |
178 |
elif reqtype == constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP: |
179 |
Log("Instance IP to node IP query: OK") |
180 |
if not answer: |
181 |
Log("no mapping received", indent=1) |
182 |
else: |
183 |
LogAtMost(answer, 5, indent=1) |
184 |
else: |
185 |
Log("Unhandled reply %s, please fix the client", reqtype) |
186 |
print answer |
187 |
|
188 |
def DoConfdRequestReply(self, req): |
189 |
self.confd_counting_callback.RegisterQuery(req.rsalt) |
190 |
self.confd_client.SendRequest(req, async=False) |
191 |
while not self.confd_counting_callback.AllAnswered(): |
192 |
if not self.confd_client.ReceiveReply(): |
193 |
Err("Did not receive all expected confd replies") |
194 |
break |
195 |
|
196 |
def TestConfd(self): |
197 |
"""Run confd queries for the cluster. |
198 |
|
199 |
""" |
200 |
Log("Checking confd results") |
201 |
|
202 |
filter_callback = confd_client.ConfdFilterCallback(self.ConfdCallback) |
203 |
counting_callback = confd_client.ConfdCountingCallback(filter_callback) |
204 |
self.confd_counting_callback = counting_callback |
205 |
|
206 |
self.confd_client = confd_client.ConfdClient(self.hmac_key, |
207 |
self.mc_list, |
208 |
counting_callback) |
209 |
|
210 |
tests = [ |
211 |
{"type": constants.CONFD_REQ_PING}, |
212 |
{"type": constants.CONFD_REQ_CLUSTER_MASTER}, |
213 |
{"type": constants.CONFD_REQ_CLUSTER_MASTER, |
214 |
"query": {constants.CONFD_REQQ_FIELDS: |
215 |
[constants.CONFD_REQFIELD_NAME, |
216 |
constants.CONFD_REQFIELD_IP, |
217 |
constants.CONFD_REQFIELD_MNODE_PIP, |
218 |
]}}, |
219 |
{"type": constants.CONFD_REQ_NODE_ROLE_BYNAME}, |
220 |
{"type": constants.CONFD_REQ_NODE_PIP_LIST}, |
221 |
{"type": constants.CONFD_REQ_MC_PIP_LIST}, |
222 |
{"type": constants.CONFD_REQ_INSTANCES_IPS_LIST, |
223 |
"query": None}, |
224 |
{"type": constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP}, |
225 |
] |
226 |
|
227 |
for kwargs in tests: |
228 |
if kwargs["type"] == constants.CONFD_REQ_NODE_ROLE_BYNAME: |
229 |
assert self.cluster_master is not None |
230 |
kwargs["query"] = self.cluster_master |
231 |
elif kwargs["type"] == constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP: |
232 |
kwargs["query"] = {constants.CONFD_REQQ_IPLIST: self.instance_ips} |
233 |
|
234 |
# pylint: disable=W0142 |
235 |
# used ** magic |
236 |
req = confd_client.ConfdClientRequest(**kwargs) |
237 |
self.DoConfdRequestReply(req) |
238 |
|
239 |
def TestTiming(self): |
240 |
"""Run timing tests. |
241 |
|
242 |
""" |
243 |
# timing tests |
244 |
if self.opts.requests <= 0: |
245 |
return |
246 |
Log("Timing tests") |
247 |
self.is_timing = True |
248 |
self.TimingOp("ping", {"type": constants.CONFD_REQ_PING}) |
249 |
self.TimingOp("instance ips", |
250 |
{"type": constants.CONFD_REQ_INSTANCES_IPS_LIST}) |
251 |
|
252 |
def TimingOp(self, name, kwargs): |
253 |
"""Run a single timing test. |
254 |
|
255 |
""" |
256 |
start = time.time() |
257 |
for _ in range(self.opts.requests): |
258 |
# pylint: disable=W0142 |
259 |
req = confd_client.ConfdClientRequest(**kwargs) |
260 |
self.DoConfdRequestReply(req) |
261 |
stop = time.time() |
262 |
per_req = 1000 * (stop - start) / self.opts.requests |
263 |
Log("%.3fms per %s request", per_req, name, indent=1) |
264 |
|
265 |
def Run(self): |
266 |
"""Run all the tests. |
267 |
|
268 |
""" |
269 |
self.TestConfd() |
270 |
self.TestTiming() |
271 |
|
272 |
|
273 |
def main(): |
274 |
"""Main function. |
275 |
|
276 |
""" |
277 |
return TestClient().Run() |
278 |
|
279 |
|
280 |
if __name__ == "__main__": |
281 |
main() |