Revision 526f866b

b/lib/jstore.py
203 203
  @return: Directory name
204 204

  
205 205
  """
206
  return str(int(job_id) / JOBS_PER_ARCHIVE_DIRECTORY)
206
  return str(ParseJobId(job_id) / JOBS_PER_ARCHIVE_DIRECTORY)
207

  
208

  
209
def ParseJobId(job_id):
210
  """Parses a job ID and converts it to integer.
211

  
212
  """
213
  try:
214
    return int(job_id)
215
  except (ValueError, TypeError):
216
    raise errors.ParameterError("Invalid job ID '%s'" % job_id)
b/lib/query.py
64 64
from ganeti import ht
65 65
from ganeti import runtime
66 66
from ganeti import qlang
67
from ganeti import jstore
67 68

  
68 69
from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
69 70
                              QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
......
103 104
# Query field flags
104 105
QFF_HOSTNAME = 0x01
105 106
QFF_IP_ADDRESS = 0x02
106
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
107
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
107
QFF_JOB_ID = 0x04
108
QFF_SPLIT_TIMESTAMP = 0x08
109
# Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
110
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
108 111

  
109 112
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
110 113
TITLE_RE = re.compile(r"^[^\s]+$")
......
345 348
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
346 349

  
347 350

  
351
def _PrepareSplitTimestamp(value):
352
  """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
353

  
354
  """
355
  if ht.TNumber(value):
356
    return value
357
  else:
358
    return utils.MergeTime(value)
359

  
360

  
361
def _MakeSplitTimestampComparison(fn):
362
  """Compares split timestamp values after converting to float.
363

  
364
  """
365
  return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
366

  
367

  
368
def _MakeComparisonChecks(fn):
369
  """Prepares flag-specific comparisons using a comparison function.
370

  
371
  """
372
  return [
373
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
374
     _PrepareSplitTimestamp),
375
    (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
376
     jstore.ParseJobId),
377
    (None, fn, None),
378
    ]
379

  
380

  
348 381
class _FilterCompilerHelper:
349 382
  """Converts a query filter to a callable usable for filtering.
350 383

  
......
363 396

  
364 397
  List of tuples containing flags and a callable receiving the left- and
365 398
  right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
366
  (e.g. L{QFF_HOSTNAME}).
399
  (e.g. L{QFF_HOSTNAME} or L{QFF_SPLIT_TIMESTAMP}).
367 400

  
368 401
  Order matters. The first item with flags will be used. Flags are checked
369 402
  using binary AND.
......
374 407
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
375 408
                                               case_sensitive=False),
376 409
     None),
410
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
411
     _PrepareSplitTimestamp),
377 412
    (None, operator.eq, None),
378 413
    ]
379 414

  
......
403 438
    qlang.OP_NOT_EQUAL:
404 439
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
405 440
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
406
    qlang.OP_LT: (_OPTYPE_BINARY, [
407
      (None, operator.lt, None),
408
      ]),
409
    qlang.OP_GT: (_OPTYPE_BINARY, [
410
      (None, operator.gt, None),
411
      ]),
412
    qlang.OP_LE: (_OPTYPE_BINARY, [
413
      (None, operator.le, None),
414
      ]),
415
    qlang.OP_GE: (_OPTYPE_BINARY, [
416
      (None, operator.ge, None),
417
      ]),
441
    qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
442
    qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
443
    qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
444
    qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
418 445
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
419 446
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
420 447
      ]),
......
2239 2266
  """
2240 2267
  fields = [
2241 2268
    (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2242
     None, 0, lambda _, (job_id, job): job_id),
2269
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2243 2270
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2244 2271
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2245 2272
    (_MakeField("priority", "Priority", QFT_NUMBER,
......
2270 2297
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2271 2298
                "List of opcode priorities"),
2272 2299
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2273
    (_MakeField("received_ts", "Received", QFT_OTHER,
2274
                "Timestamp of when job was received"),
2275
     None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2276
    (_MakeField("start_ts", "Start", QFT_OTHER,
2277
                "Timestamp of job start"),
2278
     None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2279
    (_MakeField("end_ts", "End", QFT_OTHER,
2280
                "Timestamp of job end"),
2281
     None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2282 2300
    (_MakeField("summary", "Summary", QFT_OTHER,
2283 2301
                "List of per-opcode summaries"),
2284 2302
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2285 2303
    ]
2286 2304

  
2305
  # Timestamp fields
2306
  for (name, attr, title, desc) in [
2307
    ("received_ts", "received_timestamp", "Received",
2308
     "Timestamp of when job was received"),
2309
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2310
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2311
    ]:
2312
    getter = operator.attrgetter(attr)
2313
    fields.extend([
2314
      (_MakeField(name, title, QFT_OTHER,
2315
                  "%s (tuple containing seconds and microseconds)" % desc),
2316
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2317
      ])
2318

  
2287 2319
  return _PrepareFieldList(fields, [])
2288 2320

  
2289 2321

  
b/test/ganeti.query_unittest.py
1777 1777
    self.assertEqual(q.Query(data),
1778 1778
                     [[(constants.RS_NORMAL, i)] for i in range(50, 100)])
1779 1779

  
1780
  def testFilterLessGreaterJobId(self):
1781
    fielddefs = query._PrepareFieldList([
1782
      (query._MakeField("id", "ID", constants.QFT_TEXT, "Job ID"),
1783
       None, query.QFF_JOB_ID, lambda ctx, item: item),
1784
      ], [])
1785

  
1786
    data = ["1", "2", "3", "10", "102", "120", "125", "15", "100", "7"]
1787

  
1788
    assert data != utils.NiceSort(data), "Test data should not be sorted"
1789

  
1790
    q = query.Query(fielddefs, ["id"], qfilter=["<", "id", "20"])
1791
    self.assertTrue(q.RequestedNames() is None)
1792
    self.assertEqual(q.Query(data), [
1793
      [(constants.RS_NORMAL, "1")],
1794
      [(constants.RS_NORMAL, "2")],
1795
      [(constants.RS_NORMAL, "3")],
1796
      [(constants.RS_NORMAL, "10")],
1797
      [(constants.RS_NORMAL, "15")],
1798
      [(constants.RS_NORMAL, "7")],
1799
      ])
1800

  
1801
    q = query.Query(fielddefs, ["id"], qfilter=[">=", "id", "100"])
1802
    self.assertTrue(q.RequestedNames() is None)
1803
    self.assertEqual(q.Query(data), [
1804
      [(constants.RS_NORMAL, "102")],
1805
      [(constants.RS_NORMAL, "120")],
1806
      [(constants.RS_NORMAL, "125")],
1807
      [(constants.RS_NORMAL, "100")],
1808
      ])
1809

  
1810
    # Integers are no valid job IDs
1811
    self.assertRaises(errors.ParameterError, query.Query,
1812
                      fielddefs, ["id"], qfilter=[">=", "id", 10])
1813

  
1814
  def testFilterLessGreaterSplitTimestamp(self):
1815
    fielddefs = query._PrepareFieldList([
1816
      (query._MakeField("ts", "Timestamp", constants.QFT_OTHER, "Timestamp"),
1817
       None, query.QFF_SPLIT_TIMESTAMP, lambda ctx, item: item),
1818
      ], [])
1819

  
1820
    data = [
1821
      utils.SplitTime(0),
1822
      utils.SplitTime(0.1),
1823
      utils.SplitTime(18224.7872),
1824
      utils.SplitTime(919896.12623),
1825
      utils.SplitTime(999),
1826
      utils.SplitTime(989.9999),
1827
      ]
1828

  
1829
    for i in [0, [0, 0]]:
1830
      q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", i])
1831
      self.assertTrue(q.RequestedNames() is None)
1832
      self.assertEqual(q.Query(data), [])
1833

  
1834
    q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", 1000])
1835
    self.assertTrue(q.RequestedNames() is None)
1836
    self.assertEqual(q.Query(data), [
1837
      [(constants.RS_NORMAL, (0, 0))],
1838
      [(constants.RS_NORMAL, (0, 100000))],
1839
      [(constants.RS_NORMAL, (999, 0))],
1840
      [(constants.RS_NORMAL, (989, 999900))],
1841
      ])
1842

  
1843
    q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", 5000.3])
1844
    self.assertTrue(q.RequestedNames() is None)
1845
    self.assertEqual(q.Query(data), [
1846
      [(constants.RS_NORMAL, (18224, 787200))],
1847
      [(constants.RS_NORMAL, (919896, 126230))],
1848
      ])
1849

  
1850
    for i in [18224.7772, utils.SplitTime(18224.7772)]:
1851
      q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", i])
1852
      self.assertTrue(q.RequestedNames() is None)
1853
      self.assertEqual(q.Query(data), [
1854
        [(constants.RS_NORMAL, (18224, 787200))],
1855
        [(constants.RS_NORMAL, (919896, 126230))],
1856
        ])
1857

  
1858
    q = query.Query(fielddefs, ["ts"], qfilter=[">", "ts", 18224.7880])
1859
    self.assertTrue(q.RequestedNames() is None)
1860
    self.assertEqual(q.Query(data), [
1861
      [(constants.RS_NORMAL, (919896, 126230))],
1862
      ])
1863

  
1780 1864

  
1781 1865
if __name__ == "__main__":
1782 1866
  testutils.GanetiTestProgram()

Also available in: Unified diff