|
4 | 4 |
|
5 | 5 | import datetime |
6 | 6 | import io |
| 7 | +import json |
7 | 8 | import os |
8 | 9 | import pathlib |
9 | 10 | import re |
| 11 | +import sys |
10 | 12 | import time |
11 | 13 | import traceback |
12 | 14 | import xml2rfc |
|
15 | 17 | from shutil import move |
16 | 18 | from typing import Optional, Union # pyflakes:ignore |
17 | 19 | from unidecode import unidecode |
| 20 | +from xym import xym |
18 | 21 |
|
19 | 22 | from django.conf import settings |
20 | 23 | from django.core.exceptions import ValidationError |
|
43 | 46 | from ietf.community.utils import update_name_contains_indexes_with_new_doc |
44 | 47 | from ietf.submit.mail import ( announce_to_lists, announce_new_version, announce_to_authors, |
45 | 48 | send_approval_request, send_submission_confirmation, announce_new_wg_00, send_manual_post_request ) |
| 49 | +from ietf.submit.checkers import DraftYangChecker |
46 | 50 | from ietf.submit.models import ( Submission, SubmissionEvent, Preapproval, DraftSubmissionStateName, |
47 | 51 | SubmissionCheck, SubmissionExtResource ) |
48 | 52 | from ietf.utils import log |
@@ -1431,3 +1435,133 @@ def process_uploaded_submission(submission): |
1431 | 1435 | submission.state_id = "uploaded" |
1432 | 1436 | submission.save() |
1433 | 1437 | create_submission_event(None, submission, desc="Completed submission validation checks") |
| 1438 | + |
| 1439 | + |
| 1440 | +def apply_yang_checker_to_draft(checker, draft): |
| 1441 | + submission = Submission.objects.filter(name=draft.name, rev=draft.rev).order_by('-id').first() |
| 1442 | + if submission: |
| 1443 | + check = submission.checks.filter(checker=checker.name).order_by('-id').first() |
| 1444 | + if check: |
| 1445 | + result = checker.check_file_txt(draft.get_file_name()) |
| 1446 | + passed, message, errors, warnings, items = result |
| 1447 | + items = json.loads(json.dumps(items)) |
| 1448 | + new_res = (passed, errors, warnings, message) |
| 1449 | + old_res = (check.passed, check.errors, check.warnings, check.message) if check else () |
| 1450 | + if new_res != old_res: |
| 1451 | + log.log(f"Saving new yang checker results for {draft.name}-{draft.rev}") |
| 1452 | + qs = submission.checks.filter(checker=checker.name).order_by('time') |
| 1453 | + submission.checks.filter(checker=checker.name).exclude(pk=qs.first().pk).delete() |
| 1454 | + submission.checks.create(submission=submission, checker=checker.name, passed=passed, |
| 1455 | + message=message, errors=errors, warnings=warnings, items=items, |
| 1456 | + symbol=checker.symbol) |
| 1457 | + else: |
| 1458 | + log.log(f"Could not run yang checker for {draft.name}-{draft.rev}: missing submission object") |
| 1459 | + |
| 1460 | + |
| 1461 | +def run_all_yang_model_checks(): |
| 1462 | + checker = DraftYangChecker() |
| 1463 | + for draft in Document.objects.filter( |
| 1464 | + type_id="draft", |
| 1465 | + states=State.objects.get(type="draft", slug="active"), |
| 1466 | + ): |
| 1467 | + apply_yang_checker_to_draft(checker, draft) |
| 1468 | + |
| 1469 | + |
| 1470 | +def populate_yang_model_dirs(): |
| 1471 | + """Update the yang model dirs |
| 1472 | +
|
| 1473 | + * All yang modules from published RFCs should be extracted and be |
| 1474 | + available in an rfc-yang repository. |
| 1475 | +
|
| 1476 | + * All valid yang modules from active, not replaced, Internet-Drafts |
| 1477 | + should be extracted and be available in a draft-valid-yang repository. |
| 1478 | +
|
| 1479 | + * All, valid and invalid, yang modules from active, not replaced, |
| 1480 | + Internet-Drafts should be available in a draft-all-yang repository. |
| 1481 | + (Actually, given precedence ordering, it would be enough to place |
| 1482 | + non-validating modules in a draft-invalid-yang repository instead). |
| 1483 | +
|
| 1484 | + * In all cases, example modules should be excluded. |
| 1485 | +
|
| 1486 | + * Precedence is established by the search order of the repository as |
| 1487 | + provided to pyang. |
| 1488 | +
|
| 1489 | + * As drafts expire, models should be removed in order to catch cases |
| 1490 | + where a module being worked on depends on one which has slipped out |
| 1491 | + of the work queue. |
| 1492 | +
|
| 1493 | + """ |
| 1494 | + def extract_from(file, dir, strict=True): |
| 1495 | + saved_stdout = sys.stdout |
| 1496 | + saved_stderr = sys.stderr |
| 1497 | + xymerr = io.StringIO() |
| 1498 | + xymout = io.StringIO() |
| 1499 | + sys.stderr = xymerr |
| 1500 | + sys.stdout = xymout |
| 1501 | + model_list = [] |
| 1502 | + try: |
| 1503 | + model_list = xym.xym(str(file), str(file.parent), str(dir), strict=strict, debug_level=-2) |
| 1504 | + for name in model_list: |
| 1505 | + modfile = moddir / name |
| 1506 | + mtime = file.stat().st_mtime |
| 1507 | + os.utime(str(modfile), (mtime, mtime)) |
| 1508 | + if '"' in name: |
| 1509 | + name = name.replace('"', '') |
| 1510 | + modfile.rename(str(moddir / name)) |
| 1511 | + model_list = [n.replace('"', '') for n in model_list] |
| 1512 | + except Exception as e: |
| 1513 | + log.log("Error when extracting from %s: %s" % (file, str(e))) |
| 1514 | + finally: |
| 1515 | + sys.stdout = saved_stdout |
| 1516 | + sys.stderr = saved_stderr |
| 1517 | + return model_list |
| 1518 | + |
| 1519 | + # Extract from new RFCs |
| 1520 | + |
| 1521 | + rfcdir = Path(settings.RFC_PATH) |
| 1522 | + |
| 1523 | + moddir = Path(settings.SUBMIT_YANG_RFC_MODEL_DIR) |
| 1524 | + if not moddir.exists(): |
| 1525 | + moddir.mkdir(parents=True) |
| 1526 | + |
| 1527 | + latest = 0 |
| 1528 | + for item in moddir.iterdir(): |
| 1529 | + if item.stat().st_mtime > latest: |
| 1530 | + latest = item.stat().st_mtime |
| 1531 | + |
| 1532 | + log.log(f"Extracting RFC Yang models to {moddir} ...") |
| 1533 | + for item in rfcdir.iterdir(): |
| 1534 | + if item.is_file() and item.name.startswith('rfc') and item.name.endswith('.txt') and item.name[3:-4].isdigit(): |
| 1535 | + if item.stat().st_mtime > latest: |
| 1536 | + model_list = extract_from(item, moddir) |
| 1537 | + for name in model_list: |
| 1538 | + if not (name.startswith('ietf') or name.startswith('iana')): |
| 1539 | + modfile = moddir / name |
| 1540 | + modfile.unlink() |
| 1541 | + |
| 1542 | + # Extract valid modules from drafts |
| 1543 | + |
| 1544 | + six_months_ago = time.time() - 6 * 31 * 24 * 60 * 60 |
| 1545 | + |
| 1546 | + def active(dirent): |
| 1547 | + return dirent.stat().st_mtime > six_months_ago |
| 1548 | + |
| 1549 | + draftdir = Path(settings.INTERNET_DRAFT_PATH) |
| 1550 | + moddir = Path(settings.SUBMIT_YANG_DRAFT_MODEL_DIR) |
| 1551 | + if not moddir.exists(): |
| 1552 | + moddir.mkdir(parents=True) |
| 1553 | + log.log(f"Emptying {moddir} ...") |
| 1554 | + for item in moddir.iterdir(): |
| 1555 | + item.unlink() |
| 1556 | + |
| 1557 | + log.log(f"Extracting draft Yang models to {moddir} ...") |
| 1558 | + for item in draftdir.iterdir(): |
| 1559 | + try: |
| 1560 | + if item.is_file() and item.name.startswith('draft') and item.name.endswith('.txt') and active(item): |
| 1561 | + model_list = extract_from(item, moddir, strict=False) |
| 1562 | + for name in model_list: |
| 1563 | + if name.startswith('example'): |
| 1564 | + modfile = moddir / name |
| 1565 | + modfile.unlink() |
| 1566 | + except UnicodeDecodeError as e: |
| 1567 | + log.log(f"Error processing {item.name}: {e}") |
0 commit comments