Statistics
| Branch: | Tag: | Revision:

root / tools / confd-client @ 2cfbc784

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

    
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

    
61
def Log(msg, *args, **kwargs):
62
  """Simple function that prints out its argument.
63

    
64
  """
65
  if args:
66
    msg = msg % args
67
  indent = kwargs.get("indent", 0)
68
  sys.stdout.write("%*s%s%s\n" % (2 * indent, "",
69
                                  LOG_HEADERS.get(indent, "  "), msg))
70
  sys.stdout.flush()
71

    
72

    
73
def LogAtMost(msgs, count, **kwargs):
74
  """Log at most count of given messages.
75

    
76
  """
77
  for m in msgs[:count]:
78
    Log(m, **kwargs)
79
  if len(msgs) > count:
80
    Log("...", **kwargs)
81

    
82

    
83
def Err(msg, exit_code=1):
84
  """Simple error logging that prints to stderr.
85

    
86
  """
87
  sys.stderr.write(msg + "\n")
88
  sys.stderr.flush()
89
  sys.exit(exit_code)
90

    
91

    
92
def Usage():
93
  """Shows program usage information and exits the program."""
94

    
95
  print >> sys.stderr, "Usage:"
96
  print >> sys.stderr, USAGE
97
  sys.exit(2)
98

    
99

    
100
class TestClient(object):
101
  """Confd test client."""
102

    
103
  def __init__(self):
104
    """Constructor."""
105
    self.opts = None
106
    self.cluster_master = None
107
    self.instance_ips = None
108
    self.is_timing = False
109
    self.ParseOptions()
110

    
111
  def ParseOptions(self):
112
    """Parses the command line options.
113

    
114
    In case of command line errors, it will show the usage and exit the
115
    program.
116

    
117
    """
118
    parser = optparse.OptionParser(usage="\n%s" % USAGE,
119
                                   version=("%%prog (ganeti) %s" %
120
                                            constants.RELEASE_VERSION),
121
                                   option_list=OPTIONS)
122

    
123
    options, args = parser.parse_args()
124
    if args:
125
      Usage()
126

    
127
    if options.hmac is None:
128
      options.hmac = utils.ReadFile(constants.CONFD_HMAC_KEY)
129
    self.hmac_key = options.hmac
130

    
131
    self.mc_list = [options.mc]
132

    
133
    self.opts = options
134

    
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,
143
                                                    reply.server_reply))
144
        if self.is_timing:
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")
150
        return
151
      if self.is_timing:
152
        return
153
      if reqtype == constants.CONFD_REQ_PING:
154
        Log("Ping: OK")
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",)
163
        else:
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")
173
        if not answer:
174
          Log("no IPs received", indent=1)
175
        else:
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")
180
        if not answer:
181
          Log("no mapping received", indent=1)
182
        else:
183
          LogAtMost(answer, 5, indent=1)
184
      else:
185
        Log("Unhandled reply %s, please fix the client", reqtype)
186
        print answer
187

    
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")
194
        break
195

    
196
  def TestConfd(self):
197
    """Run confd queries for the cluster.
198

    
199
    """
200
    Log("Checking confd results")
201

    
202
    filter_callback = confd_client.ConfdFilterCallback(self.ConfdCallback)
203
    counting_callback = confd_client.ConfdCountingCallback(filter_callback)
204
    self.confd_counting_callback = counting_callback
205

    
206
    self.confd_client = confd_client.ConfdClient(self.hmac_key,
207
                                                 self.mc_list,
208
                                                 counting_callback)
209

    
210
    tests = [
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,
218
                  ]}},
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,
223
       "query": None},
224
      {"type": constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP},
225
      ]
226

    
227
    for kwargs in tests:
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}
233

    
234
      # pylint: disable=W0142
235
      # used ** magic
236
      req = confd_client.ConfdClientRequest(**kwargs)
237
      self.DoConfdRequestReply(req)
238

    
239
  def TestTiming(self):
240
    """Run timing tests.
241

    
242
    """
243
    # timing tests
244
    if self.opts.requests <= 0:
245
      return
246
    Log("Timing tests")
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})
251

    
252
  def TimingOp(self, name, kwargs):
253
    """Run a single timing test.
254

    
255
    """
256
    start = time.time()
257
    for _ in range(self.opts.requests):
258
      # pylint: disable=W0142
259
      req = confd_client.ConfdClientRequest(**kwargs)
260
      self.DoConfdRequestReply(req)
261
    stop = time.time()
262
    per_req = 1000 * (stop - start) / self.opts.requests
263
    Log("%.3fms per %s request", per_req, name, indent=1)
264

    
265
  def Run(self):
266
    """Run all the tests.
267

    
268
    """
269
    self.TestConfd()
270
    self.TestTiming()
271

    
272

    
273
def main():
274
  """Main function.
275

    
276
  """
277
  return TestClient().Run()
278

    
279

    
280
if __name__ == "__main__":
281
  main()