Set release date for 2.9.4
[ganeti-local] / qa / qa_job.py
1 #
2 #
3
4 # Copyright (C) 2012, 2014 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
22 """Job-related QA tests.
23
24 """
25
26 from ganeti.utils import retry
27 from ganeti import constants
28 from ganeti import query
29
30 import functools
31 import re
32
33 import qa_config
34 import qa_error
35 import qa_utils
36
37 from qa_utils import AssertCommand, GetCommandOutput
38
39
40 def TestJobList():
41   """gnt-job list"""
42   qa_utils.GenericQueryTest("gnt-job", query.JOB_FIELDS.keys(),
43                             namefield="id", test_unknown=False)
44
45
46 def TestJobListFields():
47   """gnt-node list-fields"""
48   qa_utils.GenericQueryFieldsTest("gnt-job", query.JOB_FIELDS.keys())
49
50
51 def _GetJobStatuses():
52   """ Invokes gnt-job list and extracts an id to status dictionary.
53
54   @rtype: dict of string to string
55   @return: A dictionary mapping job ids to matching statuses
56
57   """
58   master = qa_config.GetMasterNode()
59   list_output = GetCommandOutput(
60     master.primary, "gnt-job list --no-headers --output=id,status"
61   )
62   return dict(map(lambda s: s.split(), list_output.splitlines()))
63
64
65 def _GetJobStatus(job_id):
66   """ Retrieves the status of a job.
67
68   @type job_id: string
69   @param job_id: The job id, represented as a string.
70   @rtype: string or None
71
72   @return: The job status, or None if not present.
73
74   """
75   return _GetJobStatuses().get(job_id, None)
76
77
78 def _RetryingFetchJobStatus(retry_status, job_id):
79   """ Used with C{retry.Retry}, waits for a status other than the one given.
80
81   @type retry_status: string
82   @param retry_status: The old job status, expected to change.
83   @type job_id: string
84   @param job_id: The job id, represented as a string.
85
86   @rtype: string or None
87   @return: The new job status, or None if none could be retrieved.
88
89   """
90   status = _GetJobStatus(job_id)
91   if status == retry_status:
92     raise retry.RetryAgain()
93   return status
94
95
96 def TestJobCancellation():
97   """gnt-job cancel"""
98   # The delay used for the first command should be large enough for the next
99   # command and the cancellation command to complete before the first job is
100   # done. The second delay should be small enough that not too much time is
101   # spend waiting in the case of a failed cancel and a running command.
102   FIRST_COMMAND_DELAY = 10.0
103   AssertCommand(["gnt-debug", "delay", "--submit", str(FIRST_COMMAND_DELAY)])
104
105   SECOND_COMMAND_DELAY = 1.0
106   master = qa_config.GetMasterNode()
107
108   # Forcing tty usage does not work on buildbot, so force all output of this
109   # command to be redirected to stdout
110   job_id_output = GetCommandOutput(
111     master.primary, "gnt-debug delay --submit %s 2>&1" % SECOND_COMMAND_DELAY
112   )
113
114   possible_job_ids = re.findall("JobID: ([0-9]+)", job_id_output)
115   if len(possible_job_ids) != 1:
116     raise qa_error.Error("Cannot parse gnt-debug delay output to find job id")
117
118   job_id = possible_job_ids[0]
119   AssertCommand(["gnt-job", "cancel", job_id])
120
121   # Now wait until the second job finishes, and expect the watch to fail due to
122   # job cancellation
123   AssertCommand(["gnt-job", "watch", job_id], fail=True)
124
125   # Then check for job cancellation
126   job_status = _GetJobStatus(job_id)
127   if job_status != constants.JOB_STATUS_CANCELED:
128     # Try and see if the job is being cancelled, and wait until the status
129     # changes or we hit a timeout
130     if job_status == constants.JOB_STATUS_CANCELING:
131       retry_fn = functools.partial(_RetryingFetchJobStatus,
132                                    constants.JOB_STATUS_CANCELING, job_id)
133       try:
134         job_status = retry.Retry(retry_fn, 2.0, 2 * FIRST_COMMAND_DELAY)
135       except retry.RetryTimeout:
136         # The job status remains the same
137         pass
138
139     if job_status != constants.JOB_STATUS_CANCELED:
140       raise qa_error.Error("Job was not successfully cancelled, status "
141                            "found: %s" % job_status)