17
17
#
18
18
#########################################################################
19
19
20
+ import itertools
20
21
import logging
21
- import urllib
22
22
import requests
23
-
24
- from django .conf import settings
25
23
from requests .auth import HTTPBasicAuth
24
+ import traceback
25
+ import urllib
26
26
27
27
logger = logging .getLogger (__name__ )
28
28
29
- ogc_server_settings = settings .OGC_SERVER ["default" ]
30
-
31
29
32
30
class GeofenceException (Exception ):
33
31
pass
34
32
35
33
36
34
class Rule :
37
35
"""_summary_
38
- JSON representation of a GeoFence Rule
36
+ A GeoFence Rule.
37
+ Provides the object to be rendered as JSON.
39
38
40
39
e.g.:
41
40
{"Rule":
@@ -56,14 +55,15 @@ class Rule:
56
55
ALLOW = "ALLOW"
57
56
DENY = "DENY"
58
57
LIMIT = "LIMIT"
58
+
59
59
CM_MIXED = "MIXED"
60
60
61
61
def __init__ (
62
62
self ,
63
- priority ,
64
- workspace ,
65
- layer ,
66
63
access : (str , bool ),
64
+ priority = None ,
65
+ workspace = None ,
66
+ layer = None ,
67
67
user = None ,
68
68
group = None ,
69
69
service = None ,
@@ -105,14 +105,18 @@ def __init__(
105
105
if limits :
106
106
self .fields ["limits" ] = limits
107
107
108
- def get_object (self ):
108
+ def set_priority (self , pri : int ):
109
+ self .fields ["priority" ] = pri
110
+
111
+ def get_object (self ) -> dict :
109
112
logger .debug (f"Creating Rule object: { self .fields } " )
110
113
return {"Rule" : self .fields }
111
114
112
115
113
116
class Batch :
114
117
"""_summary_
115
- Returns a list of Operations that GeoFence can execute in a batch
118
+ A GeoFence Batch.
119
+ It's a list of operations that will be executed transactionally inside GeoFence.
116
120
117
121
e.g.:
118
122
{
@@ -203,38 +207,64 @@ def add_insert_rule(self, rule: Rule):
203
207
operation .update (rule .get_object ())
204
208
self .operations .append (operation )
205
209
206
- def get_batch_length (self ):
210
+ def length (self ) -> int :
207
211
return len (self .operations )
208
212
209
- def get_object (self ):
210
- logger .debug (f"Creating Batch object { self .log_name } with { len (self .operations )} operations" )
213
+ def get_object (self ) -> dict :
211
214
return {"Batch" : {"operations" : self .operations }}
212
215
213
216
214
- class GeofenceClient :
217
+ class AutoPriorityBatch ( Batch ) :
215
218
"""_summary_
216
- Instance of a simple GeoFence REST client allowing to interact with the GeoServer APIs.
217
- Exposes few utility methods to insert or purge the rules and run batches of operations.
219
+ A Batch that handles the priority of the inserted rules.
220
+ The first rule will have the declared `start_rule_pri`, next Rules will have the priority incremented.
221
+ """
218
222
219
- Returns:
220
- _type_: Rule
223
+ def __init__ (self , start_rule_pri : int , log_name = None ) -> None :
224
+ super ().__init__ (log_name )
225
+ self .pri = itertools .count (start_rule_pri )
226
+
227
+ def add_insert_rule (self , rule : Rule ):
228
+ rule .set_priority (self .pri .__next__ ())
229
+ super ().add_insert_rule (rule )
230
+
231
+
232
+ class GeoFenceClient :
233
+ """_summary_
234
+ A GeoFence REST client allowing to interact with the embedded GeoFence API (which is slightly incompatible
235
+ with the original standalone GeoFence API.)
236
+ The class methods map on GeoFence operations.
237
+ Functionalities needing more than one call are implemented within GeoFenceUtils.
221
238
"""
222
239
223
240
def __init__ (self , baseurl : str , username : str , pw : str ) -> None :
241
+ if not baseurl .endswith ("/" ):
242
+ baseurl += "/"
243
+
224
244
self .baseurl = baseurl
225
245
self .username = username
226
246
self .pw = pw
247
+ self .timeout = 60
248
+
249
+ def set_timeout (self , timeout : int ):
250
+ self .timeout = timeout
227
251
228
252
def invalidate_cache (self ):
229
- r = requests .put (
230
- f'{ self .baseurl .rstrip ("/" )} /geofence/ruleCache/invalidate' , auth = HTTPBasicAuth (self .username , self .pw )
231
- )
253
+ r = requests .put (f"{ self .baseurl } ruleCache/invalidate" , auth = HTTPBasicAuth (self .username , self .pw ))
232
254
233
255
if r .status_code != 200 :
234
256
logger .debug ("Could not invalidate cache" )
235
257
raise GeofenceException ("Could not invalidate cache" )
236
258
237
- def get_rules (self , page = None , entries = None , workspace = None , workspace_any = None , layer = None , layer_any = None ):
259
+ def get_rules (
260
+ self ,
261
+ page : int = None ,
262
+ entries : int = None ,
263
+ workspace : str = None ,
264
+ workspace_any : bool = None ,
265
+ layer : str = None ,
266
+ layer_any : bool = None ,
267
+ ):
238
268
if (page is None and entries is not None ) or (page is not None and entries is None ):
239
269
raise GeofenceException (f"Bad page/entries combination { page } /{ entries } " )
240
270
@@ -257,13 +287,13 @@ def get_rules(self, page=None, entries=None, workspace=None, workspace_any=None,
257
287
if value is not None :
258
288
params [param ] = value
259
289
260
- url = f' { self .baseurl . rstrip ( "/" ) } /geofence/ rules.json?{ urllib .parse .urlencode (params )} '
290
+ url = f" { self .baseurl } rules.json?{ urllib .parse .urlencode (params )} "
261
291
262
292
r = requests .get (
263
293
url ,
264
294
headers = {"Content-type" : "application/json" },
265
295
auth = HTTPBasicAuth (self .username , self .pw ),
266
- timeout = ogc_server_settings . get ( "TIMEOUT" , 10 ) ,
296
+ timeout = self . timeout ,
267
297
verify = False ,
268
298
)
269
299
@@ -284,10 +314,10 @@ def get_rules_count(self):
284
314
http://<host>:<port>/geoserver/rest/geofence/rules/count.json
285
315
"""
286
316
r = requests .get (
287
- f' { self .baseurl . rstrip ( "/" ) } /geofence/ rules/count.json' ,
317
+ f" { self .baseurl } rules/count.json" ,
288
318
headers = {"Content-type" : "application/json" },
289
319
auth = HTTPBasicAuth (self .username , self .pw ),
290
- timeout = ogc_server_settings . get ( "TIMEOUT" , 10 ) ,
320
+ timeout = self . timeout ,
291
321
verify = False ,
292
322
)
293
323
@@ -310,73 +340,127 @@ def insert_rule(self, rule: Rule):
310
340
http://<host>:<port>/geoserver/rest/geofence/rules
311
341
"""
312
342
r = requests .post (
313
- f'{ self .baseurl .rstrip ("/" )} /geofence/rules' ,
314
- # headers={'Content-type': 'application/json'},
343
+ f"{ self .baseurl } rules" ,
315
344
json = rule .get_object (),
316
345
auth = HTTPBasicAuth (self .username , self .pw ),
317
- timeout = ogc_server_settings . get ( "TIMEOUT" , 60 ) ,
346
+ timeout = self . timeout ,
318
347
verify = False ,
319
348
)
320
349
321
350
if r .status_code not in (200 , 201 ):
322
- logger .debug (f"Could not insert rule: [{ r .status_code } ] - { r .content } " )
323
- raise GeofenceException (f"Could not insert rule: [{ r .status_code } ]" )
351
+ logger .debug (f"Could not insert rule: [{ r .status_code } ] - { r .text } " )
352
+ raise GeofenceException (f"Could not insert rule: [{ r .status_code } ] - { r . text } " )
324
353
325
354
except Exception as e :
326
355
logger .debug ("Error while inserting rule" , exc_info = e )
327
356
raise GeofenceException (f"Error while inserting rule: { e } " )
328
357
329
- def run_batch (self , batch : Batch ) :
330
- if batch .get_batch_length () == 0 :
358
+ def run_batch (self , batch : Batch , timeout : int = None ) -> bool :
359
+ if batch .length () == 0 :
331
360
logger .debug (f"Skipping batch execution { batch .log_name } " )
332
- return
361
+ return False
333
362
363
+ logger .debug (f"Running batch { batch .log_name } with { batch .length ()} operations" )
334
364
try :
335
365
"""
336
366
curl -X GET -u admin:geoserver \
337
367
http://<host>:<port>/geoserver/rest/geofence/rules/count.json
338
368
"""
339
369
r = requests .post (
340
- f' { self .baseurl . rstrip ( "/" ) } /geofence/ batch/exec' ,
370
+ f" { self .baseurl } batch/exec" ,
341
371
json = batch .get_object (),
342
372
auth = HTTPBasicAuth (self .username , self .pw ),
343
- timeout = ogc_server_settings . get ( "TIMEOUT" , 60 ) ,
373
+ timeout = timeout or self . timeout ,
344
374
verify = False ,
345
375
)
346
376
347
377
if r .status_code != 200 :
348
- logger .debug (f"Error while running batch { batch .log_name } : [{ r .status_code } ] - { r .content } " )
378
+ logger .debug (
379
+ f"Error while running batch { batch .log_name } : [{ r .status_code } ] - { r .content } "
380
+ f"\n { batch .get_object ()} "
381
+ )
349
382
raise GeofenceException (f"Error while running batch { batch .log_name } : [{ r .status_code } ]" )
350
383
351
- return
384
+ return True
352
385
353
386
except Exception as e :
354
- logger .debug (f"Error while requesting batch execution { batch .log_name } " , exc_info = e )
387
+ logger .info (f"Error while requesting batch exec { batch .log_name } " )
388
+ logger .debug (f"Error while requesting batch exec { batch .log_name } --> { batch .get_object ()} " , exc_info = e )
355
389
raise GeofenceException (f"Error while requesting batch execution { batch .log_name } : { e } " )
356
390
357
- def purge_all_rules (self ):
391
+
392
+ class GeoFenceUtils :
393
+ def __init__ (self , client : GeoFenceClient ):
394
+ self .geofence = client
395
+
396
+ def delete_all_rules (self ):
358
397
"""purge all existing GeoFence Cache Rules"""
359
- rules_objs = self .get_rules ()
398
+ rules_objs = self .geofence . get_rules ()
360
399
rules = rules_objs ["rules" ]
361
400
362
401
batch = Batch ("Purge All" )
363
402
for rule in rules :
364
403
batch .add_delete_rule (rule ["id" ])
365
404
366
405
logger .debug (f"Going to remove all { len (rules )} rules in geofence" )
367
- self .run_batch (batch )
368
-
369
- def purge_layer_rules (self , layer_name : str , workspace : str = None ):
370
- """purge existing GeoFence Cache Rules related to a specific Layer"""
371
- gs_rules = self .get_rules (workspace = workspace , workspace_any = False , layer = layer_name , layer_any = False )
372
-
373
- batch = Batch (f"Purge { workspace } :{ layer_name } " )
374
-
375
- if gs_rules and gs_rules ["rules" ]:
376
- logger .debug (f"Going to remove { len (gs_rules ['rules' ])} rules for layer '{ layer_name } '" )
377
- for r in gs_rules ["rules" ]:
378
- if r ["layer" ] and r ["layer" ] == layer_name :
379
- batch .add_delete_rule (r ["id" ])
380
- else :
381
- logger .debug (f"Bad rule retrieved for dataset '{ layer_name } ': { r } " )
382
- self .run_batch (batch )
406
+ self .geofence .run_batch (batch )
407
+
408
+ def collect_delete_layer_rules (self , workspace_name : str , layer_name : str , batch : Batch = None ) -> Batch :
409
+ """Collect delete operations in a Batch for all rules related to a layer"""
410
+
411
+ try :
412
+ # Scan GeoFence Rules associated to the Dataset
413
+ gs_rules = self .geofence .get_rules (
414
+ workspace = workspace_name , workspace_any = False , layer = layer_name , layer_any = False
415
+ )
416
+
417
+ if not batch :
418
+ batch = Batch (f"Delete { workspace_name } :{ layer_name } " )
419
+
420
+ cnt = 0
421
+ if gs_rules and gs_rules ["rules" ]:
422
+ logger .debug (
423
+ f"Going to collect { len (gs_rules ['rules' ])} rules for layer '{ workspace_name } :{ layer_name } '"
424
+ )
425
+ for r in gs_rules ["rules" ]:
426
+ if r ["layer" ] and r ["layer" ] == layer_name :
427
+ batch .add_delete_rule (r ["id" ])
428
+ cnt += 1
429
+ else :
430
+ logger .warning (f"Bad rule retrieved for dataset '{ workspace_name or '' } :{ layer_name } ': { r } " )
431
+
432
+ logger .debug (f"Adding { cnt } rule deletion operations for '{ workspace_name or '' } :{ layer_name } " )
433
+ return batch
434
+
435
+ except Exception as e :
436
+ logger .error (f"Error collecting rules for { workspace_name } :{ layer_name } " , exc_info = e )
437
+ tb = traceback .format_exc ()
438
+ logger .debug (tb )
439
+
440
+ def delete_layer_rules (self , workspace_name : str , layer_name : str ) -> bool :
441
+ """Delete all Rules related to a specific Layer"""
442
+ try :
443
+ batch = self .collect_delete_layer_rules (workspace_name , layer_name )
444
+ logger .debug (f"Going to remove { batch .length ()} rules for layer { workspace_name } :{ layer_name } " )
445
+ return self .geofence .run_batch (batch )
446
+
447
+ except Exception as e :
448
+ logger .error (f"Error removing rules for { workspace_name } :{ layer_name } " , exc_info = e )
449
+ tb = traceback .format_exc ()
450
+ logger .debug (tb )
451
+ return False
452
+
453
+ def get_first_available_priority (self ):
454
+ """Get the highest Rules priority"""
455
+ try :
456
+ rules_count = self .geofence .get_rules_count ()
457
+ rules_objs = self .geofence .get_rules (page = rules_count - 1 , entries = 1 )
458
+ if len (rules_objs ["rules" ]) > 0 :
459
+ highest_priority = rules_objs ["rules" ][0 ]["priority" ]
460
+ else :
461
+ highest_priority = 0
462
+ return int (highest_priority ) + 1
463
+ except Exception :
464
+ tb = traceback .format_exc ()
465
+ logger .debug (tb )
466
+ return - 1
0 commit comments