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
37 from ganeti.confd import client as confd_client
39 USAGE = ("\tconfd-client [--addr=host] [--hmac=key]")
48 cli.cli_option("--hmac", dest="hmac", default=None,
49 help="Specify HMAC key instead of reading"
50 " it from the filesystem",
52 cli.cli_option("-a", "--address", dest="mc", default="localhost",
53 help="Server IP to query (default: 127.0.0.1)",
55 cli.cli_option("-r", "--requests", dest="requests", default=100,
56 help="Number of requests for the timing tests",
57 type="int", metavar="<REQUESTS>"),
61 def Log(msg, *args, **kwargs):
62 """Simple function that prints out its argument.
67 indent = kwargs.get("indent", 0)
68 sys.stdout.write("%*s%s%s\n" % (2 * indent, "",
69 LOG_HEADERS.get(indent, " "), msg))
73 def LogAtMost(msgs, count, **kwargs):
74 """Log at most count of given messages.
77 for m in msgs[:count]:
83 def Err(msg, exit_code=1):
84 """Simple error logging that prints to stderr.
87 sys.stderr.write(msg + "\n")
93 """Shows program usage information and exits the program."""
95 print >> sys.stderr, "Usage:"
96 print >> sys.stderr, USAGE
100 class TestClient(object):
101 """Confd test client."""
106 self.cluster_master = None
107 self.instance_ips = None
108 self.is_timing = False
111 def ParseOptions(self):
112 """Parses the command line options.
114 In case of command line errors, it will show the usage and exit the
118 parser = optparse.OptionParser(usage="\n%s" % USAGE,
119 version=("%%prog (ganeti) %s" %
120 constants.RELEASE_VERSION),
123 options, args = parser.parse_args()
127 if options.hmac is None:
128 options.hmac = utils.ReadFile(constants.CONFD_HMAC_KEY)
129 self.hmac_key = options.hmac
131 self.mc_list = [options.mc]
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,
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")
153 if reqtype == constants.CONFD_REQ_PING:
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",)
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")
174 Log("no IPs received", indent=1)
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")
181 Log("no mapping received", indent=1)
183 LogAtMost(answer, 5, indent=1)
185 Log("Unhandled reply %s, please fix the client", reqtype)
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")
197 """Run confd queries for the cluster.
200 Log("Checking confd results")
202 filter_callback = confd_client.ConfdFilterCallback(self.ConfdCallback)
203 counting_callback = confd_client.ConfdCountingCallback(filter_callback)
204 self.confd_counting_callback = counting_callback
206 self.confd_client = confd_client.ConfdClient(self.hmac_key,
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,
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,
224 {"type": constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP},
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}
234 # pylint: disable=W0142
236 req = confd_client.ConfdClientRequest(**kwargs)
237 self.DoConfdRequestReply(req)
239 def TestTiming(self):
244 if self.opts.requests <= 0:
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})
252 def TimingOp(self, name, kwargs):
253 """Run a single timing test.
257 for _ in range(self.opts.requests):
258 # pylint: disable=W0142
259 req = confd_client.ConfdClientRequest(**kwargs)
260 self.DoConfdRequestReply(req)
262 per_req = 1000 * (stop - start) / self.opts.requests
263 Log("%.3fms per %s request", per_req, name, indent=1)
266 """Run all the tests.
277 return TestClient().Run()
280 if __name__ == "__main__":