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