Revision ed54b47e

b/Makefile.am
30 30
	lib/rapi \
31 31
	man \
32 32
	qa \
33
	qa/hooks \
34 33
	scripts \
35 34
	test \
36 35
	test/data \
......
51 50
	man/*.[78] \
52 51
	man/*.in \
53 52
	qa/*.py[co] \
54
	qa/hooks/*.py[co] \
55 53
	test/*.py[co] \
56 54
	stamp-directories \
57 55
	$(nodist_pkgpython_PYTHON)
......
141 139
	doc/examples/ganeti.initd.in \
142 140
	doc/examples/ganeti.cron.in \
143 141
	doc/examples/dumb-allocator \
144
	qa/hooks/datehook.py \
145
	qa/hooks/loghook.py \
146 142
	test/testutils.py \
147 143
	test/mocks.py \
148 144
	$(dist_TESTS) \
b/qa/ganeti-qa.py
263 263
    sys.exit(1)
264 264

  
265 265
  qa_config.Load(config_file)
266
  qa_utils.LoadHooks()
267 266

  
268 267
  RunTest(qa_other.UploadKnownHostsFile, known_hosts_file)
269 268

  
/dev/null
1
# Copyright (C) 2007 Google Inc.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
# General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16
# 02110-1301, USA.
17

  
18

  
19
"""Example QA hook.
20

  
21
"""
22

  
23
from ganeti import utils
24

  
25
import qa_utils
26
import qa_config
27

  
28
from qa_utils import AssertEqual, StartSSH
29

  
30

  
31
class DateHook:
32
  def run(self, ctx):
33
    if ctx.name == 'cluster-init' and ctx.phase == 'pre':
34
      self._CallDate(ctx)
35

  
36
  def _CallDate(self, ctx):
37
    for node in qa_config.get('nodes'):
38
      cmd = ['date']
39
      AssertEqual(StartSSH(node['primary'],
40
                           utils.ShellQuoteArgs(cmd)).wait(), 0)
41

  
42
hook = DateHook
/dev/null
1
# Copyright (C) 2007 Google Inc.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
# General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16
# 02110-1301, USA.
17

  
18

  
19
"""QA hook to log all function calls.
20

  
21
"""
22

  
23
from ganeti import utils
24

  
25
import qa_utils
26
import qa_config
27

  
28
from qa_utils import AssertEqual, StartSSH
29

  
30

  
31
class LogHook:
32
  def __init__(self):
33
    file_name = qa_config.get('options', {}).get('hook-logfile', None)
34
    if file_name:
35
      self.log = open(file_name, "a+")
36
    else:
37
      self.log = None
38

  
39
  def __del__(self):
40
    if self.log:
41
      self.log.close()
42

  
43
  def run(self, ctx):
44
    if not self.log:
45
      return
46

  
47
    msg = "%s-%s" % (ctx.phase, ctx.name)
48
    if ctx.phase == 'post':
49
      msg += " success=%s" % ctx.success
50
    if ctx.args:
51
      msg += " %s" % repr(ctx.args)
52
    if ctx.kwargs:
53
      msg += " %s" % repr(ctx.kwargs)
54
    if ctx.phase == 'pre':
55
      self.log.write("---\n")
56
    self.log.write(msg)
57
    self.log.write("\n")
58
    self.log.flush()
59

  
60

  
61
hook = LogHook
b/qa/qa-sample.yaml
83 83
options:
84 84
  burnin-instances: 2
85 85
  burnin-disk-template: drbd
86

  
87
  # Directory containing QA hooks
88
  #hooks-dir: hooks/
89

  
90
  # Logfile for loghook.py
91
  hook-logfile: /tmp/qa.log
b/qa/qa_cluster.py
54 54
                content)
55 55

  
56 56

  
57
@qa_utils.DefineHook('cluster-init')
58 57
def TestClusterInit():
59 58
  """gnt-cluster init"""
60 59
  master = qa_config.GetMasterNode()
......
79 78
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
80 79

  
81 80

  
82
@qa_utils.DefineHook('cluster-rename')
83 81
def TestClusterRename():
84 82
  """gnt-cluster rename"""
85 83
  master = qa_config.GetMasterNode()
......
110 108
                       utils.ShellQuoteArgs(cmd_verify)).wait(), 0)
111 109

  
112 110

  
113
@qa_utils.DefineHook('cluster-verify')
114 111
def TestClusterVerify():
115 112
  """gnt-cluster verify"""
116 113
  master = qa_config.GetMasterNode()
......
120 117
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
121 118

  
122 119

  
123
@qa_utils.DefineHook('cluster-info')
124 120
def TestClusterInfo():
125 121
  """gnt-cluster info"""
126 122
  master = qa_config.GetMasterNode()
......
130 126
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
131 127

  
132 128

  
133
@qa_utils.DefineHook('cluster-getmaster')
134 129
def TestClusterGetmaster():
135 130
  """gnt-cluster getmaster"""
136 131
  master = qa_config.GetMasterNode()
......
140 135
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
141 136

  
142 137

  
143
@qa_utils.DefineHook('cluster-version')
144 138
def TestClusterVersion():
145 139
  """gnt-cluster version"""
146 140
  master = qa_config.GetMasterNode()
......
150 144
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
151 145

  
152 146

  
153
@qa_utils.DefineHook('cluster-burnin')
154 147
def TestClusterBurnin():
155 148
  """Burnin"""
156 149
  master = qa_config.GetMasterNode()
......
191 184
      qa_config.ReleaseInstance(inst)
192 185

  
193 186

  
194
@qa_utils.DefineHook('cluster-master-failover')
195 187
def TestClusterMasterFailover():
196 188
  """gnt-cluster masterfailover"""
197 189
  master = qa_config.GetMasterNode()
......
209 201
    qa_config.ReleaseNode(failovermaster)
210 202

  
211 203

  
212
@qa_utils.DefineHook('cluster-copyfile')
213 204
def TestClusterCopyfile():
214 205
  """gnt-cluster copyfile"""
215 206
  master = qa_config.GetMasterNode()
......
234 225
    _RemoveFileFromAllNodes(testname)
235 226

  
236 227

  
237
@qa_utils.DefineHook('cluster-command')
238 228
def TestClusterCommand():
239 229
  """gnt-cluster command"""
240 230
  master = qa_config.GetMasterNode()
......
252 242
    _RemoveFileFromAllNodes(rfile)
253 243

  
254 244

  
255
@qa_utils.DefineHook('cluster-destroy')
256 245
def TestClusterDestroy():
257 246
  """gnt-cluster destroy"""
258 247
  master = qa_config.GetMasterNode()
b/qa/qa_daemon.py
104 104
  print qa_utils.FormatWarning(msg)
105 105

  
106 106

  
107
@qa_utils.DefineHook('daemon-automatic-restart')
108 107
def TestInstanceAutomaticRestart(node, instance):
109 108
  """Test automatic restart of instance by ganeti-watcher.
110 109

  
......
126 125
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
127 126

  
128 127

  
129
@qa_utils.DefineHook('daemon-consecutive-failures')
130 128
def TestInstanceConsecutiveFailures(node, instance):
131 129
  """Test five consecutive instance failures.
132 130

  
b/qa/qa_env.py
31 31
from qa_utils import AssertEqual, StartSSH
32 32

  
33 33

  
34
@qa_utils.DefineHook('env-ssh-connection')
35 34
def TestSshConnection():
36 35
  """Test SSH connection.
37 36

  
......
40 39
    AssertEqual(StartSSH(node['primary'], 'exit').wait(), 0)
41 40

  
42 41

  
43
@qa_utils.DefineHook('env-ganeti-commands')
44 42
def TestGanetiCommands():
45 43
  """Test availibility of Ganeti commands.
46 44

  
......
59 57
    AssertEqual(StartSSH(node['primary'], cmd).wait(), 0)
60 58

  
61 59

  
62
@qa_utils.DefineHook('env-icmp-ping')
63 60
def TestIcmpPing():
64 61
  """ICMP ping each node.
65 62

  
b/qa/qa_instance.py
66 66
    raise
67 67

  
68 68

  
69
@qa_utils.DefineHook('instance-add-plain-disk')
70 69
def TestInstanceAddWithPlainDisk(node):
71 70
  """gnt-instance add -t plain"""
72 71
  return _DiskTest(node['primary'], 'plain')
73 72

  
74 73

  
75
@qa_utils.DefineHook('instance-add-drbd-disk')
76 74
def TestInstanceAddWithDrbdDisk(node, node2):
77 75
  """gnt-instance add -t drbd"""
78 76
  return _DiskTest("%s:%s" % (node['primary'], node2['primary']),
79 77
                   'drbd')
80 78

  
81 79

  
82
@qa_utils.DefineHook('instance-remove')
83 80
def TestInstanceRemove(instance):
84 81
  """gnt-instance remove"""
85 82
  master = qa_config.GetMasterNode()
......
91 88
  qa_config.ReleaseInstance(instance)
92 89

  
93 90

  
94
@qa_utils.DefineHook('instance-startup')
95 91
def TestInstanceStartup(instance):
96 92
  """gnt-instance startup"""
97 93
  master = qa_config.GetMasterNode()
......
101 97
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
102 98

  
103 99

  
104
@qa_utils.DefineHook('instance-shutdown')
105 100
def TestInstanceShutdown(instance):
106 101
  """gnt-instance shutdown"""
107 102
  master = qa_config.GetMasterNode()
......
111 106
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
112 107

  
113 108

  
114
@qa_utils.DefineHook('instance-reboot')
115 109
def TestInstanceReboot(instance):
116 110
  """gnt-instance reboot"""
117 111
  master = qa_config.GetMasterNode()
......
123 117
                         utils.ShellQuoteArgs(cmd)).wait(), 0)
124 118

  
125 119

  
126
@qa_utils.DefineHook('instance-reinstall')
127 120
def TestInstanceReinstall(instance):
128 121
  """gnt-instance reinstall"""
129 122
  master = qa_config.GetMasterNode()
......
133 126
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
134 127

  
135 128

  
136
@qa_utils.DefineHook('instance-failover')
137 129
def TestInstanceFailover(instance):
138 130
  """gnt-instance failover"""
139 131
  master = qa_config.GetMasterNode()
......
148 140
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
149 141

  
150 142

  
151
@qa_utils.DefineHook('instance-info')
152 143
def TestInstanceInfo(instance):
153 144
  """gnt-instance info"""
154 145
  master = qa_config.GetMasterNode()
......
158 149
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
159 150

  
160 151

  
161
@qa_utils.DefineHook('instance-modify')
162 152
def TestInstanceModify(instance):
163 153
  """gnt-instance modify"""
164 154
  master = qa_config.GetMasterNode()
......
191 181
                          utils.ShellQuoteArgs(cmd)).wait(), 0)
192 182

  
193 183

  
194
@qa_utils.DefineHook('instance-list')
195 184
def TestInstanceList():
196 185
  """gnt-instance list"""
197 186
  master = qa_config.GetMasterNode()
......
201 190
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
202 191

  
203 192

  
204
@qa_utils.DefineHook('instance-console')
205 193
def TestInstanceConsole(instance):
206 194
  """gnt-instance console"""
207 195
  master = qa_config.GetMasterNode()
......
211 199
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
212 200

  
213 201

  
214
@qa_utils.DefineHook('instance-replace-disks')
215 202
def TestReplaceDisks(instance, pnode, snode, othernode):
216 203
  """gnt-instance replace-disks"""
217 204
  master = qa_config.GetMasterNode()
......
240 227
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
241 228

  
242 229

  
243
@qa_utils.DefineHook('backup-export')
244 230
def TestInstanceExport(instance, node):
245 231
  """gnt-backup export"""
246 232
  master = qa_config.GetMasterNode()
......
252 238
  return qa_utils.ResolveInstanceName(instance)
253 239

  
254 240

  
255
@qa_utils.DefineHook('backup-import')
256 241
def TestInstanceImport(node, newinst, expnode, name):
257 242
  """gnt-backup import"""
258 243
  master = qa_config.GetMasterNode()
......
269 254
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
270 255

  
271 256

  
272
@qa_utils.DefineHook('backup-list')
273 257
def TestBackupList(expnode):
274 258
  """gnt-backup list"""
275 259
  master = qa_config.GetMasterNode()
b/qa/qa_node.py
28 28
from qa_utils import AssertEqual, StartSSH
29 29

  
30 30

  
31
@qa_utils.DefineHook('node-add')
32 31
def _NodeAdd(node, readd=False):
33 32
  master = qa_config.GetMasterNode()
34 33

  
......
49 48
  node['_added'] = True
50 49

  
51 50

  
52
@qa_utils.DefineHook('node-remove')
53 51
def _NodeRemove(node):
54 52
  master = qa_config.GetMasterNode()
55 53

  
......
75 73
      _NodeRemove(node)
76 74

  
77 75

  
78
@qa_utils.DefineHook('node-readd')
79 76
def TestNodeReadd(node):
80 77
  """gnt-node add --readd"""
81 78
  _NodeAdd(node, readd=True)
82 79

  
83 80

  
84
@qa_utils.DefineHook('node-info')
85 81
def TestNodeInfo():
86 82
  """gnt-node info"""
87 83
  master = qa_config.GetMasterNode()
......
91 87
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
92 88

  
93 89

  
94
@qa_utils.DefineHook('node-volumes')
95 90
def TestNodeVolumes():
96 91
  """gnt-node volumes"""
97 92
  master = qa_config.GetMasterNode()
......
101 96
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
102 97

  
103 98

  
104
@qa_utils.DefineHook('node-failover')
105 99
def TestNodeFailover(node, node2):
106 100
  """gnt-node failover"""
107 101
  master = qa_config.GetMasterNode()
......
122 116
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
123 117

  
124 118

  
125
@qa_utils.DefineHook('node-evacuate')
126 119
def TestNodeEvacuate(node, node2):
127 120
  """gnt-node evacuate"""
128 121
  master = qa_config.GetMasterNode()
b/qa/qa_os.py
39 39
_TEMP_OS_PATH = os.path.join(constants.OS_SEARCH_PATH[0], _TEMP_OS_NAME)
40 40

  
41 41

  
42
@qa_utils.DefineHook('os-list')
43 42
def TestOsList():
44 43
  """gnt-os list"""
45 44
  master = qa_config.GetMasterNode()
......
49 48
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
50 49

  
51 50

  
52
@qa_utils.DefineHook('os-diagnose')
53 51
def TestOsDiagnose():
54 52
  """gnt-os diagnose"""
55 53
  master = qa_config.GetMasterNode()
......
128 126
      _RemoveTempOs(node, dir)
129 127

  
130 128

  
131
@qa_utils.DefineHook('os-valid')
132 129
def TestOsValid():
133 130
  """Testing valid OS definition"""
134 131
  return _TestOs(1)
135 132

  
136 133

  
137
@qa_utils.DefineHook('os-invalid')
138 134
def TestOsInvalid():
139 135
  """Testing invalid OS definition"""
140 136
  return _TestOs(0)
141 137

  
142 138

  
143
@qa_utils.DefineHook('os-partially-valid')
144 139
def TestOsPartiallyValid():
145 140
  """Testing partially valid OS definition"""
146 141
  return _TestOs(2)
b/qa/qa_rapi.py
97 97
        AssertEqual(data, verify)
98 98

  
99 99

  
100
@qa_utils.DefineHook('rapi-version')
101 100
def TestVersion():
102 101
  """Testing remote API version.
103 102

  
......
107 106
    ])
108 107

  
109 108

  
110
@qa_utils.DefineHook('rapi-empty-cluster')
111 109
def TestEmptyCluster():
112 110
  """Testing remote API on an empty cluster.
113 111

  
......
143 141
    ])
144 142

  
145 143

  
146
@qa_utils.DefineHook('rapi-instance')
147 144
def TestInstance(instance):
148 145
  """Testing getting instance(s) info via remote API.
149 146

  
......
168 165
    ])
169 166

  
170 167

  
171
@qa_utils.DefineHook('rapi-node')
172 168
def TestNode(node):
173 169
  """Testing getting node(s) info via remote API.
174 170

  
b/qa/qa_tags.py
74 74
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
75 75

  
76 76

  
77
@qa_utils.DefineHook('tags-cluster')
78 77
def TestClusterTags():
79 78
  """gnt-cluster tags"""
80 79
  _TestTags(constants.TAG_CLUSTER, "")
81 80

  
82 81

  
83
@qa_utils.DefineHook('tags-node')
84 82
def TestNodeTags(node):
85 83
  """gnt-node tags"""
86 84
  _TestTags(constants.TAG_NODE, node["primary"])
87 85

  
88 86

  
89
@qa_utils.DefineHook('tags-instance')
90 87
def TestInstanceTags(instance):
91 88
  """gnt-instance tags"""
92 89
  _TestTags(constants.TAG_INSTANCE, instance["name"])
b/qa/qa_utils.py
39 39
_RESET_SEQ = None
40 40

  
41 41

  
42
# List of all hooks
43
_hooks = []
44

  
45

  
46 42
def _SetupColours():
47 43
  """Initializes the colour constants.
48 44

  
......
225 221
FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
226 222
FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
227 223
FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
228

  
229

  
230
def LoadHooks():
231
  """Load all QA hooks.
232

  
233
  """
234
  hooks_dir = qa_config.get('options', {}).get('hooks-dir', None)
235
  if not hooks_dir:
236
    return
237
  if hooks_dir not in sys.path:
238
    sys.path.insert(0, hooks_dir)
239
  for name in utils.ListVisibleFiles(hooks_dir):
240
    if name.endswith('.py'):
241
      # Load and instanciate hook
242
      print "Loading hook %s" % name
243
      _hooks.append(__import__(name[:-3], None, None, ['']).hook())
244

  
245

  
246
class QaHookContext:
247
  """Definition of context passed to hooks.
248

  
249
  """
250
  name = None
251
  phase = None
252
  success = None
253
  args = None
254
  kwargs = None
255

  
256

  
257
def _CallHooks(ctx):
258
  """Calls all hooks with the given context.
259

  
260
  """
261
  if not _hooks:
262
    return
263

  
264
  name = "%s-%s" % (ctx.phase, ctx.name)
265
  if ctx.success is not None:
266
    msg = "%s (success=%s)" % (name, ctx.success)
267
  else:
268
    msg = name
269
  print FormatInfo("Begin %s" % msg)
270
  for hook in _hooks:
271
    hook.run(ctx)
272
  print FormatInfo("End %s" % name)
273

  
274

  
275
def DefineHook(name):
276
  """Wraps a function with calls to hooks.
277

  
278
  Usage: prefix function with @qa_utils.DefineHook(...)
279

  
280
  This is based on PEP 318, "Decorators for Functions and Methods".
281

  
282
  """
283
  def wrapper(fn):
284
    def new_f(*args, **kwargs):
285
      # Create context
286
      ctx = QaHookContext()
287
      ctx.name = name
288
      ctx.phase = 'pre'
289
      ctx.args = args
290
      ctx.kwargs = kwargs
291

  
292
      _CallHooks(ctx)
293
      try:
294
        ctx.phase = 'post'
295
        ctx.success = True
296
        try:
297
          # Call real function
298
          return fn(*args, **kwargs)
299
        except:
300
          ctx.success = False
301
          raise
302
      finally:
303
        _CallHooks(ctx)
304

  
305
    # Override function metadata
306
    new_f.func_name = fn.func_name
307
    new_f.func_doc = fn.func_doc
308

  
309
    return new_f
310

  
311
  return wrapper

Also available in: Unified diff