4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
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.
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.
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
21 # pylint: disable=C0103
23 """confd client program
25 This is can be used to test and debug confd daemon functionality.
33 from ganeti import constants
34 from ganeti import cli
35 from ganeti import utils
36 from ganeti import pathutils
38 from ganeti.confd import client as confd_client
40 USAGE = ("\tconfd-client [--addr=host] [--hmac=key]")
49 cli.cli_option("--hmac", dest="hmac", default=None,
50 help="Specify HMAC key instead of reading"
51 " it from the filesystem",
53 cli.cli_option("-a", "--address", dest="mc", default="localhost",
54 help="Server IP to query (default: 127.0.0.1)",
56 cli.cli_option("-r", "--requests", dest="requests", default=100,
57 help="Number of requests for the timing tests",
58 type="int", metavar="<REQUESTS>"),
62 def Log(msg, *args, **kwargs):
63 """Simple function that prints out its argument.
68 indent = kwargs.get("indent", 0)
69 sys.stdout.write("%*s%s%s\n" % (2 * indent, "",
70 LOG_HEADERS.get(indent, " "), msg))
74 def LogAtMost(msgs, count, **kwargs):
75 """Log at most count of given messages.
78 for m in msgs[:count]:
84 def Err(msg, exit_code=1):
85 """Simple error logging that prints to stderr.
88 sys.stderr.write(msg + "\n")
94 """Shows program usage information and exits the program."""
96 print >> sys.stderr, "Usage:"
97 print >> sys.stderr, USAGE
101 class TestClient(object):
102 """Confd test client."""
107 self.cluster_master = None
108 self.instance_ips = None
109 self.is_timing = False
112 def ParseOptions(self):
113 """Parses the command line options.
115 In case of command line errors, it will show the usage and exit the
119 parser = optparse.OptionParser(usage="\n%s" % USAGE,
120 version=("%%prog (ganeti) %s" %
121 constants.RELEASE_VERSION),
124 options, args = parser.parse_args()
128 if options.hmac is None:
129 options.hmac = utils.ReadFile(pathutils.CONFD_HMAC_KEY)
130 self.hmac_key = options.hmac
132 self.mc_list = [options.mc]
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,
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")
154 if reqtype == constants.CONFD_REQ_PING:
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",)
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")
175 Log("no IPs received", indent=1)
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")
182 Log("no mapping received", indent=1)
184 LogAtMost(answer, 5, indent=1)
186 Log("Unhandled reply %s, please fix the client", reqtype)
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")
198 """Run confd queries for the cluster.
201 Log("Checking confd results")
203 filter_callback = confd_client.ConfdFilterCallback(self.ConfdCallback)
204 counting_callback = confd_client.ConfdCountingCallback(filter_callback)
205 self.confd_counting_callback = counting_callback
207 self.confd_client = confd_client.ConfdClient(self.hmac_key,
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,
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,
225 {"type": constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP},
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}
235 # pylint: disable=W0142
237 req = confd_client.ConfdClientRequest(**kwargs)
238 self.DoConfdRequestReply(req)
240 def TestTiming(self):
245 if self.opts.requests <= 0:
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})
253 def TimingOp(self, name, kwargs):
254 """Run a single timing test.
258 for _ in range(self.opts.requests):
259 # pylint: disable=W0142
260 req = confd_client.ConfdClientRequest(**kwargs)
261 self.DoConfdRequestReply(req)
263 per_req = 1000 * (stop - start) / self.opts.requests
264 Log("%.3fms per %s request", per_req, name, indent=1)
267 """Run all the tests.
278 return TestClient().Run()
281 if __name__ == "__main__":