aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Elkins <melkins@tislabs.com>2012-11-14 23:36:21 +0000
committerMichael Elkins <melkins@tislabs.com>2012-11-14 23:36:21 +0000
commit01290083a9bb24091295e87d5f3a414cb8186422 (patch)
treefea8e536f823269021fd3e4173ffd6cf04f1b1d6
parentd77f7e5696aa3727cc4c9c4ab18e78d575e12b92 (diff)
merge with /trunk
svn path=/branches/tk274/; revision=4866
-rw-r--r--doc/doc.RPKI.CA.UI16
-rw-r--r--doc/doc.RPKI.CA.UI.GUI52
-rw-r--r--doc/doc.RPKI.Installation8
-rw-r--r--doc/manual.pdfbin485324 -> 483113 bytes
-rw-r--r--rpkid/portal-gui/rpki.wsgi.in16
-rw-r--r--rpkid/portal-gui/scripts/rpkigui-rcynic.py23
-rw-r--r--rpkid/portal-gui/settings.py.in3
-rw-r--r--rpkid/rpki/gui/app/forms.py55
-rw-r--r--rpkid/rpki/gui/app/templates/app/child_add_resource_form.html16
-rw-r--r--rpkid/rpki/gui/app/templates/app/object_detail.html76
-rw-r--r--rpkid/rpki/gui/app/views.py121
11 files changed, 210 insertions, 176 deletions
diff --git a/doc/doc.RPKI.CA.UI b/doc/doc.RPKI.CA.UI
index 0643f9d0..ac180a39 100644
--- a/doc/doc.RPKI.CA.UI
+++ b/doc/doc.RPKI.CA.UI
@@ -85,20 +85,22 @@ the same:
home underneath your parent in the publication tree.
6. Each of your parents sends (...) back the response XML file generated by
the "configure_child" command.
- 7. You feed the response message you just got into the IRBE using either
- rpkic or the GUI rpkic using the. This registers the parent's information
- in your database, handles BPKI cross-certification of your parent., and
+ 7. You feed the response message you just got into the IRBE using rpkic's
+ "configure_parent" command. This registers the parent's information in
+ your database, handles BPKI cross-certification of your parent., and
processes the repository offer or referral to generate a publication
request message.
8. You send (...) the publication request message to the repository. The
contact_info element in the request message should (in theory) provide
some clue as to where you should send this.
- 9. The repository operator processes your request. This registers your
- information, including BPKI cross-certification, and generates a response
- message containing the repository's BPKI trust anchor and service URL.
+ 9. The repository operator processes your request using rpkic's
+ "configure_publication_client" command. This registers your information,
+ including BPKI cross-certification, and generates a response message
+ containing the repository's BPKI trust anchor and service URL.
10. Repository operator sends (...) the publication confirmation message back
to you.
- 11. You process the publication confirmation message.
+ 11. You process the publication confirmation message using rpkic's
+ "configure_repository" command.
At this point you should, in theory, have established relationships, exchanged
trust anchors, and obtained service URLs from all of your parents and
diff --git a/doc/doc.RPKI.CA.UI.GUI b/doc/doc.RPKI.CA.UI.GUI
index d52e0a6b..109890d6 100644
--- a/doc/doc.RPKI.CA.UI.GUI
+++ b/doc/doc.RPKI.CA.UI.GUI
@@ -42,6 +42,9 @@ Now bring your database up to date with the current release:
$ rpki-manage migrate app
+From this point on you will just need to run the latter command every time you
+upgrade.
+
***** New Installation *****
**** Create the initial tables ****
@@ -122,55 +125,6 @@ In addition, your rcynic script should also have
after the rcynic run.
-****** GUI Installation to Work With rootd ******
-
-Some of the commands depend on whether your are upgrading your existing
-database, or starting a new installation from scratch.
-
-[All users] First step is you will need to install Django South. For FreeBSD
-this is /usr/ports/databases/py-south.
-
-The code is currently in the tk316 branch, so in order to play, you will need
-to check it out:
-
- $ svn co https://subvert-rpki.hactrn.net/branches/tk316
- $ cd tk316
- $ ./configure
- $ make
- $ make install
-
-[Upgrading users] You will need to edit /usr/local/etc/rpki/settings.py and add
-'south' to the INSTALLED_APPS list. See /usr/local/etc/rpki/settings.py.new for
-an example (we don't automatically overwrite settings.py).
-
-[All users] Run syncdb:
-
- $ django-admin syncdb --pythonpath=/usr/local/etc/rpki --settings=settings
-
-Verify that Django South is installed:
-
- $ django-admin migrate --list --pythonpath=/usr/local/etc/rpki --
- settings=settings
-
-[Upgrading Users] Since you already have an existing db, you need to fake doing
-the initial migration step:
-
- $ django-admin migrate app 0001 --fake --pythonpath=/usr/local/etc/rpki --
- settings=settings
-
-[All users] Perform the database migrations new to this release:
-
- $ django-admin migrate app --pythonpath=/usr/local/etc/rpki --
- settings=settings
-
-[All users] Restart apache so that the web portal picks up the newly installed
-code:
-
- $ apachectl restart
-
-Now head back to the gui. Click on the 'refresh' link when viewing the altCA
-dashboard, and it should now pick up the resources from the root cert.
-
****** Using the GUI ******
****** GUI Examples ******
diff --git a/doc/doc.RPKI.Installation b/doc/doc.RPKI.Installation
index 3fd3f8cf..20f42c2c 100644
--- a/doc/doc.RPKI.Installation
+++ b/doc/doc.RPKI.Installation
@@ -119,12 +119,12 @@ Packages you will need:
o FreeBSD: /usr/ports/www/mod_wsgi3
-* http://south.aeracode.org/ Django South. This tool is used to ease the pain
- of changes to the web portal database schema.
+* http://south.aeracode.org/ Django South 0.7.6 or later. This tool is used to
+ ease the pain of changes to the web portal database schema.
o FreeBSD: /usr/ports/databases/py-south
- o Ubuntu: python-django-south
- o CentOS/RHEL: Django-south
+ o Ubuntu: do not use the python-django-south 0.7.3 package in 12.04 LTS as it
+ is known not to work
***** Configure and build *****
diff --git a/doc/manual.pdf b/doc/manual.pdf
index a01fdcaf..89bbebd5 100644
--- a/doc/manual.pdf
+++ b/doc/manual.pdf
Binary files differ
diff --git a/rpkid/portal-gui/rpki.wsgi.in b/rpkid/portal-gui/rpki.wsgi.in
index eb49fe05..3d198349 100644
--- a/rpkid/portal-gui/rpki.wsgi.in
+++ b/rpkid/portal-gui/rpki.wsgi.in
@@ -25,16 +25,22 @@ import sys
old_sys_path = list(sys.path)
+def walk_error(e):
+ 'This function is invoked when os.walk() needs to report an error'
+ print >>sys.stderr, 'error reading %s: %s' % (e.filename, e)
+
# When used with virtualenv, specify the location of the python modules to use
if VIRTUAL_ENV:
- import site
+ d = os.path.join(VIRTUAL_ENV, 'lib')
# locate the site-packages directory
- for (dp, dn, fn) in os.walk(VIRTUAL_ENV + '/lib'):
+ for dp, dn, fn in os.walk(os.path.join(VIRTUAL_ENV, 'lib'),
+ onerror=walk_error):
if 'site-packages' in dn:
- site.addsitedir(os.path.join(dp, 'site-packages'))
+ d = os.path.join(dp, 'site-packages')
+ import site
+ site.addsitedir(d)
break
-import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
sys.path.insert(1, '@PYTHONPATH@')
@@ -48,6 +54,8 @@ for elt in list(sys.path):
sys.path.remove(elt)
sys.path[:0] = new_sys_path
+print >>sys.stderr, sys.path
+
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/rpkid/portal-gui/scripts/rpkigui-rcynic.py b/rpkid/portal-gui/scripts/rpkigui-rcynic.py
index 439a091a..b7f6c661 100644
--- a/rpkid/portal-gui/scripts/rpkigui-rcynic.py
+++ b/rpkid/portal-gui/scripts/rpkigui-rcynic.py
@@ -98,7 +98,6 @@ def rcynic_roa(roa, obj):
def rcynic_gbr(gbr, obj):
vcard = vobject.readOne(gbr.vcard)
- logger.debug(vcard.prettyPrint())
obj.full_name = vcard.fn.value if hasattr(vcard, 'fn') else None
obj.email_address = vcard.email.value if hasattr(vcard, 'email') else None
obj.telephone = vcard.tel.value if hasattr(vcard, 'tel') else None
@@ -123,21 +122,21 @@ def save_statuses(inst, statuses):
# if this object is in our interest set, update with the current validation
# status
if inst.uri in uris:
- x, y, z, q = uris[inst.repo.uri]
+ x, y, z, q = uris[inst.uri]
uris[inst.uri] = x, y, valid, inst
@transaction.commit_on_success
def process_cache(root, xml_file):
dispatch = {
- 'rcynic_certificate': rcynic_cert,
- 'rcynic_roa': rcynic_roa,
- 'rcynic_ghostbuster': rcynic_gbr
+ 'rcynic_certificate': rcynic_cert,
+ 'rcynic_roa': rcynic_roa,
+ 'rcynic_ghostbuster': rcynic_gbr
}
model_class = {
- 'rcynic_certificate': models.Cert,
- 'rcynic_roa': models.ROA,
- 'rcynic_ghostbuster': models.Ghostbuster
+ 'rcynic_certificate': models.Cert,
+ 'rcynic_roa': models.ROA,
+ 'rcynic_ghostbuster': models.Ghostbuster
}
last_uri = None
@@ -176,7 +175,8 @@ def process_cache(root, xml_file):
try:
obj = vs.obj # causes object to be lazily loaded
except rpki.POW._der.DerError, e:
- logger.warning('Caught %s while processing %s: %s' % (type(e), vs.filename, e))
+ logger.warning('Caught %s while processing %s: %s' % (
+ type(e), vs.filename, e))
continue
inst.not_before = obj.notBefore.to_sql()
@@ -283,9 +283,9 @@ def fetch_published_objects():
qs = models.RepositoryObject.objects.filter(uri=pdu.uri)
if qs:
# get the current validity state
- valid = obj.statuses.filter(status=object_accepted).exists()
+ valid = qs[0].statuses.filter(status=object_accepted).exists()
uris[pdu.uri] = (pdu.self_handle, valid, False, None)
- logger.debug('adding ' + ', '.join(uris[pdu.uri]))
+ logger.debug('adding ' + pdu.uri)
else:
# this object is not in the cache. it was either published
# recently, or disappared previously. if it disappeared
@@ -394,6 +394,7 @@ if __name__ == '__main__':
start = time.time()
process_labels(options.logfile)
object_accepted = LABEL_CACHE['object_accepted']
+ fetch_published_objects()
process_cache(options.root, options.logfile)
notify_invalid()
diff --git a/rpkid/portal-gui/settings.py.in b/rpkid/portal-gui/settings.py.in
index 10705ef8..46f6ca60 100644
--- a/rpkid/portal-gui/settings.py.in
+++ b/rpkid/portal-gui/settings.py.in
@@ -86,12 +86,13 @@ INSTALLED_APPS = (
#'django.contrib.admin',
#'django.contrib.admindocs',
'django.contrib.contenttypes',
+ 'django.contrib.formtools',
'django.contrib.sessions',
'rpki.irdb',
'rpki.gui.app',
'rpki.gui.cacheview',
'rpki.gui.routeview',
- 'south'
+ 'south',
)
TEMPLATE_CONTEXT_PROCESSORS = (
diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py
index 7f7be61d..2246a03b 100644
--- a/rpkid/rpki/gui/app/forms.py
+++ b/rpkid/rpki/gui/app/forms.py
@@ -278,14 +278,13 @@ class ROARequestConfirm(forms.Form):
return self.cleaned_data
-def AddASNForm(qs):
+def AddASNForm(child):
"""
- Generate a form class which only allows specification of ASNs contained
- within the specified queryset. `qs` should be a QuerySet of
- irdb.models.ChildASN.
+ Returns a forms.Form subclass which verifies that the entered ASN range
+ does not overlap with a previous allocation to the specified child, and
+ that the ASN range is within the range allocated to the parent.
"""
-
class _wrapped(forms.Form):
asns = forms.CharField(label='ASNs', help_text='single ASN or range')
@@ -294,21 +293,30 @@ def AddASNForm(qs):
r = resource_range_as.parse_str(self.cleaned_data.get('asns'))
except:
raise forms.ValidationError('invalid AS or range')
- if not qs.filter(min__lte=r.min, max__gte=r.max).exists():
+
+ if not models.ResourceRangeAS.objects.filter(
+ cert__conf=child.issuer,
+ min__lte=r.min,
+ max__gte=r.max).exists():
raise forms.ValidationError('AS or range is not delegated to you')
+
+ # determine if the entered range overlaps with any AS already
+ # allocated to this child
+ if child.asns.filter(end_as__gte=r.min, start_as__lte=r.max).exists():
+ raise forms.ValidationError(
+ 'Overlap with previous allocation to this child')
+
return str(r)
return _wrapped
-
-def AddNetForm(qsv4, qsv6):
+def AddNetForm(child):
"""
- Generate a form class which only allows specification of prefixes contained
- within the specified queryset. `qs` should be a QuerySet of
- irdb.models.ChildNet.
+ Returns a forms.Form subclass which validates that the entered address
+ range is within the resources allocated to the parent, and does not overlap
+ with what is already allocated to the specified child.
"""
-
class _wrapped(forms.Form):
address_range = forms.CharField(help_text='CIDR or range')
@@ -317,19 +325,32 @@ def AddNetForm(qsv4, qsv6):
try:
if ':' in address_range:
r = resource_range_ipv6.parse_str(address_range)
- if not qsv6.filter(prefix_min__lte=r.min, prefix_max__gte=r.max).exists():
- raise forms.ValidationError('IP address range is not delegated to you')
+ qs = models.ResourceRangeAddressV6
+ version = 'IPv6'
else:
r = resource_range_ipv4.parse_str(address_range)
- if not qsv4.filter(prefix_min__lte=r.min, prefix_max__gte=r.max).exists():
- raise forms.ValidationError('IP address range is not delegated to you')
+ qs = models.ResourceRangeAddressV4
+ version = 'IPv4'
except BadIPResource:
raise forms.ValidationError('invalid IP address range')
+
+ if not qs.objects.filter(cert__conf=child.issuer,
+ prefix_min__lte=r.min,
+ prefix_max__gte=r.max).exists():
+ raise forms.ValidationError('IP address range is not delegated to you')
+
+ # determine if the entered range overlaps with any prefix
+ # already allocated to this child
+ for n in child.address_ranges.filter(version=version):
+ rng = n.as_resource_range()
+ if r.max >= rng.min and r.min <= rng.max:
+ raise forms.ValidationError(
+ 'Overlap with previous allocation to this child')
+
return str(r)
return _wrapped
-
def ChildForm(instance):
"""
Form for editing a Child model.
diff --git a/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html b/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html
deleted file mode 100644
index 98789191..00000000
--- a/rpkid/rpki/gui/app/templates/app/child_add_resource_form.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "app/app_base.html" %}
-
-{% block content %}
-<div class='page-header'>
- <h1>Add Resource: {{ object.handle }}</h1>
-</div>
-
-<form method='POST' action='{{ request.get_full_path }}'>
- {% csrf_token %}
- {% include "app/bootstrap_form.html" %}
- <div class='actions'>
- <input class='btn primary' type='submit' value='Save'>
- <a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a>
- </div>
-</form>
-{% endblock content %}
diff --git a/rpkid/rpki/gui/app/templates/app/object_detail.html b/rpkid/rpki/gui/app/templates/app/object_detail.html
index 6a93f644..3927bfd5 100644
--- a/rpkid/rpki/gui/app/templates/app/object_detail.html
+++ b/rpkid/rpki/gui/app/templates/app/object_detail.html
@@ -10,24 +10,68 @@
{{ object }}
{% endblock object_detail %}
-{% if confirm_delete %}
-<div class='alert-message block-message warning'>
- <p><strong>Please confirm</strong> that you would like to delete this object.
- <div class='alert-actions'>
- <form method='POST' action='{{ request.get_full_path }}'>
- {% csrf_token %}
- <input class='btn danger' type='submit' value='Delete'/>
+{% if form %}
+ <h2>{{ form_label }}<h2>
+
+ {% if is_preview %}
+ <h3>Preview</h3>
+ <table>
+ {% for field in form %}
+ <tr>
+ <th>{{ field.label }}:</th>
+ <td>{{ field.data }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <!-- <p>Security hash: {{ hash_value }}</p> -->
+
+ <!-- this form is used by django.contrib.formtools -->
+ <form action="" method="post">{% csrf_token %}
+ {% for field in form %}{{ field.as_hidden }}
+ {% endfor %}
+ <input type="hidden" name="{{ stage_field }}" value="2" />
+ <input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
+ <div class='actions'>
+ <input class='btn primary' type="submit" value="Save" /></p>
+ </div>
+ </form>
+
+ <h3>Or edit again</h3>
+
+ {% endif %}
+
+ <!-- this form is used by django.contrib.formtools -->
+ <form method='POST' action=''>
+ {% csrf_token %}
+ {% include "app/bootstrap_form.html" %}
+ <div class='actions'>
+ <input class='btn primary' type='submit' value='Preview'>
<a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a>
- </form>
- </div>
-</div>
+ </div>
+ <input type="hidden" name="{{ stage_field }}" value="1" />
+ </form>
+
{% else %}
-<div class='actions'>
- {% if can_edit %}
- <a class='btn' href='{{ object.get_absolute_url }}/edit'>Edit</a>
+ {% if confirm_delete %}
+ <div class='alert-message block-message warning'>
+ <p><strong>Please confirm</strong> that you would like to delete this object.
+ <div class='alert-actions'>
+ <form method='POST' action='{{ request.get_full_path }}'>
+ {% csrf_token %}
+ <input class='btn danger' type='submit' value='Delete'/>
+ <a class='btn' href='{{ object.get_absolute_url }}'>Cancel</a>
+ </form>
+ </div>
+ </div>
+ {% else %}
+ <div class='actions'>
+ {% if can_edit %}
+ <a class='btn' href='{{ object.get_absolute_url }}/edit'>Edit</a>
+ {% endif %}
+ <a class='btn danger' href='{{ object.get_absolute_url }}/delete' title='Permanently delete this object'>Delete</a>
+ {% block actions %}{% endblock actions %}
+ </div>
{% endif %}
- <a class='btn danger' href='{{ object.get_absolute_url }}/delete' title='Permanently delete this object'>Delete</a>
- {% block actions %}{% endblock actions %}
-</div>
{% endif %}
{% endblock content %}
diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py
index 02f145b3..02830dd0 100644
--- a/rpkid/rpki/gui/app/views.py
+++ b/rpkid/rpki/gui/app/views.py
@@ -31,15 +31,14 @@ from django.utils.http import urlquote
from django.template import RequestContext
from django import http
from django.views.generic.list_detail import object_list, object_detail
-from django.views.generic.create_update import delete_object
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
+from django.contrib.formtools.preview import FormPreview
from rpki.irdb import Zookeeper, ChildASN, ChildNet
from rpki.gui.app import models, forms, glue, range_list
from rpki.resource_set import (resource_range_as, resource_range_ipv4,
resource_range_ipv6, roa_prefix_ipv4)
-from rpki.exceptions import BadIPResource
from rpki import sundial
from rpki.gui.cacheview.models import ROAPrefixV4, ROAPrefixV6, ROA
@@ -339,61 +338,81 @@ def child_list(request):
'create_label': 'Import'})
-@handle_required
-def child_add_resource(request, pk, form_class, unused_list, callback,
- template_name='app/child_add_resource_form.html'):
- conf = request.session['handle']
- child = models.Child.objects.get(issuer=conf, pk=pk)
- log = request.META['wsgi.errors']
- if request.method == 'POST':
- form = form_class(request.POST, request.FILES)
- if form.is_valid():
- callback(child, form)
- Zookeeper(handle=conf.handle, logstream=log).run_rpkid_now()
- return http.HttpResponseRedirect(child.get_absolute_url())
- else:
- form = form_class()
-
- return render(request, template_name,
- {'object': child, 'form': form, 'unused': unused_list})
-
-
-def add_asn_callback(child, form):
- asns = form.cleaned_data.get('asns')
- r = resource_range_as.parse_str(asns)
- child.asns.create(start_as=r.min, end_as=r.max)
-
-
-def child_add_asn(request, pk):
- conf = request.session['handle']
- get_object_or_404(models.Child, issuer=conf, pk=pk)
- qs = models.ResourceRangeAS.objects.filter(cert__conf=conf)
- return child_add_resource(request, pk, forms.AddASNForm(qs), [],
- add_asn_callback)
-
+class ChildAddResourcePreview(FormPreview):
+ """
+ Base class for handling preview of AS/Prefix additions to a child.
+ Subclasses implement the 'done' method to perform actual work on IRDB.
-def add_address_callback(child, form):
- address_range = form.cleaned_data.get('address_range')
- if ':' in address_range:
- r = resource_range_ipv6.parse_str(address_range)
- version = 'IPv6'
- else:
- r = resource_range_ipv4.parse_str(address_range)
- version = 'IPv4'
- child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max),
- version=version)
+ """
+ form_template = 'app/child_detail.html'
+ preview_template = 'app/child_detail.html'
+
+ def __init__(self, *args, **kwargs):
+ """
+ The docstring for FormPreview says we should not redefine this method, but
+ I don't see how we can set extra information in this class otherwise.
+
+ """
+ self.child = kwargs.pop('child')
+ self.logstream = kwargs.pop('logstream')
+ super(ChildAddResourcePreview, self).__init__(*args, **kwargs)
+
+ def get_context(self, *args, **kwargs):
+ """"
+ Override the superclass method to add context variables needed by the
+ form template.
+
+ """
+ d = super(ChildAddResourcePreview, self).get_context(*args, **kwargs)
+ d['object'] = self.child
+ d['form_label'] = 'Add Resource'
+ return d
+
+ def process_preview(self, request, form, context):
+ # set a boolean flag so that the template knows this is a preview
+ context['is_preview'] = True
+
+
+class ChildAddPrefixPreview(ChildAddResourcePreview):
+ def done(self, request, cleaned_data):
+ address_range = cleaned_data.get('address_range')
+ if ':' in address_range:
+ r = resource_range_ipv6.parse_str(address_range)
+ version = 'IPv6'
+ else:
+ r = resource_range_ipv4.parse_str(address_range)
+ version = 'IPv4'
+ self.child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max),
+ version=version)
+ Zookeeper(handle=self.child.issuer.handle, logstream=self.logstream).run_rpkid_now()
+ return http.HttpResponseRedirect(self.child.get_absolute_url())
+@handle_required
def child_add_address(request, pk):
+ logstream = request.META['wsgi.errors']
conf = request.session['handle']
- get_object_or_404(models.Child, issuer=conf, pk=pk)
- qsv4 = models.ResourceRangeAddressV4.objects.filter(cert__conf=conf)
- qsv6 = models.ResourceRangeAddressV6.objects.filter(cert__conf=conf)
- return child_add_resource(request, pk,
- forms.AddNetForm(qsv4, qsv6),
- [],
- callback=add_address_callback)
+ child = get_object_or_404(models.Child, issuer=conf, pk=pk)
+ form = forms.AddNetForm(child)
+ preview = ChildAddPrefixPreview(form, child=child, logstream=logstream)
+ return preview(request)
+
+class ChildAddASNPreview(ChildAddResourcePreview):
+ def done(self, request, cleaned_data):
+ asns = cleaned_data.get('asns')
+ r = resource_range_as.parse_str(asns)
+ self.child.asns.create(start_as=r.min, end_as=r.max)
+ Zookeeper(handle=self.child.issuer.handle, logstream=self.logstream).run_rpkid_now()
+ return http.HttpResponseRedirect(self.child.get_absolute_url())
+@handle_required
+def child_add_asn(request, pk):
+ logstream = request.META['wsgi.errors']
+ conf = request.session['handle']
+ child = get_object_or_404(models.Child, issuer=conf, pk=pk)
+ form = forms.AddASNForm(child)
+ preview = ChildAddASNPreview(form, child=child, logstream=logstream)
+ return preview(request)
@handle_required
def child_view(request, pk):