Statistics
| Branch: | Tag: | Revision:

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