Refactored draft submission checks so that new checkers can be slotted in through a configuration in settings.py. Refactored the calling of idnits to use the new API, and added a pyang validation check.
- Legacy-Id: 10894
This commit is contained in:
parent
1c8a171703
commit
76bb233b70
|
@ -2,6 +2,7 @@ import os
|
|||
|
||||
from django.conf import settings
|
||||
from django.core import checks
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
@checks.register('directories')
|
||||
def check_cdn_directory_exists(app_configs, **kwargs):
|
||||
|
@ -98,3 +99,46 @@ def check_id_submission_files(app_configs, **kwargs):
|
|||
id = "datatracker.E0007",
|
||||
))
|
||||
return errors
|
||||
|
||||
@checks.register('submission-checkers')
|
||||
def check_id_submission_checkers(app_configs, **kwargs):
|
||||
errors = []
|
||||
for checker_path in settings.IDSUBMIT_CHECKER_CLASSES:
|
||||
try:
|
||||
checker_class = import_string(checker_path)
|
||||
except Exception as e:
|
||||
errors.append(checks.Critical(
|
||||
"An exception was raised when trying to import the draft submission"
|
||||
"checker class '%s':\n %s" % (checker_path, e),
|
||||
hint = "Please check that the class exists and can be imported.",
|
||||
id = "datatracker.E0008",
|
||||
))
|
||||
try:
|
||||
checker = checker_class()
|
||||
except Exception as e:
|
||||
errors.append(checks.Critical(
|
||||
"An exception was raised when trying to instantiate the draft submission"
|
||||
"checker class '%s': %s" % (checker_path, e),
|
||||
hint = "Please check that the class can be instantiated.",
|
||||
id = "datatracker.E0009",
|
||||
))
|
||||
continue
|
||||
for attr in ('name',):
|
||||
if not hasattr(checker, attr):
|
||||
errors.append(checks.Critical(
|
||||
"The draft submission checker '%s' has no attribute '%s', which is required" % (checker_path, attr),
|
||||
hint = "Please update the class.",
|
||||
id = "datatracker.E0010",
|
||||
))
|
||||
checker_methods = ("check_file_txt", "check_file_xml", "check_fragment_txt", "check_fragment_xml", )
|
||||
for method in checker_methods:
|
||||
if hasattr(checker, method):
|
||||
break
|
||||
else:
|
||||
errors.append(checks.Critical(
|
||||
"The draft submission checker '%s' has no recognised checker method; "
|
||||
"should be one or more of %s." % (checker_path, checker_methods),
|
||||
hint = "Please update the class.",
|
||||
id = "datatracker.E0011",
|
||||
))
|
||||
return errors
|
||||
|
|
|
@ -472,10 +472,16 @@ IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH
|
|||
IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/'
|
||||
IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/'
|
||||
IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits'
|
||||
IDSUBMIT_PYANG_COMMAND = 'pyang -p %(workdir)s --verbose --ietf %(model)s'
|
||||
|
||||
IDSUBMIT_CHECKER_CLASSES = (
|
||||
"ietf.submit.checkers.DraftIdnitsChecker",
|
||||
"ietf.submit.checkers.DraftYangChecker",
|
||||
)
|
||||
|
||||
|
||||
IDSUBMIT_MANUAL_STAGING_DIR = '/tmp/'
|
||||
|
||||
|
||||
IDSUBMIT_FILE_TYPES = (
|
||||
'txt',
|
||||
'xml',
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
from django.contrib import admin
|
||||
|
||||
|
||||
from ietf.submit.models import Preapproval, Submission
|
||||
from ietf.submit.models import Preapproval, Submission, SubmissionCheck
|
||||
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'draft_link', 'status_link', 'submission_date',]
|
||||
|
@ -23,9 +23,13 @@ class SubmissionAdmin(admin.ModelAdmin):
|
|||
else:
|
||||
return instance.name
|
||||
draft_link.allow_tags = True
|
||||
|
||||
admin.site.register(Submission, SubmissionAdmin)
|
||||
|
||||
class SubmissionCheckAdmin(admin.ModelAdmin):
|
||||
list_display = ['submission', 'time', 'checker', 'passed', 'errors', 'warnings', 'items']
|
||||
raw_id_fields = ['submission']
|
||||
admin.site.register(SubmissionCheck, SubmissionCheckAdmin)
|
||||
|
||||
class PreapprovalAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
admin.site.register(Preapproval, PreapprovalAdmin)
|
||||
|
|
171
ietf/submit/checkers.py
Normal file
171
ietf/submit/checkers.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
# Copyright The IETF Trust 2016, All Rights Reserved
|
||||
|
||||
import os
|
||||
import re
|
||||
from xym import xym
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.log import log
|
||||
|
||||
class DraftSubmissionChecker():
|
||||
name = ""
|
||||
|
||||
def check_file_txt(self, text):
|
||||
"Run checks on a text file"
|
||||
raise NotImplementedError
|
||||
|
||||
def check_file_xml(self, xml):
|
||||
"Run checks on an xml file"
|
||||
raise NotImplementedError
|
||||
|
||||
def check_fragment_txt(self, text):
|
||||
"Run checks on a fragment from a text file"
|
||||
raise NotImplementedError
|
||||
|
||||
def check_fragment_xml(self, xml):
|
||||
"Run checks on a fragment from an xml file"
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DraftIdnitsChecker(object):
|
||||
"""
|
||||
Draft checker class for idnits. Idnits can only handle whole text files,
|
||||
so only check_file_txt() is defined; check_file_xml and check_fragment_*
|
||||
methods are undefined.
|
||||
|
||||
Furthermore, idnits doesn't provide an error code or line-by-line errors,
|
||||
so a bit of massage is needed in order to return the expected failure flag.
|
||||
"""
|
||||
name = "idnits check"
|
||||
|
||||
def check_file_txt(self, path):
|
||||
"""
|
||||
Run an idnits check, and return a passed/failed indication, a message,
|
||||
and error and warning messages.
|
||||
|
||||
Error and warning list items are tuples:
|
||||
(line_number, line_text, message)
|
||||
"""
|
||||
filename = os.path.basename(path)
|
||||
result = {}
|
||||
items = []
|
||||
errors = 0
|
||||
warnings = 0
|
||||
errstart = [' ** ', ' ~~ ']
|
||||
warnstart = [' == ', ' -- ']
|
||||
|
||||
|
||||
cmd = "%s --submitcheck --nitcount %s" % (settings.IDSUBMIT_IDNITS_BINARY, path)
|
||||
code, out, err = pipe(cmd)
|
||||
if code != 0 or out == "":
|
||||
message = "idnits error: %s:\n Error %s: %s" %( cmd, code, err)
|
||||
log(message)
|
||||
passed = False
|
||||
|
||||
else:
|
||||
message = out
|
||||
if re.search("\s+Summary:\s+0\s+|No nits found", out):
|
||||
passed = True
|
||||
else:
|
||||
passed = False
|
||||
|
||||
item = None
|
||||
for line in message.splitlines():
|
||||
if line[:5] in (errstart + warnstart):
|
||||
item = line.rstrip()
|
||||
elif line.strip() == "" and item:
|
||||
tuple = (None, None, item)
|
||||
items.append(tuple)
|
||||
if item[:5] in errstart:
|
||||
errors += 1
|
||||
elif item[:5] in warnstart:
|
||||
warnings += 1
|
||||
else:
|
||||
raise RuntimeError("Unexpected state in idnits checker: item: %s, line: %s" % (item, line))
|
||||
item = None
|
||||
elif item and line.strip() != "":
|
||||
item += " " + line.strip()
|
||||
else:
|
||||
pass
|
||||
result[filename] = {
|
||||
"passed": passed,
|
||||
"message": message,
|
||||
"errors": errors,
|
||||
"warnings":warnings,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
|
||||
return passed, message, errors, warnings, result
|
||||
|
||||
class DraftYangChecker(object):
|
||||
|
||||
name = "yang validation"
|
||||
|
||||
def check_file_txt(self, path):
|
||||
name = os.path.basename(path)
|
||||
workdir = tempfile.mkdtemp()
|
||||
results = {}
|
||||
|
||||
extractor = xym.YangModuleExtractor(path, workdir, strict=True, debug_level = 0)
|
||||
with open(path) as file:
|
||||
try:
|
||||
# This places the yang models as files in workdir
|
||||
extractor.extract_yang_model(file.readlines())
|
||||
model_list = extractor.get_extracted_models()
|
||||
except Exception as exc:
|
||||
passed = False
|
||||
message = exc
|
||||
errors = [ (name, None, None, exc) ]
|
||||
warnings = []
|
||||
return passed, message, errors, warnings
|
||||
|
||||
for model in model_list:
|
||||
path = os.path.join(workdir, model)
|
||||
with open(path) as file:
|
||||
text = file.readlines()
|
||||
cmd = settings.IDSUBMIT_PYANG_COMMAND % {"workdir": workdir, "model": path, }
|
||||
code, out, err = pipe(cmd)
|
||||
errors = 0
|
||||
warnings = 0
|
||||
items = []
|
||||
if code > 0:
|
||||
error_lines = err.splitlines()
|
||||
for line in error_lines:
|
||||
fn, lnum, msg = line.split(':', 2)
|
||||
lnum = int(lnum)
|
||||
line = text[lnum-1].rstrip()
|
||||
items.append((lnum, line, msg))
|
||||
if 'error: ' in msg:
|
||||
errors += 1
|
||||
if 'warning: ' in msg:
|
||||
warnings += 1
|
||||
results[model] = {
|
||||
"passed": code == 0,
|
||||
"message": out+"No validation errors\n" if code == 0 else err,
|
||||
"warnings": warnings,
|
||||
"errors": errors,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
shutil.rmtree(workdir)
|
||||
|
||||
## For now, never fail because of failed yang validation.
|
||||
if len(model_list):
|
||||
passed = True
|
||||
else:
|
||||
passed = None
|
||||
#passed = all( res["passed"] for res in results.values() )
|
||||
message = "\n\n".join([ "\n".join([model+':', res["message"]]) for model, res in results.items() ])
|
||||
errors = sum(res["errors"] for res in results.values() )
|
||||
warnings = sum(res["warnings"] for res in results.values() )
|
||||
items = [ e for res in results.values() for e in res["items"] ]
|
||||
|
||||
return passed, message, errors, warnings, items
|
||||
|
31
ietf/submit/migrations/0004_submissioncheck.py
Normal file
31
ietf/submit/migrations/0004_submissioncheck.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import jsonfield
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submit', '0003_auto_20150713_1104'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SubmissionCheck',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('time', models.DateTimeField(default=None, auto_now=True)),
|
||||
('checker', models.CharField(max_length=256, blank=True)),
|
||||
('passed', models.NullBooleanField(default=False)),
|
||||
('message', models.TextField(null=True, blank=True)),
|
||||
('warnings', models.IntegerField(null=True, blank=True, default=None)),
|
||||
('errors', models.IntegerField(null=True, blank=True, default=None)),
|
||||
('items', jsonfield.JSONField(null=True, blank=True, default=b'{}')),
|
||||
('submission', models.ForeignKey(related_name='checks', to='submit.Submission')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
31
ietf/submit/migrations/0005_auto_20160227_0809.py
Normal file
31
ietf/submit/migrations/0005_auto_20160227_0809.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def convert_to_submission_check(apps, schema_editor):
|
||||
Submission = apps.get_model('submit','Submission')
|
||||
SubmissionCheck = apps.get_model('submit','SubmissionCheck')
|
||||
for s in Submission.objects.all():
|
||||
passed = re.search('\s+Summary:\s+0\s+|No nits found', s.idnits_message) != None
|
||||
c = SubmissionCheck(submission=s, checker='idnits check', passed=passed, message=s.idnits_message)
|
||||
c.save()
|
||||
|
||||
def convert_from_submission_check(apps, schema_editor):
|
||||
SubmissionCheck = apps.get_model('submit','SubmissionCheck')
|
||||
for c in SubmissionCheck.objects.filter(checker='idnits check'):
|
||||
c.submission.idnits_message = c.message
|
||||
c.save()
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submit', '0004_submissioncheck'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(convert_to_submission_check, convert_from_submission_check)
|
||||
]
|
|
@ -2,6 +2,7 @@ import re
|
|||
import datetime
|
||||
|
||||
from django.db import models
|
||||
import jsonfield
|
||||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.person.models import Person
|
||||
|
@ -66,6 +67,23 @@ class Submission(models.Model):
|
|||
def existing_document(self):
|
||||
return Document.objects.filter(name=self.name).first()
|
||||
|
||||
class SubmissionCheck(models.Model):
|
||||
time = models.DateTimeField(auto_now=True, default=None) # The default is to make makemigrations happy
|
||||
submission = models.ForeignKey(Submission, related_name='checks')
|
||||
checker = models.CharField(max_length=256, blank=True)
|
||||
passed = models.NullBooleanField(default=False)
|
||||
message = models.TextField(null=True, blank=True)
|
||||
errors = models.IntegerField(null=True, blank=True, default=None)
|
||||
warnings = models.IntegerField(null=True, blank=True, default=None)
|
||||
items = jsonfield.JSONField(null=True, blank=True, default='{}')
|
||||
#
|
||||
def __unicode__(self):
|
||||
return "%s submission check: %s: %s" % (self.checker, 'Passed' if self.passed else 'Failed', self.message[:48]+'...')
|
||||
def has_warnings(self):
|
||||
return self.warnings != '[]'
|
||||
def has_errors(self):
|
||||
return self.errors != '[]'
|
||||
|
||||
class SubmissionEvent(models.Model):
|
||||
submission = models.ForeignKey(Submission)
|
||||
time = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Autogenerated by the mkresources management command 2014-11-13 23:53
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.fields import ToOneField
|
||||
from tastypie.fields import ToOneField, ToManyField
|
||||
from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
||||
|
||||
from ietf import api
|
||||
|
@ -28,6 +28,7 @@ from ietf.name.resources import DraftSubmissionStateNameResource
|
|||
class SubmissionResource(ModelResource):
|
||||
state = ToOneField(DraftSubmissionStateNameResource, 'state')
|
||||
group = ToOneField(GroupResource, 'group', null=True)
|
||||
checks = ToManyField(SubmissionCheck, 'checks', null=True)
|
||||
class Meta:
|
||||
queryset = Submission.objects.all()
|
||||
serializer = api.Serializer()
|
||||
|
@ -51,7 +52,6 @@ class SubmissionResource(ModelResource):
|
|||
"document_date": ALL,
|
||||
"submission_date": ALL,
|
||||
"submitter": ALL,
|
||||
"idnits_message": ALL,
|
||||
"state": ALL_WITH_RELATIONS,
|
||||
"group": ALL_WITH_RELATIONS,
|
||||
}
|
||||
|
@ -74,3 +74,19 @@ class SubmissionEventResource(ModelResource):
|
|||
}
|
||||
api.submit.register(SubmissionEventResource())
|
||||
|
||||
class SubmissionCheckResource(ModelResource):
|
||||
submission = ToOneField(SubmissionResource, 'submission')
|
||||
class Meta:
|
||||
queryset = SubmissionCheck.objects.all()
|
||||
serializer = api.Serializer()
|
||||
#resource_name = 'submissioncheck'
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"checker": ALL,
|
||||
"passed": ALL,
|
||||
"warning": ALL,
|
||||
"message": ALL,
|
||||
"errors": ALL,
|
||||
"submission": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.submit.register(SubmissionCheckResource())
|
||||
|
|
|
@ -65,15 +65,115 @@ Table of Contents
|
|||
3. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 2
|
||||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
|
||||
1. Introduction
|
||||
1. Introduction
|
||||
|
||||
This document describes a protocol for testing tests.
|
||||
|
||||
2. Security Considerations
|
||||
2. Yang
|
||||
|
||||
<CODE BEGINS> file "ietf-mpls@2015-10-16.yang"
|
||||
|
||||
module ietf-mpls {
|
||||
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-mpls";
|
||||
|
||||
prefix "mpls";
|
||||
|
||||
import ietf-routing {
|
||||
prefix "rt";
|
||||
}
|
||||
|
||||
import ietf-interfaces {
|
||||
prefix "if";
|
||||
}
|
||||
|
||||
organization "TBD";
|
||||
|
||||
contact "TBD";
|
||||
|
||||
description
|
||||
"This YANG module defines the essential components for the
|
||||
management of the MPLS subsystem.";
|
||||
|
||||
revision "2015-10-16" {
|
||||
description
|
||||
"Initial revision";
|
||||
reference "RFC 3031: A YANG Data Model for base MPLS";
|
||||
}
|
||||
|
||||
typedef mpls-label {
|
||||
type uint32 {
|
||||
range "0..1048575";
|
||||
}
|
||||
description
|
||||
"The MPLS label range";
|
||||
}
|
||||
|
||||
typedef percent {
|
||||
type uint16 {
|
||||
range "0 .. 100";
|
||||
}
|
||||
description "Percentage";
|
||||
}
|
||||
|
||||
grouping interface-mpls {
|
||||
description "MPLS interface properties grouping";
|
||||
leaf enabled {
|
||||
type boolean;
|
||||
description
|
||||
"'true' if mpls encapsulation is enabled on the
|
||||
interface. 'false' if mpls encapsulation is enabled
|
||||
on the interface.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing/rt:routing-instance" {
|
||||
description "MPLS augmentation.";
|
||||
container mpls {
|
||||
description
|
||||
"MPLS container, to be used as an augmentation target node
|
||||
other MPLS sub-features config, e.g. MPLS static LSP, MPLS
|
||||
LDP LSPs, and Trafic Engineering MPLS LSP Tunnels, etc.";
|
||||
|
||||
list interface {
|
||||
key "name";
|
||||
description "List of MPLS interfaces";
|
||||
leaf name {
|
||||
type if:interface-ref;
|
||||
description
|
||||
"The name of a configured MPLS interface";
|
||||
}
|
||||
container config {
|
||||
description "Holds intended configuration";
|
||||
uses interface-mpls;
|
||||
}
|
||||
container state {
|
||||
config false;
|
||||
description "Holds inuse configuration";
|
||||
uses interface-mpls;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:routing-instance" {
|
||||
description "MPLS augmentation.";
|
||||
container mpls {
|
||||
config false;
|
||||
description
|
||||
"MPLS container, to be used as an augmentation target node
|
||||
other MPLS sub-features state";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<CODE ENDS>
|
||||
|
||||
3. Security Considerations
|
||||
|
||||
There are none.
|
||||
|
||||
3. IANA Considerations
|
||||
4. IANA Considerations
|
||||
|
||||
No new registrations for IANA.
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<workgroup>%(group)s</workgroup>
|
||||
<abstract>
|
||||
<t>
|
||||
This document describes how to test tests.
|
||||
This document describes how to test tests.
|
||||
</t>
|
||||
</abstract>
|
||||
</front>
|
||||
|
@ -28,17 +28,123 @@
|
|||
<middle>
|
||||
<section title="Introduction">
|
||||
<t>
|
||||
This document describes a protocol for testing tests.
|
||||
This document describes a protocol for testing tests.
|
||||
</t>
|
||||
</section>
|
||||
<section title="Yang">
|
||||
<figure>
|
||||
<artwork>
|
||||
<![CDATA[
|
||||
<CODE BEGINS> file "ietf-mpls@2015-10-16.yang"
|
||||
|
||||
module ietf-mpls {
|
||||
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-mpls";
|
||||
|
||||
prefix "mpls";
|
||||
|
||||
import ietf-routing {
|
||||
prefix "rt";
|
||||
}
|
||||
|
||||
import ietf-interfaces {
|
||||
prefix "if";
|
||||
}
|
||||
|
||||
organization "TBD";
|
||||
|
||||
contact "TBD";
|
||||
|
||||
description
|
||||
"This YANG module defines the essential components for the
|
||||
management of the MPLS subsystem.";
|
||||
|
||||
revision "2015-10-16" {
|
||||
description
|
||||
"Initial revision";
|
||||
reference "RFC 3031: A YANG Data Model for base MPLS";
|
||||
}
|
||||
|
||||
typedef mpls-label {
|
||||
type uint32 {
|
||||
range "0..1048575";
|
||||
}
|
||||
description
|
||||
"The MPLS label range";
|
||||
}
|
||||
|
||||
typedef percent {
|
||||
type uint16 {
|
||||
range "0 .. 100";
|
||||
}
|
||||
description "Percentage";
|
||||
}
|
||||
|
||||
grouping interface-mpls {
|
||||
description "MPLS interface properties grouping";
|
||||
leaf enabled {
|
||||
type boolean;
|
||||
description
|
||||
"'true' if mpls encapsulation is enabled on the
|
||||
interface. 'false' if mpls encapsulation is enabled
|
||||
on the interface.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing/rt:routing-instance" {
|
||||
description "MPLS augmentation.";
|
||||
container mpls {
|
||||
description
|
||||
"MPLS container, to be used as an augmentation target node
|
||||
other MPLS sub-features config, e.g. MPLS static LSP, MPLS
|
||||
LDP LSPs, and Trafic Engineering MPLS LSP Tunnels, etc.";
|
||||
|
||||
list interface {
|
||||
key "name";
|
||||
description "List of MPLS interfaces";
|
||||
leaf name {
|
||||
type if:interface-ref;
|
||||
description
|
||||
"The name of a configured MPLS interface";
|
||||
}
|
||||
container config {
|
||||
description "Holds intended configuration";
|
||||
uses interface-mpls;
|
||||
}
|
||||
container state {
|
||||
config false;
|
||||
description "Holds inuse configuration";
|
||||
uses interface-mpls;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:routing-instance" {
|
||||
description "MPLS augmentation.";
|
||||
container mpls {
|
||||
config false;
|
||||
description
|
||||
"MPLS container, to be used as an augmentation target node
|
||||
other MPLS sub-features state";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<CODE ENDS>
|
||||
]]>
|
||||
</artwork>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section anchor="Security" title="Security Considerations">
|
||||
<t>
|
||||
There are none.
|
||||
There are none.
|
||||
</t>
|
||||
</section>
|
||||
<section anchor="IANA" title="IANA Considerations">
|
||||
<t>
|
||||
No new registrations for IANA.
|
||||
No new registrations for IANA.
|
||||
</t>
|
||||
</section>
|
||||
</middle>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -98,7 +97,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, u"%s-%s.%s" % (name, rev, format))))
|
||||
self.assertEqual(Submission.objects.filter(name=name).count(), 1)
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertTrue(re.search('\s+Summary:\s+0\s+errors|No nits found', submission.idnits_message))
|
||||
self.assertTrue(all([ c.passed!=False for c in submission.checks.all() ]))
|
||||
self.assertEqual(len(submission.authors_parsed()), 1)
|
||||
author = submission.authors_parsed()[0]
|
||||
self.assertEqual(author["name"], "Author Name")
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -18,23 +17,6 @@ from ietf.submit.mail import announce_to_lists, announce_new_version, announce_t
|
|||
from ietf.submit.models import Submission, SubmissionEvent, Preapproval, DraftSubmissionStateName
|
||||
from ietf.utils import unaccent
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.pipe import pipe
|
||||
|
||||
def check_idnits(path):
|
||||
#p = subprocess.Popen([self.idnits, '--submitcheck', '--nitcount', path], stdout=subprocess.PIPE)
|
||||
cmd = "%s --submitcheck --nitcount %s" % (settings.IDSUBMIT_IDNITS_BINARY, path)
|
||||
code, out, err = pipe(cmd)
|
||||
if code != 0:
|
||||
log("idnits error: %s:\n Error %s: %s" %( cmd, code, err))
|
||||
return out
|
||||
|
||||
def found_idnits(idnits_message):
|
||||
if not idnits_message:
|
||||
return False
|
||||
success_re = re.compile('\s+Summary:\s+0\s+|No nits found')
|
||||
if success_re.search(idnits_message):
|
||||
return True
|
||||
return False
|
||||
|
||||
def validate_submission(submission):
|
||||
errors = {}
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
from django.core.validators import validate_email, ValidationError
|
||||
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -17,9 +18,9 @@ from ietf.group.models import Group
|
|||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.submit.forms import SubmissionUploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm, ReplacesForm
|
||||
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, send_manual_post_request
|
||||
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName
|
||||
from ietf.submit.models import Submission, SubmissionCheck, Preapproval, DraftSubmissionStateName
|
||||
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
|
||||
from ietf.submit.utils import check_idnits, found_idnits, validate_submission, create_submission_event
|
||||
from ietf.submit.utils import validate_submission, create_submission_event
|
||||
from ietf.submit.utils import post_submission, cancel_submission, rename_submission_files
|
||||
from ietf.utils.accesstoken import generate_random_key, generate_access_token
|
||||
from ietf.utils.draft import Draft
|
||||
|
@ -92,9 +93,6 @@ def upload_submission(request):
|
|||
else:
|
||||
abstract = form.parsed_draft.get_abstract()
|
||||
|
||||
# check idnits
|
||||
idnits_message = check_idnits(file_name['txt'])
|
||||
|
||||
# save submission
|
||||
try:
|
||||
submission = Submission.objects.create(
|
||||
|
@ -114,12 +112,28 @@ def upload_submission(request):
|
|||
submission_date=datetime.date.today(),
|
||||
document_date=form.parsed_draft.get_creation_date(),
|
||||
replaces="",
|
||||
idnits_message=idnits_message,
|
||||
)
|
||||
except Exception as e:
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
|
||||
# run submission checkers
|
||||
def apply_check(submission, checker, method, fn):
|
||||
func = getattr(checker, method)
|
||||
passed, message, errors, warnings, items = func(fn)
|
||||
check = SubmissionCheck(submission=submission, checker=checker.name, passed=passed, message=message, errors=errors, warnings=warnings, items=items)
|
||||
check.save()
|
||||
|
||||
for checker_path in settings.IDSUBMIT_CHECKER_CLASSES:
|
||||
checker_class = import_string(checker_path)
|
||||
checker = checker_class()
|
||||
# ordered list of methods to try
|
||||
for method in ("check_fragment_xml", "check_file_xml", "check_fragment_txt", "check_file_txt", ):
|
||||
ext = method[-3:]
|
||||
if hasattr(checker, method) and ext in file_name:
|
||||
apply_check(submission, checker, method, file_name[ext])
|
||||
break
|
||||
|
||||
create_submission_event(request, submission, desc="Uploaded submission")
|
||||
|
||||
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=submission.access_token())
|
||||
|
@ -175,7 +189,7 @@ def submission_status(request, submission_id, access_token=None):
|
|||
raise Http404
|
||||
|
||||
errors = validate_submission(submission)
|
||||
passes_idnits = found_idnits(submission.idnits_message)
|
||||
passes_checks = all([ c.passed!=False for c in submission.checks.all() ])
|
||||
|
||||
is_secretariat = has_role(request.user, "Secretariat")
|
||||
is_chair = submission.group and submission.group.has_role(request.user, "chair")
|
||||
|
@ -316,7 +330,7 @@ def submission_status(request, submission_id, access_token=None):
|
|||
'selected': 'status',
|
||||
'submission': submission,
|
||||
'errors': errors,
|
||||
'passes_idnits': passes_idnits,
|
||||
'passes_checks': passes_checks,
|
||||
'submitter_form': submitter_form,
|
||||
'replaces_form': replaces_form,
|
||||
'message': message,
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
<div class='alert alert-warning'>
|
||||
{% for item in help_text_and_errors %} {{ item }}<br> {% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -37,32 +37,52 @@
|
|||
<p class="alert alert-danger">Please fix errors in the form below.</p>
|
||||
{% endif %}
|
||||
|
||||
<h2>I-D nits</h2>
|
||||
<h2>Submission checks</h2>
|
||||
<p>
|
||||
{% if passes_idnits %}
|
||||
Your draft has been verified to meet I-D nits requirements.
|
||||
{% if passes_checks %}
|
||||
Your draft has been verified to pass the submission checks.
|
||||
{% else %}
|
||||
Your draft has <b>NOT</b> been verified to meet I-D nits requirements.
|
||||
Your draft has <b>NOT</b> been verified to pass the submission checks.
|
||||
{% endif %}
|
||||
</p>
|
||||
<button class="btn btn-default" data-toggle="modal" data-target="#nits">View I-D nits</button>
|
||||
{% for check in submission.checks.all %}
|
||||
{% if check.errors %}
|
||||
<p class="alert alert-warning">
|
||||
The {{check.checker}} returned {{ check.errors }} error{{ check.errors|pluralize }}
|
||||
and {{ check.warnings }} warning
|
||||
|
||||
<div class="modal fade" id="nits" tabindex="-1" role="dialog" aria-labelledby="nits" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="nitslabel">I-D nits for {{ submission.name }}-{{ submission.rev }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre>{{ submission.idnits_message }}</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
{{ check.warnings|pluralize }} ; click the button
|
||||
below to see details. Please fix those, and resubmit.
|
||||
</p>
|
||||
{% elif check.warnings %}
|
||||
<p class="alert alert-warning">
|
||||
The {{check.checker}} returned {{ check.warnings }} warning{{ check.warnings|pluralize }}.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for check in submission.checks.all %}
|
||||
{% if check.passed != None %}
|
||||
<button class="btn btn-{% if check.passed %}{% if check.warnings %}warning{% elif check.errors %}warning{% else %}success{% endif %}{% else %}danger{% endif %}" data-toggle="modal" data-target="#check-{{check.pk}}">View {{ check.checker }}</button>
|
||||
|
||||
<div class="modal fade" id="check-{{check.pk}}" tabindex="-1" role="dialog" aria-labelledby="check-{{check.pk}}" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="nitslabel">{{ check.checker|title }} for {{ submission.name }}-{{ submission.rev }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre>{{ check.message }}</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="modal fade" id="twopages" tabindex="-1" role="dialog" aria-labelledby="twopageslabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
|
@ -218,7 +238,7 @@
|
|||
</form>
|
||||
<p>Leads to manual post by the secretariat.</p>
|
||||
|
||||
{% if passes_idnits and not errors %}
|
||||
{% if passes_checks and not errors %}
|
||||
<h2>Please edit the following meta-data before posting:</h2>
|
||||
|
||||
<form class="idsubmit" method="post">
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
|
|
|
@ -16,6 +16,7 @@ class AsyncCoreLoopThread(object):
|
|||
kwargs={'exit_condition':self.exit_condition,'timeout':1.0}
|
||||
self.thread = threading.Thread(target=self.wrap_loop, kwargs=kwargs)
|
||||
self.thread.daemon = True
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
|
|
|
@ -295,4 +295,3 @@ class TestCase(django.test.TestCase):
|
|||
self.assertTrue(resp['Content-Type'].startswith('text/html'))
|
||||
self.assertValidHTML(resp.content)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue