Skip to content
Snippets Groups Projects
Commit be8171a8 authored by David Schmitz's avatar David Schmitz
Browse files

REST API: PUT / PATCH distinc behavior: PATCH without change of status or any...

REST API: PUT / PATCH distinc behavior: PATCH without change of status or any FlowSpec match/then attribute will no trigger a recommiting of an active rule, e.g. useful for updating/prolonging expires field or just updating the comments fields; broken live status messages for pending states restored
parent 44eb3e29
Branches
No related tags found
No related merge requests found
......@@ -294,7 +294,7 @@ class Route(models.Model):
else:
peer = None
send_message("[%s] Adding rule %s. Please wait..." % (self.applier_username_nice, self.name), peer)
send_message("[%s] Adding rule %s. Please wait..." % (self.applier_username_nice, self.name_visible), peer, self)
response = add.delay(self.pk)
logger.info('Got add job id: %s' % response)
if not settings.DISABLE_EMAIL_NOTIFICATION:
......@@ -346,13 +346,7 @@ class Route(models.Model):
else:
peer = None
send_message(
'[%s] Editing rule %s. Please wait...' %
(
self.applier_username_nice,
self.name
), peer
)
send_message('[%s] Editing rule %s. Please wait...' % (self.applier_username_nice, self.name_visible), peer, self)
response = edit.delay(self.pk)
logger.info('Got edit job id: %s' % response)
if not settings.DISABLE_EMAIL_NOTIFICATION:
......@@ -389,6 +383,7 @@ class Route(models.Model):
def commit_deactivate(self, *args, **kwargs):
username = None
reason_text = ''
reason = ''
if "reason" in kwargs:
......@@ -411,13 +406,14 @@ class Route(models.Model):
peer = username.peer_tag
else:
peer = None
send_message(
'[%s] Suspending rule %s. %sPlease wait...' % (
self.applier_username_nice,
self.name,
reason_text
), peer
)
#send_message(
# '[%s] Suspending rule %s. %sPlease wait...' % (
# self.applier_username_nice,
# self.name_visible,
# reason_text
# ),
# peer, self
#)
if not settings.DISABLE_EMAIL_NOTIFICATION:
fqdn = Site.objects.get_current().domain
admin_url = 'https://%s%s' % (
......@@ -748,14 +744,16 @@ class Route(models.Model):
##
def send_message(msg, user):
# username = user.username
peer = user
#b = beanstalkc.Connection()
#b.use(settings.POLLS_TUBE)
tube_message = json.dumps({'message': str(msg), 'username': peer})
#b.put(tube_message)
#b.close()
def send_message(msg, peer, route):
## username = user.username
##b = beanstalkc.Connection()
##b.use(settings.POLLS_TUBE)
#tube_message = json.dumps({'message': str(msg), 'username': peer})
##b.put(tube_message)
##b.close()
# use new announce method in tasks.py
announce(msg, peer, route)
#############################################################################
#############################################################################
......
......@@ -170,13 +170,21 @@ class RouteSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Route
fields = (
'name', 'id', 'comments', 'applier', 'source', 'sourceport',
'destination', 'destinationport', 'port', 'dscp', 'fragmenttype',
'icmpcode', 'packetlength', 'protocol', 'tcpflag', 'then', 'filed',
'last_updated', 'status', 'expires', 'response', 'comments',
'name', 'id', 'comments',
'applier',
'packetlength',
'source', 'destination',
'dscp', 'fragmenttype',
'protocol',
'icmptype', 'icmpcode',
'sourceport', 'destinationport', 'port',
'tcpflag',
'then',
'filed', 'last_updated',
'status', 'expires', 'response',
'requesters_address')
read_only_fields = (
'requesters_address', 'response', 'last_updated', 'id', 'filed')
'id', 'requesters_address', 'filed', 'last_updated', 'response')
class PortSerializer(serializers.HyperlinkedModelSerializer):
......
......@@ -99,6 +99,13 @@ def edit(routepk, callback=None):
@shared_task(ignore_result=True, autoretry_for=(TimeoutError, TimeLimitExceeded, SoftTimeLimitExceeded), retry_backoff=True, retry_kwargs={'max_retries': settings.NETCONF_MAX_RETRY_BEFORE_ERROR})
def deactivate_route(routepk, **kwargs):
"""Deactivate the Route in ACTIVE state. Permissions must be checked before this call."""
reason_text = ''
if "reason" in kwargs:
reason = kwargs['reason']
reason_text = 'Reason: %s.' % reason
# here imported to avoid cyclic import on file level
from flowspec.models import Route
route = Route.objects.get(pk=routepk)
initial_status = route.status
......@@ -106,11 +113,13 @@ def deactivate_route(routepk, **kwargs):
logger.error("tasks::deactivate(): Cannot deactivate route that is not in ACTIVE or potential ACTIVE status.")
return
logger.info("tasks::deactivate_route(): initial_status="+str(initial_status))
announce("[%s] Suspending rule : %s. %sPlease wait..." % (route.applier_username_nice, route.name_visible, reason_text), route.applier, route)
applier = PR.Applier(route_object=route)
# Delete from router via NETCONF
commit, response = applier.apply(operation="delete")
reason_text = ''
#reason_text = ''
logger.info("tasks::deactivate_route(): commit="+str(commit))
if commit:
route.status="INACTIVE"
......@@ -229,13 +238,16 @@ def announce(messg, user, route):
# check if the target is a subnet of peer range (python3.6 doesn't have subnet_of())
try:
if tgt_net.version==net.version and tgt_net.network_address >= net.network_address and tgt_net.broadcast_address <= net.broadcast_address:
username = peer.peer_tag
logger.info("ANNOUNCE found peer " + str(username))
peername = peer.peer_tag
#logger.info("ANNOUNCE found peer " + str(peername))
#break
if username not in visited_channel:
visited_channel[username]=True
announce_redis_lowlevel(messg, username)
if peername not in visited_channel:
logger.info("ANNOUNCE found peer to announce to: " + str(peername))
visited_channel[peername]=True
announce_redis_lowlevel(messg, peername)
else:
logger.info("ANNOUNCE peer already haveing announced to: " + str(peername))
except TypeError:
pass
......
......@@ -79,7 +79,9 @@ def user_routes(request):
request,
'user_routes.html',
{
'routes': user_routes
'routes': user_routes,
'maxexpires': settings.MAX_RULE_EXPIRE_DAYS,
'expiration_day_offset': settings.EXPIRATION_DAYS_OFFSET
},
)
......@@ -160,7 +162,10 @@ def group_routes(request):
return render(
request,
'user_routes.html',
{}
{
'maxexpires': settings.MAX_RULE_EXPIRE_DAYS,
'expiration_day_offset': settings.EXPIRATION_DAYS_OFFSET
}
)
......@@ -529,14 +534,17 @@ def prolong_route(request, route_slug):
('Cannot edit a pending rule: %s.') % (route_slug)
)
return HttpResponseRedirect(reverse("group-routes"))
add_days = settings.EXPIRATION_DAYS_OFFSET - 1
prolonged_date = datetime.date.today()+datetime.timedelta(add_days)
if route_edit.expires < prolonged_date:
route_edit.expires = prolonged_date
#route_edit.expires = route_edit.expires+datetime.timedelta(30)
route_edit.expires = datetime.date.today()+datetime.timedelta(30)
#route_edit.expires = route_edit.expires+datetime.timedelta(30)
if True:
#route_edit.commit_edit()
route_edit.save()
return HttpResponseRedirect(reverse("group-routes"))
return HttpResponseRedirect(reverse("group-routes"))
@login_required
@never_cache
......
......@@ -8,6 +8,8 @@ from rest_framework import viewsets
from flowspec.models import Route, ThenAction, FragmentType, MatchProtocol, MatchDscp
from flowspec.models import convert_container_to_queryset
from flowspec.tasks import announce
from flowspec.serializers import (
RouteSerializer,
ThenActionSerializer,
......@@ -162,7 +164,7 @@ class RouteViewSet(viewsets.ModelViewSet):
obj.response = "N/A"
obj.save()
def work_on_active_object(obj, new_status):
def work_on_active_object(obj, new_status, partial, route_params_changed):
"""
Decides which `commit` action to choose depending on the
requested status
......@@ -170,21 +172,27 @@ class RouteViewSet(viewsets.ModelViewSet):
Cases:
* `ACTIVE` ~> `INACTIVE`: The `Route` must be deleted from the
flowspec device (`commit_deactivate`)
* `ACTIVE` ~> `ACTIVE`: The `Route` is present, so it must be
* (not partial or route_params_changed) and `ACTIVE` ~> `ACTIVE`: The `Route` is present and to be changed actually, so it must be
edited (`commit_edit`)
* partial (PATCH method) and !route_params_changed and `ACTIVE` ~> `ACTIVE`: only update database object
:param new_status: the newly requested status
:type new_status: str
:param obj: the `Route` object
:type obj: `flowspec.models.Route`
"""
set_object_pending(obj)
if new_status == 'INACTIVE':
set_object_pending(obj)
deactivate_route.delay(obj.pk)
else:
elif not partial or route_params_changed: # in case of an PATCH without status change and !route_params_changed dont re-commit object
set_object_pending(obj)
obj.commit_edit()
else:
obj.save()
announce("[%s] Rule non-flowspec-params updated: %s" % (obj.applier_username_nice, obj.name_visible), obj.applier, obj)
def work_on_inactive_object(obj, new_status):
def work_on_inactive_object(obj, new_status, partial):
"""
Decides which `commit` action to choose depending on the
requested status
......@@ -200,6 +208,9 @@ class RouteViewSet(viewsets.ModelViewSet):
if new_status == 'ACTIVE':
set_object_pending(obj)
obj.commit_add()
else:
obj.save()
announce("[%s] Rule non-flowspec-params updated: %s" % (obj.applier_username_nice, obj.name_visible), obj.applier, obj)
#if not partial:
# raise PermissionDenied('Permission Denied')
......@@ -207,16 +218,21 @@ class RouteViewSet(viewsets.ModelViewSet):
obj = get_object_or_404(self.queryset, pk=pk)
old_status = obj.status
serializer = RouteSerializer(
obj, context={'request': request},
#data=request.DATA, partial=partial)
data=request.data, partial=partial)
logger.info("RouteViewSet::update(): request.data="+str(request.data))
serializer = RouteSerializer(obj, context={'request': request}, data=request.data, partial=partial)
logger.info("RouteViewSet::update(): partial="+str(partial)+" request.data="+str(request.data))
if serializer.is_valid():
#new_status = serializer.object.status
new_status = serializer.data["status"]
requested_status = request.data["status"]
try:
requested_status = request.data["status"]
except KeyError:
if partial:
requested_status = old_status
else:
return Response("no status specified", status=400)
#if requested_status == 'INACTIVE':
new_status = requested_status
#logger.info("RouteViewSet::update(): data="+str(request.data))
......@@ -225,18 +241,178 @@ class RouteViewSet(viewsets.ModelViewSet):
logger.info("RouteViewSet::update(): obj.type="+str(type(obj)))
logger.info("RouteViewSet::update(): obj="+str(obj))
logger.info("RouteViewSet::update(): old_status="+str(old_status)+", new_status="+str(new_status))
route_params_changed = self.helper_check_route_params_change(partial, obj, serializer, request.data)
super(RouteViewSet, self).update(request, pk, partial=partial)
obj = get_object_or_404(self.queryset, pk=pk)
if old_status == 'ACTIVE':
work_on_active_object(obj, new_status)
work_on_active_object(obj, new_status, partial, route_params_changed)
elif old_status in ['INACTIVE', 'ERROR']:
work_on_inactive_object(obj, new_status)
logger.info("RouteViewSet::update(): obj="+str(obj))
return Response(
RouteSerializer(obj,context={'request': request}).data,
status=200)
work_on_inactive_object(obj, new_status, partial)
logger.info("RouteViewSet::update(): returning (obj="+str(obj)+" obj.status="+str(obj.status)+")")
return Response(RouteSerializer(obj, context={'request': request}).data, status=200)
else:
return Response(serializer.errors, status=400)
def helper_check_route_params_change(self, is_partial, obj, serializer, request_data):
#old_source = obj.source
old_source = serializer.data["source"]
try:
new_source = request_data["source"]
except KeyError:
if is_partial:
new_source = old_source
else:
new_source = None
#old_destination = obj.destination
old_destination = serializer.data["destination"]
try:
new_destination = request_data["destination"]
except KeyError:
if is_partial:
new_destination = old_destination
else:
new_destination = None
#old_sourceport = obj.sourceport
old_sourceport = serializer.data["sourceport"]
try:
new_sourceport = request_data["sourceport"]
except KeyError:
if is_partial:
new_sourceport = old_sourceport
else:
new_sourceport = None
#old_destinationport = obj.destinationport
old_destinationport = serializer.data["destinationport"]
try:
new_destinationport = request_data["destinationport"]
except KeyError:
if is_partial:
new_destinationport = old_destinationport
else:
new_destinationport = None
#old_port = obj.port
old_port = serializer.data["port"]
try:
new_port = request_data["port"]
except KeyError:
if is_partial:
new_port = old_port
else:
new_port = None
#old_protocol = [str(el) for el in obj.protocol.all()]
old_protocol = [str(el) for el in serializer.data["protocol"]]
old_protocol.sort()
try:
new_protocol = [str(el) for el in request_data["protocol"]]
new_protocol.sort()
except KeyError:
if is_partial:
new_protocol = old_protocol
else:
new_protocol = []
#old_fragmentype = [str(el) for el in obj.fragmenttype.all()]
old_fragmentype = [str(el) for el in serializer.data["fragmenttype"]]
old_fragmentype.sort()
try:
new_fragmentype = [str(el) for el in request_data["fragmenttype"]]
new_fragmentype.sort()
except KeyError:
if is_partial:
new_fragmentype = old_fragmentype
else:
new_fragmentype = []
old_icmptype = obj.icmptype
#old_icmptype = serializer.data["icmptype"]
try:
new_icmptype = request_data["icmptype"]
except KeyError:
if is_partial:
new_icmptype = old_icmptype
else:
new_icmptype = None
old_icmpcode = obj.icmpcode
#old_icmpcode = serializer.data["icmpcode"]
try:
new_icmpcode = request_data["icmpcode"]
except KeyError:
if is_partial:
new_icmpcode = old_icmpcode
else:
new_icmpcode = None
old_tcpflag = obj.tcpflag
#old_tcpflag = serializer.data["tcpflag"]
try:
new_tcpflag = request_data["tcpflag"]
except KeyError:
if is_partial:
new_tcpflag = old_tcpflag
else:
new_tcpflag = None
old_dscp = obj.dscp
#old_dscp = serializer.data["dscp"]
try:
new_dscp = request_data["dscp"]
except KeyError:
if is_partial:
new_dscp = old_dscp
else:
new_dscp = None
old_packetlength = obj.packetlength
#old_packetlength = serializer.data["packetlength"]
try:
new_packetlength = request_data["packetlength"]
except KeyError:
if is_partial:
new_packetlength = old_packetlength
else:
new_packetlength = None
#old_then = [str(el) for el in obj.then.all()]
old_then = [str(el) for el in serializer.data["then"]]
old_then.sort()
try:
new_then = [str(el) for el in request_data["then"]]
new_then.sort()
except KeyError:
if is_partial:
new_then = old_then
else:
new_then = None
#logger.info("RouteViewSet::helper_check_route_params_change(): old_source="+str(old_source)+", new_source="+str(new_source))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_sourceport="+str(old_sourceport)+", new_sourceport="+str(new_sourceport))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_destination="+str(old_destination)+", new_destination="+str(new_destination))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_destinationport="+str(old_destinationport)+", new_destinationport="+str(new_destinationport))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_protocol="+str(old_protocol)+", new_protocol="+str(new_protocol))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_fragmentype="+str(old_fragmentype)+", new_fragmentype="+str(new_fragmentype))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_then="+str(old_then)+", new_then="+str(new_then))
##logger.info("RouteViewSet::helper_check_route_params_change(): old="+str(obj))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_icmptype="+str(old_icmptype)+", new_icmptype="+str(new_icmptype))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_icmpcode="+str(old_icmpcode)+", new_icmpcode="+str(new_icmpcode))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_tcpflag="+str(old_tcpflag)+", new_tcpflag="+str(new_tcpflag))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_dscp="+str(old_dscp)+", new_dscp="+str(new_dscp))
#logger.info("RouteViewSet::helper_check_route_params_change(): old_packetlength="+str(old_packetlength)+", new_packetlength="+str(new_packetlength))
route_params_changed = old_source!=new_source or old_destination != new_destination or old_sourceport!=new_sourceport or old_destinationport!=new_destinationport or old_protocol!=new_protocol or old_fragmentype!=new_fragmentype or old_then!=new_then
logger.info("RouteViewSet::helper_check_route_params_change(): => route_params_changed="+str(route_params_changed))
return route_params_changed
def destroy(self, request, pk=None, partial=False):
""" HTTTP DELETE Method """
obj = get_object_or_404(self.queryset, pk=pk)
......
......@@ -178,7 +178,7 @@ var updater = {
updater.showMessage(messages[i], peerid);
try {
body = messages[i].body
if (body.match(/Successfully committed$/) || body.match(/Deleting inactive/) || body.match(/NETCONF/)) {
if (body.match(/Successfully committed$/) || body.match(/Deleting inactive/) || body.match(/NETCONF/) || body.match(/non-flowspec-params updated/)) {
reloadContent = true;
}
} catch (e) {
......
......@@ -398,7 +398,12 @@ $(document).ready( function(){
prolongurl = "{% url 'prolong-route' 'routename' %}".replace('routename', full.name.toString());
if (status == "ACTIVE" ){
btn = '<a href="'+editurl+'" class="btn-info btn btn-sm btn-outline">{% trans "Edit" %}</a>';
if (full.isnonexpire !== 'True') {
today1 = new Date(Date.now());
expires = new Date(full.expires);
delta = Math.floor(expires.getTime()/1000/86400) - Math.floor(today1.getTime()/1000/86400)
max_delta = {{ expiration_day_offset }} -1
//btn = btn + delta + " " + max_delta
if (full.isnonexpire !== 'True' && delta < max_delta) {
btn = btn + '<a href="'+prolongurl+'" class="btn-info btn btn-sm btn-outline">{% trans "Prolong" %}</a>';
}
btn = btn + ' <button class="deactivate_button btn-warning btn btn-sm btn-outline" id="'+full.name+'" data-routename="'+full.name+'">{% trans "Deactivate" %}</button>';
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment