1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/admin_commandline_tools.py

325 lines
10 KiB
Python
Raw Normal View History

2018-11-08 19:45:21 +00:00
import StringIO
import json
2019-02-15 17:46:20 +00:00
2018-11-08 19:45:21 +00:00
import reversion
2019-02-15 17:46:20 +00:00
from reversion.models import Version
2018-11-08 19:45:21 +00:00
from dal import autocomplete
from django import forms
from django.core.management import call_command
2019-02-15 17:46:20 +00:00
from peeringdb_server.models import (REFTAG_MAP, COMMANDLINE_TOOLS, CommandLineTool,
2018-11-08 19:45:21 +00:00
InternetExchange, Facility)
2019-01-04 10:02:28 +00:00
from peeringdb_server import maintenance
2018-11-08 19:45:21 +00:00
def _(m):
return m
TOOL_MAP = {}
def register_tool(cls):
TOOL_MAP[cls.tool] = cls
def get_tool(tool_id, form):
"""
Arguments:
tool_id (str): tool_id as it exists in COMMANDLINE_TOOLS
form (django.forms.Form): form instance
Returns:
CommandLineToolWrapper instance
"""
t = TOOL_MAP.get(tool_id)
t = t(form)
return t
def get_tool_from_data(data):
"""
Arguments:
data (dict): dict containing form data, at the very least
needs to have a "tool" key containing the tool_id
Returns:
CommandLineToolWrapper instance
"""
tool_id = data.get("tool")
t = TOOL_MAP.get(tool_id)
form = t.Form(data)
form.is_valid()
t = t(form)
return t
class EmptyId(object):
id = 0
class CommandLineToolWrapper(object):
tool = None
2019-01-04 10:02:28 +00:00
queue = 0
maintenance = False
2018-11-08 19:45:21 +00:00
class Form(forms.Form):
pass
def __init__(self, form):
self.status = 0
self.result = None
self.args = []
self.kwargs = {}
self.form_instance = form
self.set_arguments(form.cleaned_data)
@property
def name(self):
return dict(COMMANDLINE_TOOLS).get(self.tool)
@property
def form(self):
return self.Form()
@property
def description(self):
return self.tool
@property
def pretty_result(self):
if not self.result:
return ""
r = []
for line in self.result.split("\n"):
if line.find("[error]") > -1:
r.append('<div class="error">{}</div>'.format(line))
elif line.find("[warning]") > -1:
r.append('<div class="warning">{}</div>'.format(line))
else:
r.append('<div class="info">{}</div>'.format(line))
return "\n".join(r)
def set_arguments(self, form_data):
pass
2019-02-15 17:46:20 +00:00
def validate(self):
pass
2019-01-04 10:02:28 +00:00
def _run(self, user, commit=False):
2018-11-08 19:45:21 +00:00
r = StringIO.StringIO()
2019-01-04 10:02:28 +00:00
if self.maintenance and commit:
maintenance.on()
2018-11-08 19:45:21 +00:00
try:
2019-02-15 17:46:20 +00:00
self.validate()
2018-11-08 19:45:21 +00:00
if commit:
call_command(self.tool, *self.args, commit=True, stdout=r,
**self.kwargs)
else:
call_command(self.tool, *self.args, stdout=r, **self.kwargs)
self.result = r.getvalue()
except Exception as inst:
self.result = "[error] {}".format(inst)
self.status = 1
2019-01-04 10:02:28 +00:00
finally:
if self.maintenance and commit:
maintenance.off()
2018-11-08 19:45:21 +00:00
if commit:
CommandLineTool.objects.create(user=user, tool=self.tool,
description=self.description,
2019-01-04 10:02:28 +00:00
status="done",
2018-11-08 19:45:21 +00:00
arguments=json.dumps({
"args": self.args,
"kwargs": self.kwargs
}), result=self.result)
return self.result
2019-01-04 10:02:28 +00:00
def run(self, user, commit=False):
if self.queue and commit:
if CommandLineTool.objects.filter(tool=self.tool).exclude(status="done").count() >= self.queue:
self.result = "[error] {}".format(_("This command is already waiting / running - please wait for it to finish before executing it again"))
return self.result
CommandLineTool.objects.create(user=user, tool=self.tool,
description=self.description,
status="waiting",
arguments=json.dumps({
"args": self.args,
"kwargs": self.kwargs
}), result="")
self.result = "[warn] {}".format(_("This command takes a while to complete and will be queued and ran in the "\
"background. No output log can be provided at this point in time. You may "\
"review once the command has finished."))
return self.result
else:
with reversion.create_revision():
return self._run(user, commit=commit)
2018-11-08 19:45:21 +00:00
# TOOL: RENUMBER LAN
@register_tool
class ToolRenumberLans(CommandLineToolWrapper):
"""
This tools runs the pdb_renumber_lans command to
Renumber IP Spaces in an Exchange
"""
tool = "pdb_renumber_lans"
class Form(forms.Form):
exchange = forms.ModelChoiceField(
queryset=InternetExchange.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/ix/json"))
old_prefix = forms.CharField(
help_text=_(
"Three leftmost octets of the original prefix - eg. xxx.xxx.xxx"
))
new_prefix = forms.CharField(
help_text=_(
"Three leftmost octets of the new prefix - eg. xxx.xxx.xxx"))
@property
def description(self):
""" Provide a human readable description of the command that was run """
return "{}: {} to {}".format(
InternetExchange.objects.get(id=self.kwargs["ix"]), self.args[0],
self.args[1])
def set_arguments(self, form_data):
self.args = [form_data.get("old_prefix"), form_data.get("new_prefix")]
self.kwargs = {"ix": form_data.get("exchange", EmptyId()).id}
@register_tool
class ToolMergeFacilities(CommandLineToolWrapper):
"""
This tool runs the pdb_fac_merge command to
merge two facilities
"""
tool = "pdb_fac_merge"
class Form(forms.Form):
other = forms.ModelChoiceField(
queryset=Facility.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/fac/json"),
help_text=_("Merge this facility - it will be deleted"))
target = forms.ModelChoiceField(
queryset=Facility.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/fac/json"),
help_text=_("Target facility"))
@property
def description(self):
""" Provide a human readable description of the command that was run """
return "{} into {}".format(
Facility.objects.get(id=self.kwargs["ids"]),
Facility.objects.get(id=self.kwargs["target"]))
def set_arguments(self, form_data):
self.kwargs = {
"ids": str(form_data.get("other", EmptyId()).id),
"target": str(form_data.get("target", EmptyId()).id)
}
@register_tool
class ToolMergeFacilitiesUndo(CommandLineToolWrapper):
"""
This tool runs the pdb_fac_merge_undo command to
undo a facility merge
"""
tool = "pdb_fac_merge_undo"
class Form(forms.Form):
merge = forms.ModelChoiceField(
queryset=CommandLineTool.objects.filter(
tool="pdb_fac_merge").order_by("-created"),
widget=autocomplete.ModelSelect2(
url="/autocomplete/admin/clt-history/pdb_fac_merge/"),
help_text=_("Undo this merge"))
@property
def description(self):
""" Provide a human readable description of the command that was run """
# in order to make a useful description we need to collect the arguments
# from the merge command that was undone
kwargs = json.loads(
CommandLineTool.objects.get(
id=self.kwargs["clt"]).arguments).get("kwargs")
return "Undo: {} into {}".format(
Facility.objects.get(id=kwargs["ids"]),
Facility.objects.get(id=kwargs["target"]))
def set_arguments(self, form_data):
self.kwargs = {"clt": form_data.get("merge", EmptyId()).id}
2019-01-04 10:02:28 +00:00
@register_tool
class ToolReset(CommandLineToolWrapper):
tool = "pdb_wipe"
queue = 1
maintenance = True
class Form(forms.Form):
keep_users = forms.BooleanField(required=False,
help_text=_("Don't delete users. Note that superuser accounts are always kept - regardless of this setting."))
load_data = forms.BooleanField(required=False, initial=True, help_text=_("Load data from peeringdb API"))
load_data_url = forms.CharField(required=False, initial="https://www.peeringdb.com/api")
@property
def description(self):
return "Reset environment"
def set_arguments(self, form_data):
self.kwargs = form_data
2019-02-15 17:46:20 +00:00
@register_tool
class ToolUndelete(CommandLineToolWrapper):
"""
Allows restoration of an object object and it's child objects
"""
tool = "pdb_undelete"
# These are the reftags that are currently supported by this
# tool.
supported_reftags = ["ixlan","fac"]
class Form(forms.Form):
version = forms.ModelChoiceField(
queryset=Version.objects.all().order_by("-revision_id"),
widget=autocomplete.ModelSelect2(
url="/autocomplete/admin/deletedversions"),
help_text=_("Restore this object - search by [reftag] [id]"))
@property
def description(self):
return "{reftag} {id}".format(**self.kwargs)
def set_arguments(self, form_data):
version = form_data.get("version")
if not version:
return
reftag = version.content_type.model_class().HandleRef.tag
self.kwargs = {"reftag":reftag, "id":version.object_id, "version_id":version.id}
def validate(self):
if self.kwargs.get("reftag") not in self.supported_reftags:
raise ValueError(_("Only {} type objects may be restored " \
"through this interface at this point").format(",".join(self.supported_reftags)))
obj = REFTAG_MAP[self.kwargs.get("reftag")].objects.get(id=self.kwargs.get("id"))
if obj.status != "deleted":
raise ValueError("{} is not currently marked as deleted".format(obj))