1 """
2 A cli-facing module providing functionality to "validate" one or more
3 resource descriptors.
4
5 Validation means giving some prognosis as to whether RD will properly work
6 within both the DC and the VO.
7
8 While validation is active there's base.VALIDATING=True. If RDs
9 to anything expensive, they're advised to have something like::
10
11 if getattr(base, "VALIDATING", False):
12 (don't do the expensive thing)
13 """
14
15
16
17
18
19
20
21 from __future__ import print_function
22
23 import argparse
24 import re
25 import sys
26 import traceback
27
28 from gavo import api
29 from gavo import adql
30 from gavo import base
31 from gavo import stc
32 from gavo import registry
33 from gavo import utils
34 from gavo.helpers import testtricks
35 from gavo.registry import builders
36 from gavo.protocols import datalink
37 from gavo.user import errhandle
38 from gavo.web import htmltable
39
40 from gavo.web import examplesrender
41
42 builders.VALIDATING = True
45 """a singleton that collects use cases to run.
46
47 Don't instantiate, this is a global singleton.
48
49 The testsToRun attribute contains the test suites to run.
50 """
51 testsToRun = []
52
53 @classmethod
55 """adds tests from rd.
56 """
57 for suite in rd.tests:
58 cls.testsToRun.append(suite)
59
62 """an output function for errhandle.raiseAndCatch.
63
64 It is used here to indent dependent error messages.
65 """
66 print(re.sub("(?m)^", " ", aString))
67
73
79
82 """returns the RD identified by rdId.
83
84 If that fails, diagnostics are printed and None is returned.
85 """
86 try:
87 rd = api.getReferencedElement(rdId, doQueries=False)
88
89
90 if hasattr(rd, "getRealRD"):
91 rd = rd.getRealRD()
92
93 except api.RDNotFound:
94 outputError(rdId, "RD or dependency not found, message follows", True)
95 except api.LiteralParseError:
96 outputError(rdId, "Bad literal in RD, message follows", True)
97 except api.StructureError:
98 outputError(rdId, "Malformed RD input, message follows", True)
99 except api.Error:
100 outputError(rdId, "Syntax or internal error, message follows", True)
101 else:
102 return rd
103
104 return None
105
106
107 _XSD_VALIDATOR = testtricks.XSDTestMixin()
111 """returns true if svc has a publication facing the VO.
112 """
113 for pub in svc.publications:
114 if "ivo_managed" in pub.sets:
115 return True
116 else:
117 return False
118
121 """outputs to stdout various diagnostics about the services on rd.
122 """
123 validSoFar = True
124 for svc in rd.services:
125
126 if not (args.prePublication or svc.publications):
127 continue
128 try:
129 base.validateStructure(svc)
130 except api.MetaValidationError as ex:
131 validSoFar = False
132 outputError(rd.sourceId, "Missing metadata for publication of"
133 " service %s:\n%s"%(svc.id, str(ex)))
134 continue
135
136 if not (args.prePublication or isIVOPublished(svc)):
137
138 continue
139
140
141 api.getMetaText(svc, "identifier")
142 registryRecord = None
143 try:
144 registryRecord = builders.getVORMetadataElement(svc)
145 except stc.STCSParseError as msg:
146 validSoFar = False
147 outputError(rd.sourceId, "Invalid STC-S (probably in coverage meta)"
148 ": %s"%str(msg))
149 except:
150 validSoFar = False
151 outputError(rd.sourceId, "Error when producing registry record"
152 " of service %s:"%svc.id, True)
153
154 if registryRecord is not None:
155 try:
156 _XSD_VALIDATOR.assertValidates(
157 registryRecord.render(), leaveOffending=True)
158 except AssertionError as msg:
159 validSoFar = False
160 outputError(rd.sourceId, "Invalid registry record for service"
161 " %s:\n%s"%(svc.id, str(msg)))
162
163 return validSoFar
164
186
187 return validateRSTOne(rd)
188
191 """tries to build all rowmakers mentioned in the RD and bails out
192 if one is bad.
193 """
194 for dd in rd:
195 for m in dd.makes:
196 m.table.onDisk = False
197 try:
198 api.TableForDef(m.table)
199 m.rowmaker.compileForTableDef(m.table)
200 finally:
201 m.table.onDisk = True
202 return True
203
206 """tries to compile other pieces of code in an RD and bails out
207 if one is bad.
208 """
209 retval = True
210
211 for suite in rd.tests:
212 for test in suite.tests:
213 try:
214 test.compile()
215 except Exception as msg:
216 outputError(rd.sourceId, "Bad test '%s': %s"%(test.title,
217 msg))
218 retval = False
219
220 for svc in rd.services:
221 for outputField in svc.getCurOutputFields():
222 if outputField.formatter:
223 try:
224 htmltable._compileRenderer(outputField.formatter, None, rd)
225 except Exception as msg:
226 outputError(rd.sourceId, "Bad formatter on output field '%s': %s"%(
227 outputField.name, msg))
228 retval = False
229
230 if isinstance(svc.core, datalink.DatalinkCore):
231 try:
232 if "dlmeta" in svc.allowed:
233 svc.core.descriptorGenerator.compile(svc.core)
234 if "dlget" in svc.allowed:
235 for df in svc.core.dataFunctions:
236 df.compile(svc.core)
237 svc.core.dataFormatter.compile(svc.core)
238 except Exception as msg:
239 outputError(rd.sourceId, "Bad datalink function in service '%s': %s"%(
240 svc.id, msg))
241 if isinstance(msg, base.BadCode):
242 outputError(rd.sourceId, "Bad code:\n%s"%msg.code)
243 retval = False
244
245 for job in rd.jobs:
246 try:
247 job.job.compile(parent=rd)
248 except Exception as msg:
249 outputError(rd.sourceId, "Bad code in job '%s': %s"%(
250 job.title, msg))
251 retval = False
252
253
254
255
256
257
258
259 return retval
260
263 """does some sanity checks on the (top-level) tables within rd.
264 """
265 valid = True
266
267 identifierSymbol = adql.getSymbols()["identifier"]
268
269 for td in rd.tables:
270 for col in td:
271 try:
272 if col.unit:
273 parsedUnit = api.parseUnit(col.unit)
274 if parsedUnit.isUnknown and not args.acceptFreeUnits:
275 outputWarning(rd.sourceId,
276 "Column %s.%s: Unit %s is not interoperable"%(
277 td.getQName(), col.name, col.unit))
278
279 except api.BadUnit:
280 valid = False
281 outputError(rd.sourceId, "Bad unit in table %s, column %s: %s"%(
282 td.getQName(), col.name, repr(col.unit)))
283
284 try:
285 identifierSymbol.parseString(str(col.name), parseAll=True)
286 except base.ParseException as msg:
287 outputWarning(rd.sourceId, "Column %s.%s: Name is not a regular"
288 " ADQL identifier."%(td.id, col.name))
289
290 if td.onDisk and args.compareDB:
291 with base.getTableConn() as conn:
292 q = base.UnmanagedQuerier(conn)
293 if q.getTableType(td.getQName()) is not None:
294 t = api.TableForDef(td, connection=conn)
295 try:
296 t.ensureOnDiskMatches()
297 except api.DataError as msg:
298 valid = False
299 outputError(rd.sourceId,
300 utils.makeEllipsis(utils.safe_str(msg), 160))
301
302
303 for dldef in td.iterMeta("_associatedDatalinkService"):
304 try:
305 _ = base.resolveId(td.rd,
306 base.getMetaText(dldef, "serviceId"))
307 _ = td.getColumnByName(base.getMetaText(dldef, "idColumn"))
308 _
309
310 except (base.NotFoundError, base.MetaError) as msg:
311 valid = False
312 outputError(rd.sourceId,
313 utils.makeEllipsis(utils.safe_str(msg), 160))
314
315 if td.registration:
316 registryRecord = None
317 try:
318 registryRecord = builders.getVORMetadataElement(td)
319 except Exception as msg:
320 valid = False
321 outputError(rd.sourceId,
322 "Table publication of %s could not be built: %s"%(
323 td.id, str(msg)))
324
325 if registryRecord is not None:
326 try:
327 _XSD_VALIDATOR.assertValidates(
328 registryRecord.render(), leaveOffending=True)
329 except AssertionError as msg:
330 valid = False
331 outputError(rd.sourceId, "Invalid registry record for table"
332 " %s:\n%s"%(td.id, str(msg)))
333
334 return valid
335
357
360 """validates all accessible RDs.
361 """
362 for rdId in registry.findAllRDs():
363 if args.verbose:
364 sys.stdout.write(rdId+" ")
365 sys.stdout.flush()
366 try:
367 validateOne(rdId, args)
368 except Exception:
369 sys.stderr.write("Severe error while validating %s:\n"%rdId)
370 traceback.print_exc()
371 if args.verbose:
372 sys.stdout.write("\n")
373
376 parser = argparse.ArgumentParser(description="Check RDs for well-formedness"
377 " and some aspects of VO-friendlyness")
378 parser.add_argument("rd", nargs="+", type=str,
379 help="RD identifier or file system path. Use magic value ALL to"
380 " check all reachable RDs.")
381 parser.add_argument("-p", "--pre-publication", help="Validate"
382 " as if all services were IVOA published even if they are not"
383 " (this may produce spurious errors if unpublished services are in"
384 " the RD).",
385 action="store_true", dest="prePublication")
386 parser.add_argument("-v", "--verbose", help="Talk while working",
387 action="store_true", dest="verbose")
388 parser.add_argument("-t", "--run-tests", help="Run regression tests"
389 " embedded in the checked RDs", action="store_true", dest="runTests")
390 parser.add_argument("-T", "--timeout", help="When running tests, abort"
391 " and fail requests after inactivity of SECONDS",
392 action="store", dest="timeout", type=int, default=15, metavar="SECONDS")
393 parser.add_argument("-c", "--compare-db", help="Also make sure that"
394 " tables that are on disk (somewhat) match the definition in the RD.",
395 action="store_true", dest="compareDB")
396 parser.add_argument("-u", "--accept-free-units", help="Do not warn"
397 " against units not listed in VOUnits.",
398 action="store_true", dest="acceptFreeUnits")
399
400
401 return parser.parse_args()
402
405 base.VALIDATING = True
406 args = parseCommandLine()
407 if len(args.rd)==1 and args.rd[0]=="ALL":
408 validateAll(args)
409 else:
410 for rd in args.rd:
411 print(rd, "--", end=' ')
412 sys.stdout.flush()
413 if validateOne(rd, args):
414 print("OK")
415 else:
416 print("Fail")
417
418 if args.runTests:
419 print("\nRunning regression tests\n")
420 from gavo.rscdef import regtest
421 runner = regtest.TestRunner(TestsCollector.testsToRun,
422 verbose=False, timeout=args.timeout)
423 runner.runTests(showDots=True)
424 print(runner.stats.getReport())
425 if runner.stats.fails:
426 print("\nThe following tests failed:\n")
427 print(runner.stats.getFailures())
428