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