ganeti-qa.py 14 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#!/usr/bin/python -u
Iustin Pop's avatar
Iustin Pop committed
2
3
#

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


22
"""Script for doing QA on Ganeti.
23
24

"""
Iustin Pop's avatar
Iustin Pop committed
25
26

import sys
27
28
import datetime
import optparse
Iustin Pop's avatar
Iustin Pop committed
29

30
31
32
33
import qa_cluster
import qa_config
import qa_daemon
import qa_env
34
import qa_group
35
36
import qa_instance
import qa_node
37
import qa_os
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
38
import qa_rapi
Michael Hanselmann's avatar
Michael Hanselmann committed
39
import qa_tags
40
import qa_utils
Iustin Pop's avatar
Iustin Pop committed
41

42
from ganeti import utils
43
44
45
from ganeti import rapi

import ganeti.rapi.client
46

Iustin Pop's avatar
Iustin Pop committed
47

Iustin Pop's avatar
Iustin Pop committed
48
def _FormatHeader(line, end=72):
Iustin Pop's avatar
Iustin Pop committed
49
50
51
52
53
54
55
56
57
  """Fill a line up to the end column.

  """
  line = "---- " + line + " "
  line += "-" * (end-len(line))
  line = line.rstrip()
  return line


Iustin Pop's avatar
Iustin Pop committed
58
59
def _DescriptionOf(fn):
  """Computes the description of an item.
Iustin Pop's avatar
Iustin Pop committed
60
61

  """
62
63
  if fn.__doc__:
    desc = fn.__doc__.splitlines()[0].strip()
Iustin Pop's avatar
Iustin Pop committed
64
  else:
Iustin Pop's avatar
Iustin Pop committed
65
    desc = "%r" % fn
Iustin Pop's avatar
Iustin Pop committed
66

Iustin Pop's avatar
Iustin Pop committed
67
68
69
70
71
72
  return desc.rstrip(".")

def RunTest(fn, *args):
  """Runs a test after printing a header.

  """
Iustin Pop's avatar
Iustin Pop committed
73

Iustin Pop's avatar
Iustin Pop committed
74
  tstart = datetime.datetime.now()
Iustin Pop's avatar
Iustin Pop committed
75

Iustin Pop's avatar
Iustin Pop committed
76
77
  desc = _DescriptionOf(fn)

Iustin Pop's avatar
Iustin Pop committed
78
79
80
81
82
83
84
85
86
87
  print
  print _FormatHeader("%s start %s" % (tstart, desc))

  try:
    retval = fn(*args)
    return retval
  finally:
    tstop = datetime.datetime.now()
    tdelta = tstop - tstart
    print _FormatHeader("%s time=%s %s" % (tstop, tdelta, desc))
Iustin Pop's avatar
Iustin Pop committed
88
89


Iustin Pop's avatar
Iustin Pop committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
def RunTestIf(testnames, fn, *args):
  """Runs a test conditionally.

  @param testnames: either a single test name in the configuration
      file, or a list of testnames (which will be AND-ed together)

  """
  if qa_config.TestEnabled(testnames):
    RunTest(fn, *args)
  else:
    tstart = datetime.datetime.now()
    desc = _DescriptionOf(fn)
    print _FormatHeader("%s skipping %s, test(s) %s disabled" %
                        (tstart, desc, testnames))


Michael Hanselmann's avatar
Michael Hanselmann committed
106
107
def RunEnvTests():
  """Run several environment tests.
Iustin Pop's avatar
Iustin Pop committed
108
109

  """
Iustin Pop's avatar
Iustin Pop committed
110
111
112
  RunTestIf("env", qa_env.TestSshConnection)
  RunTestIf("env", qa_env.TestIcmpPing)
  RunTestIf("env", qa_env.TestGanetiCommands)
Iustin Pop's avatar
Iustin Pop committed
113

114

115
def SetupCluster(rapi_user, rapi_secret):
Michael Hanselmann's avatar
Michael Hanselmann committed
116
  """Initializes the cluster.
Iustin Pop's avatar
Iustin Pop committed
117

118
119
120
  @param rapi_user: Login user for RAPI
  @param rapi_secret: Login secret for RAPI

Michael Hanselmann's avatar
Michael Hanselmann committed
121
  """
Iustin Pop's avatar
Iustin Pop committed
122
123
  RunTestIf("create-cluster", qa_cluster.TestClusterInit,
            rapi_user, rapi_secret)
124
125
126
127
128

  # Test on empty cluster
  RunTestIf("node-list", qa_node.TestNodeList)
  RunTestIf("instance-list", qa_instance.TestInstanceList)

Iustin Pop's avatar
Iustin Pop committed
129
130
  RunTestIf("create-cluster", qa_node.TestNodeAddAll)
  if not qa_config.TestEnabled("create-cluster"):
131
132
    # consider the nodes are already there
    qa_node.MarkNodeAddedAll()
133

Iustin Pop's avatar
Iustin Pop committed
134
  RunTestIf("test-jobqueue", qa_cluster.TestJobqueue)
135

136
137
138
  # enable the watcher (unconditionally)
  RunTest(qa_daemon.TestResumeWatcher)

139
140
  RunTestIf("node-list", qa_node.TestNodeList)

141
142
143
144
  # Test listing fields
  RunTestIf("node-list", qa_node.TestNodeListFields)
  RunTestIf("instance-list", qa_instance.TestInstanceListFields)

Iustin Pop's avatar
Iustin Pop committed
145
  RunTestIf("node-info", qa_node.TestNodeInfo)
Michael Hanselmann's avatar
Michael Hanselmann committed
146
147
148
149


def RunClusterTests():
  """Runs tests related to gnt-cluster.
150

Michael Hanselmann's avatar
Michael Hanselmann committed
151
  """
Iustin Pop's avatar
Iustin Pop committed
152
153
154
155
  for test, fn in [
    ("cluster-renew-crypto", qa_cluster.TestClusterRenewCrypto),
    ("cluster-verify", qa_cluster.TestClusterVerify),
    ("cluster-reserved-lvs", qa_cluster.TestClusterReservedLvs),
156
    # TODO: add more cluster modify tests
Iustin Pop's avatar
Iustin Pop committed
157
158
159
160
161
162
163
164
165
166
167
168
169
    ("cluster-modify", qa_cluster.TestClusterModifyBe),
    ("cluster-rename", qa_cluster.TestClusterRename),
    ("cluster-info", qa_cluster.TestClusterVersion),
    ("cluster-info", qa_cluster.TestClusterInfo),
    ("cluster-info", qa_cluster.TestClusterGetmaster),
    ("cluster-copyfile", qa_cluster.TestClusterCopyfile),
    ("cluster-command", qa_cluster.TestClusterCommand),
    ("cluster-burnin", qa_cluster.TestClusterBurnin),
    ("cluster-master-failover", qa_cluster.TestClusterMasterFailover),
    ("rapi", qa_rapi.TestVersion),
    ("rapi", qa_rapi.TestEmptyCluster),
    ]:
    RunTestIf(test, fn)
170

171

Michael Hanselmann's avatar
Michael Hanselmann committed
172
173
def RunOsTests():
  """Runs all tests related to gnt-os.
Michael Hanselmann's avatar
Michael Hanselmann committed
174

Michael Hanselmann's avatar
Michael Hanselmann committed
175
  """
Iustin Pop's avatar
Iustin Pop committed
176
177
178
179
180
181
182
183
184
185
186
  for fn in [
    qa_os.TestOsList,
    qa_os.TestOsDiagnose,
    qa_os.TestOsValid,
    qa_os.TestOsInvalid,
    qa_os.TestOsPartiallyValid,
    qa_os.TestOsModifyValid,
    qa_os.TestOsModifyInvalid,
    qa_os.TestOsStates,
    ]:
    RunTestIf("os", fn)
Michael Hanselmann's avatar
Michael Hanselmann committed
187
188
189
190
191
192


def RunCommonInstanceTests(instance):
  """Runs a few tests that are common to all disk types.

  """
Iustin Pop's avatar
Iustin Pop committed
193
194
  RunTestIf("instance-shutdown", qa_instance.TestInstanceShutdown, instance)
  RunTestIf("instance-shutdown", qa_instance.TestInstanceStartup, instance)
Iustin Pop's avatar
Iustin Pop committed
195

Iustin Pop's avatar
Iustin Pop committed
196
  RunTestIf("instance-list", qa_instance.TestInstanceList)
Michael Hanselmann's avatar
Michael Hanselmann committed
197

Iustin Pop's avatar
Iustin Pop committed
198
  RunTestIf("instance-info", qa_instance.TestInstanceInfo, instance)
199

Iustin Pop's avatar
Iustin Pop committed
200
201
202
  RunTestIf("instance-modify", qa_instance.TestInstanceModify, instance)
  RunTestIf(["instance-modify", "rapi"],
            qa_rapi.TestRapiInstanceModify, instance)
203

Iustin Pop's avatar
Iustin Pop committed
204
  RunTestIf("instance-console", qa_instance.TestInstanceConsole, instance)
205

Iustin Pop's avatar
Iustin Pop committed
206
207
208
  RunTestIf("instance-reinstall", qa_instance.TestInstanceShutdown, instance)
  RunTestIf("instance-reinstall", qa_instance.TestInstanceReinstall, instance)
  RunTestIf("instance-reinstall", qa_instance.TestInstanceStartup, instance)
Iustin Pop's avatar
Iustin Pop committed
209

Iustin Pop's avatar
Iustin Pop committed
210
  RunTestIf("instance-reboot", qa_instance.TestInstanceReboot, instance)
211

212
  if qa_config.TestEnabled('instance-rename'):
213
    rename_source = instance["name"]
214
    rename_target = qa_config.get("rename", None)
215
216
217
    RunTest(qa_instance.TestInstanceShutdown, instance)
    # perform instance rename to the same name
    RunTest(qa_instance.TestInstanceRename, rename_source, rename_source)
218
219
    RunTestIf("rapi", qa_rapi.TestRapiInstanceRename,
              rename_source, rename_source)
220
221
    if rename_target is not None:
      # perform instance rename to a different name, if we have one configured
222
223
      RunTest(qa_instance.TestInstanceRename, rename_source, rename_target)
      RunTest(qa_instance.TestInstanceRename, rename_target, rename_source)
224
225
226
227
      RunTestIf("rapi", qa_rapi.TestRapiInstanceRename,
                rename_source, rename_target)
      RunTestIf("rapi", qa_rapi.TestRapiInstanceRename,
                rename_target, rename_source)
228
    RunTest(qa_instance.TestInstanceStartup, instance)
229

Iustin Pop's avatar
Iustin Pop committed
230
  RunTestIf("tags", qa_tags.TestInstanceTags, instance)
Michael Hanselmann's avatar
Michael Hanselmann committed
231

Michael Hanselmann's avatar
Michael Hanselmann committed
232
  RunTestIf("cluster-verify", qa_cluster.TestClusterVerify)
Michael Hanselmann's avatar
Michael Hanselmann committed
233

Iustin Pop's avatar
Iustin Pop committed
234
  RunTestIf("rapi", qa_rapi.TestInstance, instance)
235

236
237
238
  # Lists instances, too
  RunTestIf("node-list", qa_node.TestNodeList)

239
240
241
242
243

def RunCommonNodeTests():
  """Run a few common node tests.

  """
Iustin Pop's avatar
Iustin Pop committed
244
245
  RunTestIf("node-volumes", qa_node.TestNodeVolumes)
  RunTestIf("node-storage", qa_node.TestNodeStorage)
246
  RunTestIf("node-oob", qa_node.TestOutOfBand)
247

248

249
250
251
252
def RunGroupListTests():
  """Run tests for listing node groups.

  """
253
254
  RunTestIf("group-list", qa_group.TestGroupList)
  RunTestIf("group-list", qa_group.TestGroupListFields)
255
256


257
258
259
260
261
def RunGroupRwTests():
  """Run tests for adding/removing/renaming groups.

  """
  RunTestIf("group-rwops", qa_group.TestGroupAddRemoveRename)
262
263
264
265
  RunTestIf("group-rwops", qa_group.TestGroupAddWithOptions)
  RunTestIf("group-rwops", qa_group.TestGroupModify)
  RunTestIf("rapi", qa_rapi.TestRapiNodeGroups)

266

267
def RunExportImportTests(instance, pnode, snode):
Michael Hanselmann's avatar
Michael Hanselmann committed
268
  """Tries to export and import the instance.
Iustin Pop's avatar
Iustin Pop committed
269

270
271
272
273
  @param pnode: current primary node of the instance
  @param snode: current secondary node of the instance, if any,
      otherwise None

Michael Hanselmann's avatar
Michael Hanselmann committed
274
275
  """
  if qa_config.TestEnabled('instance-export'):
276
277
    RunTest(qa_instance.TestInstanceExportNoTarget, instance)

Michael Hanselmann's avatar
Michael Hanselmann committed
278
279
280
281
282
283
284
285
    expnode = qa_config.AcquireNode(exclude=pnode)
    try:
      name = RunTest(qa_instance.TestInstanceExport, instance, expnode)

      RunTest(qa_instance.TestBackupList, expnode)

      if qa_config.TestEnabled('instance-import'):
        newinst = qa_config.AcquireInstance()
Michael Hanselmann's avatar
Michael Hanselmann committed
286
        try:
Michael Hanselmann's avatar
Michael Hanselmann committed
287
288
289
          RunTest(qa_instance.TestInstanceImport, pnode, newinst,
                  expnode, name)
          RunTest(qa_instance.TestInstanceRemove, newinst)
Michael Hanselmann's avatar
Michael Hanselmann committed
290
        finally:
Michael Hanselmann's avatar
Michael Hanselmann committed
291
292
293
          qa_config.ReleaseInstance(newinst)
    finally:
      qa_config.ReleaseNode(expnode)
Michael Hanselmann's avatar
Michael Hanselmann committed
294

Iustin Pop's avatar
Iustin Pop committed
295
  if qa_config.TestEnabled(["rapi", "inter-cluster-instance-move"]):
296
297
    newinst = qa_config.AcquireInstance()
    try:
298
299
300
301
302
      if snode is None:
        excl = [pnode]
      else:
        excl = [pnode, snode]
      tnode = qa_config.AcquireNode(exclude=excl)
303
304
      try:
        RunTest(qa_rapi.TestInterClusterInstanceMove, instance, newinst,
305
                pnode, snode, tnode)
306
      finally:
307
        qa_config.ReleaseNode(tnode)
308
309
310
    finally:
      qa_config.ReleaseInstance(newinst)

Michael Hanselmann's avatar
Michael Hanselmann committed
311

Michael Hanselmann's avatar
Michael Hanselmann committed
312
313
def RunDaemonTests(instance, pnode):
  """Test the ganeti-watcher script.
314

Michael Hanselmann's avatar
Michael Hanselmann committed
315
  """
316
  RunTest(qa_daemon.TestPauseWatcher)
317

Iustin Pop's avatar
Iustin Pop committed
318
319
320
321
  RunTestIf("instance-automatic-restart",
            qa_daemon.TestInstanceAutomaticRestart, pnode, instance)
  RunTestIf("instance-consecutive-failures",
            qa_daemon.TestInstanceConsecutiveFailures, pnode, instance)
322

323
324
  RunTest(qa_daemon.TestResumeWatcher)

325

Michael Hanselmann's avatar
Michael Hanselmann committed
326
327
def RunHardwareFailureTests(instance, pnode, snode):
  """Test cluster internal hardware failure recovery.
Iustin Pop's avatar
Iustin Pop committed
328

Michael Hanselmann's avatar
Michael Hanselmann committed
329
  """
Iustin Pop's avatar
Iustin Pop committed
330
  RunTestIf("instance-failover", qa_instance.TestInstanceFailover, instance)
Michael Hanselmann's avatar
Michael Hanselmann committed
331

Iustin Pop's avatar
Iustin Pop committed
332
333
334
  RunTestIf("instance-migrate", qa_instance.TestInstanceMigrate, instance)
  RunTestIf(["instance-migrate", "rapi"],
            qa_rapi.TestRapiInstanceMigrate, instance)
335

336
  if qa_config.TestEnabled('instance-replace-disks'):
337
    othernode = qa_config.AcquireNode(exclude=[pnode, snode])
338
339
340
341
342
343
    try:
      RunTest(qa_instance.TestReplaceDisks,
              instance, pnode, snode, othernode)
    finally:
      qa_config.ReleaseNode(othernode)

Iustin Pop's avatar
Iustin Pop committed
344
  RunTestIf("node-evacuate", qa_node.TestNodeEvacuate, pnode, snode)
Michael Hanselmann's avatar
Michael Hanselmann committed
345

Iustin Pop's avatar
Iustin Pop committed
346
  RunTestIf("node-failover", qa_node.TestNodeFailover, pnode, snode)
Michael Hanselmann's avatar
Michael Hanselmann committed
347

Iustin Pop's avatar
Iustin Pop committed
348
  RunTestIf("instance-disk-failure", qa_instance.TestInstanceMasterDiskFailure,
Michael Hanselmann's avatar
Michael Hanselmann committed
349
            instance, pnode, snode)
Iustin Pop's avatar
Iustin Pop committed
350
351
352
  RunTestIf("instance-disk-failure",
            qa_instance.TestInstanceSecondaryDiskFailure, instance,
            pnode, snode)
Michael Hanselmann's avatar
Michael Hanselmann committed
353
354


355
@rapi.client.UsesRapiClient
Michael Hanselmann's avatar
Michael Hanselmann committed
356
357
358
359
def main():
  """Main program.

  """
360
  parser = optparse.OptionParser(usage="%prog [options] <config-file>")
Michael Hanselmann's avatar
Michael Hanselmann committed
361
362
363
364
  parser.add_option('--yes-do-it', dest='yes_do_it',
      action="store_true",
      help="Really execute the tests")
  (qa_config.options, args) = parser.parse_args()
Michael Hanselmann's avatar
Michael Hanselmann committed
365

366
367
  if len(args) == 1:
    (config_file, ) = args
Michael Hanselmann's avatar
Michael Hanselmann committed
368
  else:
369
    parser.error("Wrong number of arguments.")
Iustin Pop's avatar
Iustin Pop committed
370

Michael Hanselmann's avatar
Michael Hanselmann committed
371
372
373
374
375
  if not qa_config.options.yes_do_it:
    print ("Executing this script irreversibly destroys any Ganeti\n"
           "configuration on all nodes involved. If you really want\n"
           "to start testing, supply the --yes-do-it option.")
    sys.exit(1)
376

Michael Hanselmann's avatar
Michael Hanselmann committed
377
  qa_config.Load(config_file)
Iustin Pop's avatar
Iustin Pop committed
378

379
380
381
  rapi_user = "ganeti-qa"
  rapi_secret = utils.GenerateSecret()

Michael Hanselmann's avatar
Michael Hanselmann committed
382
  RunEnvTests()
383
  SetupCluster(rapi_user, rapi_secret)
Michael Hanselmann's avatar
Michael Hanselmann committed
384
385

  # Load RAPI certificate
386
  qa_rapi.Setup(rapi_user, rapi_secret)
Michael Hanselmann's avatar
Michael Hanselmann committed
387

Michael Hanselmann's avatar
Michael Hanselmann committed
388
389
  RunClusterTests()
  RunOsTests()
390

Iustin Pop's avatar
Iustin Pop committed
391
  RunTestIf("tags", qa_tags.TestClusterTags)
Michael Hanselmann's avatar
Michael Hanselmann committed
392

393
  RunCommonNodeTests()
394
  RunGroupListTests()
395
  RunGroupRwTests()
396

397
398
  pnode = qa_config.AcquireNode(exclude=qa_config.GetMasterNode())
  try:
Iustin Pop's avatar
Iustin Pop committed
399
400
    RunTestIf("node-readd", qa_node.TestNodeReadd, pnode)
    RunTestIf("node-modify", qa_node.TestNodeModify, pnode)
401
402
  finally:
    qa_config.ReleaseNode(pnode)
403

Michael Hanselmann's avatar
Michael Hanselmann committed
404
405
  pnode = qa_config.AcquireNode()
  try:
Iustin Pop's avatar
Iustin Pop committed
406
    RunTestIf("tags", qa_tags.TestNodeTags, pnode)
Michael Hanselmann's avatar
Michael Hanselmann committed
407

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
408
409
410
    if qa_rapi.Enabled():
      RunTest(qa_rapi.TestNode, pnode)

411
      if qa_config.TestEnabled("instance-add-plain-disk"):
412
413
414
415
416
417
        for use_client in [True, False]:
          rapi_instance = RunTest(qa_rapi.TestRapiInstanceAdd, pnode,
                                  use_client)
          RunCommonInstanceTests(rapi_instance)
          RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance, use_client)
          del rapi_instance
418

Michael Hanselmann's avatar
Michael Hanselmann committed
419
420
421
    if qa_config.TestEnabled('instance-add-plain-disk'):
      instance = RunTest(qa_instance.TestInstanceAddWithPlainDisk, pnode)
      RunCommonInstanceTests(instance)
422
      RunGroupListTests()
423
      RunExportImportTests(instance, pnode, None)
Michael Hanselmann's avatar
Michael Hanselmann committed
424
425
426
      RunDaemonTests(instance, pnode)
      RunTest(qa_instance.TestInstanceRemove, instance)
      del instance
427

428
429
430
431
432
433
434
435
436
437
438
    multinode_tests = [
      ('instance-add-drbd-disk',
       qa_instance.TestInstanceAddWithDrbdDisk),
    ]

    for name, func in multinode_tests:
      if qa_config.TestEnabled(name):
        snode = qa_config.AcquireNode(exclude=pnode)
        try:
          instance = RunTest(func, pnode, snode)
          RunCommonInstanceTests(instance)
439
          RunGroupListTests()
440
          if qa_config.TestEnabled('instance-convert-disk'):
441
            RunTest(qa_instance.TestInstanceShutdown, instance)
442
            RunTest(qa_instance.TestInstanceConvertDisk, instance, snode)
443
            RunTest(qa_instance.TestInstanceStartup, instance)
444
          RunExportImportTests(instance, pnode, snode)
445
446
447
448
449
          RunHardwareFailureTests(instance, pnode, snode)
          RunTest(qa_instance.TestInstanceRemove, instance)
          del instance
        finally:
          qa_config.ReleaseNode(snode)
Iustin Pop's avatar
Iustin Pop committed
450

Iustin Pop's avatar
Iustin Pop committed
451
    if qa_config.TestEnabled(["instance-add-plain-disk", "instance-export"]):
452
453
454
455
456
457
458
459
460
461
462
463
464
      for shutdown in [False, True]:
        instance = RunTest(qa_instance.TestInstanceAddWithPlainDisk, pnode)
        expnode = qa_config.AcquireNode(exclude=pnode)
        try:
          if shutdown:
            # Stop instance before exporting and removing it
            RunTest(qa_instance.TestInstanceShutdown, instance)
          RunTest(qa_instance.TestInstanceExportWithRemove, instance, expnode)
          RunTest(qa_instance.TestBackupList, expnode)
        finally:
          qa_config.ReleaseNode(expnode)
        del expnode
        del instance
465

Iustin Pop's avatar
Iustin Pop committed
466
  finally:
Michael Hanselmann's avatar
Michael Hanselmann committed
467
    qa_config.ReleaseNode(pnode)
Iustin Pop's avatar
Iustin Pop committed
468

Iustin Pop's avatar
Iustin Pop committed
469
  RunTestIf("create-cluster", qa_node.TestNodeRemoveAll)
Iustin Pop's avatar
Iustin Pop committed
470

Iustin Pop's avatar
Iustin Pop committed
471
  RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy)
Iustin Pop's avatar
Iustin Pop committed
472

473
474
475

if __name__ == '__main__':
  main()