Statistics
| Branch: | Tag: | Revision:

root / tools / confd-client @ 18397489

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()