synapse 2.172.0__py311-none-any.whl → 2.174.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (52) hide show
  1. synapse/axon.py +1 -1
  2. synapse/common.py +19 -5
  3. synapse/cortex.py +46 -10
  4. synapse/lib/agenda.py +6 -0
  5. synapse/lib/ast.py +1 -1
  6. synapse/lib/hiveauth.py +81 -4
  7. synapse/lib/layer.py +8 -8
  8. synapse/lib/lmdbslab.py +11 -1
  9. synapse/lib/modelrev.py +17 -1
  10. synapse/lib/modules.py +1 -0
  11. synapse/lib/msgpack.py +25 -3
  12. synapse/lib/nexus.py +26 -22
  13. synapse/lib/schemas.py +31 -0
  14. synapse/lib/stormsvc.py +30 -11
  15. synapse/lib/stormtypes.py +23 -9
  16. synapse/lib/trigger.py +0 -4
  17. synapse/lib/version.py +2 -2
  18. synapse/lib/view.py +2 -0
  19. synapse/models/crypto.py +22 -0
  20. synapse/models/economic.py +23 -2
  21. synapse/models/entity.py +16 -0
  22. synapse/models/files.py +4 -1
  23. synapse/models/geopol.py +3 -0
  24. synapse/models/orgs.py +7 -5
  25. synapse/models/person.py +5 -2
  26. synapse/models/planning.py +5 -0
  27. synapse/tests/test_cortex.py +13 -0
  28. synapse/tests/test_lib_agenda.py +129 -1
  29. synapse/tests/test_lib_ast.py +21 -0
  30. synapse/tests/test_lib_grammar.py +4 -0
  31. synapse/tests/test_lib_hiveauth.py +139 -1
  32. synapse/tests/test_lib_httpapi.py +1 -0
  33. synapse/tests/test_lib_layer.py +67 -13
  34. synapse/tests/test_lib_lmdbslab.py +16 -1
  35. synapse/tests/test_lib_modelrev.py +57 -0
  36. synapse/tests/test_lib_msgpack.py +58 -8
  37. synapse/tests/test_lib_nexus.py +44 -1
  38. synapse/tests/test_lib_storm.py +7 -7
  39. synapse/tests/test_lib_stormsvc.py +128 -51
  40. synapse/tests/test_lib_stormtypes.py +43 -4
  41. synapse/tests/test_lib_trigger.py +23 -4
  42. synapse/tests/test_model_crypto.py +6 -0
  43. synapse/tests/test_model_economic.py +14 -1
  44. synapse/tests/test_model_geopol.py +3 -0
  45. synapse/tests/test_model_orgs.py +8 -2
  46. synapse/tests/test_model_person.py +2 -0
  47. synapse/tools/changelog.py +236 -0
  48. {synapse-2.172.0.dist-info → synapse-2.174.0.dist-info}/METADATA +1 -1
  49. {synapse-2.172.0.dist-info → synapse-2.174.0.dist-info}/RECORD +52 -50
  50. {synapse-2.172.0.dist-info → synapse-2.174.0.dist-info}/WHEEL +1 -1
  51. {synapse-2.172.0.dist-info → synapse-2.174.0.dist-info}/LICENSE +0 -0
  52. {synapse-2.172.0.dist-info → synapse-2.174.0.dist-info}/top_level.txt +0 -0
synapse/lib/stormsvc.py CHANGED
@@ -4,6 +4,7 @@ import logging
4
4
  import synapse.telepath as s_telepath
5
5
 
6
6
  import synapse.lib.base as s_base
7
+ import synapse.lib.hashitem as s_hashitem
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
@@ -139,32 +140,50 @@ class StormSvcClient(s_base.Base):
139
140
  self.core.svcsbysvcname[self.svcname] = self
140
141
 
141
142
  await self.core.feedBeholder('svc:set', {'name': self.name, 'iden': self.iden, 'svcname': self.svcname, 'version': self.svcvers})
143
+ # if the old service is the same as the new service, just skip
142
144
 
143
- try:
144
- await self.core._delStormSvcPkgs(self.iden)
145
-
146
- except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only
147
- raise
148
-
149
- except Exception:
150
- logger.exception(f'_delStormSvcPkgs failed for service {self.name} ({self.iden})')
145
+ oldpkgs = self.core.getStormSvcPkgs(self.iden)
146
+ byname = {}
147
+ done = set()
148
+ for pdef in oldpkgs:
149
+ iden = s_hashitem.hashitem(pdef)
150
+ byname[pdef.get('name')] = iden
151
151
 
152
152
  # Register new packages
153
153
  for pdef in self.info.get('pkgs', ()):
154
-
155
154
  try:
156
- # push the svciden in the package metadata for later reference.
157
155
  pdef['svciden'] = self.iden
158
156
  await self.core._normStormPkg(pdef)
157
+ except Exception:
158
+ name = pdef.get('name')
159
+ logger.exception(f'normStormPkg ({name}) failed for service {self.name} ({self.iden})')
160
+ continue
161
+
162
+ name = pdef.get('name')
163
+ iden = s_hashitem.hashitem(pdef)
164
+
165
+ done.add(name)
166
+ if name in byname:
167
+ if byname[name] != iden:
168
+ await self.core._delStormPkg(name) # we're updating an old package, so delete the old and then re-add
169
+ else:
170
+ continue # pkg unchanged. Can skip.
171
+
172
+ try:
173
+ # push the svciden in the package metadata for later reference.
159
174
  await self.core._addStormPkg(pdef)
160
175
 
161
176
  except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only
162
177
  raise
163
178
 
164
179
  except Exception:
165
- name = pdef.get('name')
166
180
  logger.exception(f'addStormPkg ({name}) failed for service {self.name} ({self.iden})')
167
181
 
182
+ # clean up any packages that no longer exist
183
+ for name in byname.keys():
184
+ if name not in done:
185
+ await self.core._delStormPkg(name)
186
+
168
187
  # Set events and fire as needed
169
188
  evts = self.info.get('evts')
170
189
  try:
synapse/lib/stormtypes.py CHANGED
@@ -18,7 +18,6 @@ import calendar
18
18
  import functools
19
19
  import contextlib
20
20
  import collections
21
- from typing import Any
22
21
 
23
22
  import synapse.exc as s_exc
24
23
  import synapse.common as s_common
@@ -8440,7 +8439,9 @@ class Trigger(Prim):
8440
8439
 
8441
8440
  async def set(self, name, valu):
8442
8441
  trigiden = self.valu.get('iden')
8443
- viewiden = self.runt.snap.view.iden
8442
+ viewiden = self.valu.get('view')
8443
+
8444
+ view = self.runt.snap.core.reqView(viewiden)
8444
8445
 
8445
8446
  name = await tostr(name)
8446
8447
  if name in ('async', 'enabled', ):
@@ -8449,11 +8450,11 @@ class Trigger(Prim):
8449
8450
  valu = await tostr(valu)
8450
8451
 
8451
8452
  if name == 'user':
8452
- self.runt.user.confirm(('trigger', 'set', 'user'))
8453
+ self.runt.confirm(('trigger', 'set', 'user'))
8453
8454
  else:
8454
- self.runt.user.confirm(('trigger', 'set', name), gateiden=viewiden)
8455
+ self.runt.confirm(('trigger', 'set', name), gateiden=viewiden)
8455
8456
 
8456
- await self.runt.snap.view.setTriggerInfo(trigiden, name, valu)
8457
+ await view.setTriggerInfo(trigiden, name, valu)
8457
8458
 
8458
8459
  self.valu[name] = valu
8459
8460
 
@@ -8825,6 +8826,8 @@ class LibCron(Lib):
8825
8826
  'desc': 'Permits a user to create a cron job.'},
8826
8827
  {'perm': ('cron', 'del'), 'gate': 'cronjob',
8827
8828
  'desc': 'Permits a user to remove a cron job.'},
8829
+ {'perm': ('cron', 'kill'), 'gate': 'cronjob',
8830
+ 'desc': 'Controls the ability to terminate a running cron job.'},
8828
8831
  {'perm': ('cron', 'get'), 'gate': 'cronjob',
8829
8832
  'desc': 'Permits a user to list cron jobs.'},
8830
8833
  {'perm': ('cron', 'set'), 'gate': 'cronjob',
@@ -9276,14 +9279,19 @@ class CronJob(Prim):
9276
9279
  {'name': 'valu', 'type': 'any', 'desc': 'The value to set on the definition.', },
9277
9280
  ),
9278
9281
  'returns': {'type': 'cronjob', 'desc': 'The ``cronjob``', }}},
9282
+
9283
+ {'name': 'kill', 'desc': 'If the job is currently running, terminate the task.',
9284
+ 'type': {'type': 'function', '_funcname': '_methCronJobKill',
9285
+ 'returns': {'type': 'boolean', 'desc': 'A boolean value which is true if the task was terminated.'}}},
9286
+
9279
9287
  {'name': 'pack', 'desc': 'Get the Cronjob definition.',
9280
9288
  'type': {'type': 'function', '_funcname': '_methCronJobPack',
9281
- 'returns': {'type': 'dict', 'desc': 'The definition.', }}},
9289
+ 'returns': {'type': 'dict', 'desc': 'The definition.'}}},
9282
9290
  {'name': 'pprint', 'desc': 'Get a dictionary containing user friendly strings for printing the CronJob.',
9283
9291
  'type': {'type': 'function', '_funcname': '_methCronJobPprint',
9284
9292
  'returns':
9285
9293
  {'type': 'dict',
9286
- 'desc': 'A dictionary containing structured data about a cronjob for display purposes.', }}},
9294
+ 'desc': 'A dictionary containing structured data about a cronjob for display purposes.'}}},
9287
9295
  )
9288
9296
  _storm_typename = 'cronjob'
9289
9297
  _ismutable = False
@@ -9300,10 +9308,16 @@ class CronJob(Prim):
9300
9308
  def getObjLocals(self):
9301
9309
  return {
9302
9310
  'set': self._methCronJobSet,
9311
+ 'kill': self._methCronJobKill,
9303
9312
  'pack': self._methCronJobPack,
9304
9313
  'pprint': self._methCronJobPprint,
9305
9314
  }
9306
9315
 
9316
+ async def _methCronJobKill(self):
9317
+ iden = self.valu.get('iden')
9318
+ self.runt.confirm(('cron', 'kill'), gateiden=iden)
9319
+ return await self.runt.snap.core.killCronTask(iden)
9320
+
9307
9321
  async def _methCronJobSet(self, name, valu):
9308
9322
  name = await tostr(name)
9309
9323
  valu = await toprim(valu)
@@ -9312,9 +9326,9 @@ class CronJob(Prim):
9312
9326
  if name == 'creator':
9313
9327
  # this permission must be granted cortex wide
9314
9328
  # to prevent abuse...
9315
- self.runt.user.confirm(('cron', 'set', 'creator'))
9329
+ self.runt.confirm(('cron', 'set', 'creator'))
9316
9330
  else:
9317
- self.runt.user.confirm(('cron', 'set', name), gateiden=iden)
9331
+ self.runt.confirm(('cron', 'set', name), gateiden=iden)
9318
9332
 
9319
9333
  self.valu = await self.runt.snap.core.editCronJob(iden, name, valu)
9320
9334
 
synapse/lib/trigger.py CHANGED
@@ -522,10 +522,6 @@ class Trigger:
522
522
  logger.warning(f'Skipping trigger execution because user {self.user.iden} is locked')
523
523
  return
524
524
 
525
- tag = self.tdef.get('tag')
526
- cond = self.tdef.get('cond')
527
- form = self.tdef.get('form')
528
- prop = self.tdef.get('prop')
529
525
  storm = self.tdef.get('storm')
530
526
 
531
527
  query = await self.view.core.getStormQuery(storm)
synapse/lib/version.py CHANGED
@@ -223,6 +223,6 @@ def reqVersion(valu, reqver,
223
223
  ##############################################################################
224
224
  # The following are touched during the release process by bumpversion.
225
225
  # Do not modify these directly.
226
- version = (2, 172, 0)
226
+ version = (2, 174, 0)
227
227
  verstring = '.'.join([str(x) for x in version])
228
- commit = '3e33d8a8cbdfd0f4f6f9a31d664578d817d9ccb8'
228
+ commit = 'cc04ec913185e0c1ef5a94958d6eb40eb288f38f'
synapse/lib/view.py CHANGED
@@ -1518,6 +1518,8 @@ class View(s_nexus.Pusher): # type: ignore
1518
1518
  elif self.triggers.get(iden) is not None:
1519
1519
  raise s_exc.DupIden(mesg='A trigger with this iden already exists')
1520
1520
 
1521
+ tdef['view'] = self.iden
1522
+
1521
1523
  root = await self.core.auth.getUserByName('root')
1522
1524
 
1523
1525
  tdef.setdefault('created', s_common.now())
synapse/models/crypto.py CHANGED
@@ -371,31 +371,53 @@ class CryptoModule(s_module.CoreModule):
371
371
  ('crypto:algorithm', {}, ()),
372
372
 
373
373
  ('crypto:key', {}, (
374
+
374
375
  ('algorithm', ('crypto:algorithm', {}), {
375
376
  'ex': 'aes256',
376
377
  'doc': 'The cryptographic algorithm which uses the key material.'}),
378
+
377
379
  ('mode', ('str', {'lower': True, 'onespace': True}), {
378
380
  'doc': 'The algorithm specific mode in use.'}),
381
+
379
382
  ('iv', ('hex', {}), {
380
383
  'doc': 'The hex encoded initialization vector.'}),
384
+
385
+ ('iv:text', ('it:dev:str', {}), {
386
+ 'doc': 'Set only if the :iv property decodes to ASCII.'}),
387
+
381
388
  ('public', ('hex', {}), {
382
389
  'doc': 'The hex encoded public key material if the algorithm has a public/private key pair.'}),
390
+
391
+ ('public:text', ('it:dev:str', {}), {
392
+ 'doc': 'Set only if the :public property decodes to ASCII.'}),
393
+
383
394
  ('public:md5', ('hash:md5', {}), {
384
395
  'doc': 'The MD5 hash of the public key in raw binary form.'}),
396
+
385
397
  ('public:sha1', ('hash:sha1', {}), {
386
398
  'doc': 'The SHA1 hash of the public key in raw binary form.'}),
399
+
387
400
  ('public:sha256', ('hash:sha256', {}), {
388
401
  'doc': 'The SHA256 hash of the public key in raw binary form.'}),
402
+
389
403
  ('private', ('hex', {}), {
390
404
  'doc': 'The hex encoded private key material. All symmetric keys are private.'}),
405
+
406
+ ('private:text', ('it:dev:str', {}), {
407
+ 'doc': 'Set only if the :private property decodes to ASCII.'}),
408
+
391
409
  ('private:md5', ('hash:md5', {}), {
392
410
  'doc': 'The MD5 hash of the private key in raw binary form.'}),
411
+
393
412
  ('private:sha1', ('hash:sha1', {}), {
394
413
  'doc': 'The SHA1 hash of the private key in raw binary form.'}),
414
+
395
415
  ('private:sha256', ('hash:sha256', {}), {
396
416
  'doc': 'The SHA256 hash of the private key in raw binary form.'}),
417
+
397
418
  ('seed:passwd', ('inet:passwd', {}), {
398
419
  'doc': 'The seed password used to generate the key material.'}),
420
+
399
421
  ('seed:algorithm', ('crypto:algorithm', {}), {
400
422
  'ex': 'pbkdf2',
401
423
  'doc': 'The algorithm used to generate the key from the seed password.'})
@@ -48,11 +48,11 @@ class EconModule(s_module.CoreModule):
48
48
  'doc': 'An invoice issued requesting payment.'}),
49
49
 
50
50
  ('econ:price', ('hugenum', {'norm': False}), {
51
- 'doc': 'The amount of money expected, required, or given in payment for something',
51
+ 'doc': 'The amount of money expected, required, or given in payment for something.',
52
52
  'ex': '2.20'}),
53
53
 
54
54
  ('econ:currency', ('str', {'lower': True, 'strip': False}), {
55
- 'doc': 'The name of a system of money in general use',
55
+ 'doc': 'The name of a system of money in general use.',
56
56
  'ex': 'usd'}),
57
57
 
58
58
  ('econ:fin:exchange', ('guid', {}), {
@@ -99,6 +99,7 @@ class EconModule(s_module.CoreModule):
99
99
 
100
100
  'forms': (
101
101
 
102
+ ('econ:currency', {}, ()),
102
103
  ('econ:pay:iin', {}, (
103
104
 
104
105
  ('org', ('ou:org', {}), {
@@ -205,6 +206,9 @@ class EconModule(s_module.CoreModule):
205
206
  ('fee', ('econ:price', {}), {
206
207
  'doc': 'The transaction fee paid by the recipient to the payment processor.'}),
207
208
 
209
+ ('from:cash', ('bool', {}), {
210
+ 'doc': 'Set to true if the payment input was in cash.'}),
211
+
208
212
  ('from:account', ('econ:bank:account', {}), {
209
213
  'doc': 'The bank account which made the payment.'}),
210
214
 
@@ -220,6 +224,9 @@ class EconModule(s_module.CoreModule):
220
224
  ('from:contact', ('ps:contact', {}), {
221
225
  'doc': 'Contact information for the entity making the payment.'}),
222
226
 
227
+ ('to:cash', ('bool', {}), {
228
+ 'doc': 'Set to true if the payment output was in cash.'}),
229
+
223
230
  ('to:account', ('econ:bank:account', {}), {
224
231
  'doc': 'The bank account which received the payment.'}),
225
232
 
@@ -256,6 +263,20 @@ class EconModule(s_module.CoreModule):
256
263
  ('receipt', ('econ:acct:receipt', {}), {
257
264
  'doc': 'The receipt that was issued for the payment.'}),
258
265
 
266
+ ('place', ('geo:place', {}), {
267
+ 'doc': 'The place where the payment occurred.'}),
268
+
269
+ ('place:name', ('geo:name', {}), {
270
+ 'doc': 'The name of the place where the payment occurred.'}),
271
+
272
+ ('place:address', ('geo:address', {}), {
273
+ 'doc': 'The address of the place where the payment occurred.'}),
274
+
275
+ ('place:loc', ('loc', {}), {
276
+ 'doc': 'The loc of the place where the payment occurred.'}),
277
+
278
+ ('place:latlong', ('geo:latlong', {}), {
279
+ 'doc': 'The latlong where the payment occurred.'}),
259
280
  )),
260
281
 
261
282
  ('econ:acct:balance', {}, (
@@ -0,0 +1,16 @@
1
+ import synapse.lib.module as s_module
2
+
3
+ class EntityModule(s_module.CoreModule):
4
+
5
+ def getModelDefs(self):
6
+ return (('entity', {
7
+
8
+ 'types': (
9
+ ('entity:name', ('str', {'onespace': True, 'lower': True}), {
10
+ 'doc': 'A name used to refer to an entity.'}),
11
+ ),
12
+
13
+ 'forms': (
14
+ ('entity:name', {}, ()),
15
+ ),
16
+ }),)
synapse/models/files.py CHANGED
@@ -412,7 +412,10 @@ class FileModule(s_module.CoreModule):
412
412
  'doc': 'The GUID of the metadata pulled from a Windows shortcut or LNK file.',
413
413
  }),
414
414
  ),
415
-
415
+ 'edges': (
416
+ (('file:bytes', 'refs', 'it:dev:str'), {
417
+ 'doc': 'The source file contains the target string.'}),
418
+ ),
416
419
  'forms': (
417
420
 
418
421
  ('file:bytes', {}, (
synapse/models/geopol.py CHANGED
@@ -88,6 +88,9 @@ class PolModule(s_module.CoreModule):
88
88
 
89
89
  ('vitals', ('pol:vitals', {}), {
90
90
  'doc': 'The most recent known vitals for the country.'}),
91
+
92
+ ('currencies', ('array', {'type': 'econ:currency', 'sorted': True, 'uniq': True}), {
93
+ 'doc': 'The official currencies used in the country.'}),
91
94
  )),
92
95
  ('pol:immigration:status:type:taxonomy', {}, ()),
93
96
  ('pol:immigration:status', {}, (
synapse/models/orgs.py CHANGED
@@ -731,8 +731,7 @@ class OuModule(s_module.CoreModule):
731
731
  ('contact', ('ps:contact', {}), {
732
732
  'doc': 'The contact info for the person who holds the position.',
733
733
  }),
734
- # TODO migrate to ou:jobtitle
735
- ('title', ('str', {'lower': True, 'onespace': True}), {
734
+ ('title', ('ou:jobtitle', {}), {
736
735
  'doc': 'The title of the position.',
737
736
  }),
738
737
  ('reports', ('array', {'type': 'ou:position', 'uniq': True, 'sorted': True}), {
@@ -1024,10 +1023,13 @@ class OuModule(s_module.CoreModule):
1024
1023
  ('sponsors', ('array', {'type': 'ps:contact', 'uniq': True, 'sorted': True}), {
1025
1024
  'doc': 'An array of contacts which sponsored the conference.',
1026
1025
  }),
1027
- ('name', ('str', {'lower': True}), {
1026
+ ('name', ('entity:name', {}), {
1028
1027
  'doc': 'The full name of the conference.',
1029
- 'ex': 'decfon 2017',
1030
- }),
1028
+ 'ex': 'defcon 2017'}),
1029
+
1030
+ ('names', ('array', {'type': 'entity:name', 'uniq': True, 'sorted': True}), {
1031
+ 'doc': 'An array of alternate names for the conference.'}),
1032
+
1031
1033
  ('desc', ('str', {'lower': True}), {
1032
1034
  'doc': 'A description of the conference.',
1033
1035
  'ex': 'annual cybersecurity conference',
synapse/models/person.py CHANGED
@@ -356,8 +356,11 @@ class PsModule(s_module.CoreModule):
356
356
  'doc': 'A description of this contact.',
357
357
  }),
358
358
  ('title', ('ou:jobtitle', {}), {
359
- 'doc': 'The job/org title listed for this contact.',
360
- }),
359
+ 'doc': 'The job/org title listed for this contact.'}),
360
+
361
+ ('titles', ('array', {'type': 'ou:jobtitle', 'sorted': True, 'uniq': True}), {
362
+ 'doc': 'An array of alternate titles for the contact.'}),
363
+
361
364
  ('photo', ('file:bytes', {}), {
362
365
  'doc': 'The photo listed for this contact.',
363
366
  }),
@@ -28,6 +28,11 @@ class PlanModule(s_module.CoreModule):
28
28
  'doc': 'A link between steps in a procedure.'}),
29
29
  ),
30
30
 
31
+ 'edges': (
32
+ (('plan:procedure:step', 'uses', None), {
33
+ 'doc': 'The step in the procedure makes use of the target node.'}),
34
+ ),
35
+
31
36
  'forms': (
32
37
  ('plan:system', {}, (
33
38
 
@@ -269,6 +269,19 @@ class CortexTest(s_t_utils.SynTest):
269
269
  self.eq(await core00.getJsonObj('foo/bar'), 'zoinks')
270
270
  self.eq(await core01.getJsonObj('foo/bar'), 'zoinks')
271
271
 
272
+ # Test startup sequencing. We must create the child cells prior to
273
+ # the nexus recover() call from occuring :)
274
+ with self.getTestDir() as dirn:
275
+ async with self.getTestCore(dirn=dirn) as core:
276
+ await core.callStorm('$lib.jsonstor.set((path,), hehe)')
277
+
278
+ with self.getAsyncLoggerStream('synapse.lib.nexus') as stream:
279
+ async with self.getTestCore(dirn=dirn) as core:
280
+ q = 'return( $lib.jsonstor.get((path,)) )'
281
+ self.eq('hehe', await core.callStorm(q))
282
+ stream.seek(0)
283
+ self.notin('Exception while replaying log', stream.read())
284
+
272
285
  async def test_cortex_layer_mirror(self):
273
286
 
274
287
  # test a layer mirror from a layer
@@ -331,7 +331,17 @@ class AgendaTest(s_t_utils.SynTest):
331
331
  appt = await agenda.get(guid)
332
332
  self.eq(appt.isrunning, True)
333
333
  self.eq(core.view.iden, appt.task.info.get('view'))
334
- await appt.task.kill()
334
+
335
+ self.true(await core._killCronTask(guid))
336
+
337
+ events = [
338
+ {'event': 'cron:stop', 'info': {'iden': appt.iden}},
339
+ ]
340
+
341
+ task = core.schedCoro(s_t_utils.waitForBehold(core, events))
342
+ await asyncio.wait_for(task, timeout=5)
343
+
344
+ self.false(await core._killCronTask(guid))
335
345
 
336
346
  appt = await agenda.get(guid)
337
347
  self.eq(appt.isrunning, False)
@@ -798,3 +808,121 @@ class AgendaTest(s_t_utils.SynTest):
798
808
  stream.seek(0)
799
809
  data = stream.read()
800
810
  self.isin("_Appt.edits() Invalid attribute received: invalid = 'newp'", data)
811
+
812
+ async def test_cron_kill(self):
813
+ async with self.getTestCore() as core:
814
+
815
+ data = []
816
+ evt = asyncio.Event()
817
+
818
+ async def task():
819
+ async for mesg in core.behold():
820
+ data.append(mesg)
821
+ if mesg.get('event') == 'cron:stop':
822
+ evt.set()
823
+
824
+ core.schedCoro(task())
825
+
826
+ q = '$q=$lib.queue.gen(test) for $i in $lib.range(60) { $lib.time.sleep(0.1) $q.put($i) }'
827
+ guid = s_common.guid()
828
+ cdef = {
829
+ 'creator': core.auth.rootuser.iden, 'iden': guid,
830
+ 'storm': q,
831
+ 'reqs': {'now': True}
832
+ }
833
+ await core.addCronJob(cdef)
834
+
835
+ q = '$q=$lib.queue.gen(test) for $valu in $q.get((0), wait=(true)) { return ($valu) }'
836
+ valu = await core.callStorm(q)
837
+ self.eq(valu, 0)
838
+
839
+ opts = {'vars': {'iden': guid}}
840
+ get_cron = 'return($lib.cron.get($iden).pack())'
841
+ cdef = await core.callStorm(get_cron, opts=opts)
842
+ self.true(cdef.get('isrunning'))
843
+
844
+ self.true(await core.callStorm('return($lib.cron.get($iden).kill())', opts=opts))
845
+
846
+ self.true(await asyncio.wait_for(evt.wait(), timeout=12))
847
+
848
+ cdef = await core.callStorm(get_cron, opts=opts)
849
+ self.false(cdef.get('isrunning'))
850
+
851
+ async def test_cron_kill_pool(self):
852
+
853
+ async with self.getTestAhaProv() as aha:
854
+
855
+ import synapse.cortex as s_cortex
856
+ import synapse.lib.base as s_base
857
+
858
+ async with await s_base.Base.anit() as base:
859
+
860
+ with self.getTestDir() as dirn:
861
+
862
+ dirn00 = s_common.genpath(dirn, 'cell00')
863
+ dirn01 = s_common.genpath(dirn, 'cell01')
864
+
865
+ core00 = await base.enter_context(self.addSvcToAha(aha, '00.core', s_cortex.Cortex, dirn=dirn00))
866
+ provinfo = {'mirror': '00.core'}
867
+ core01 = await base.enter_context(self.addSvcToAha(aha, '01.core', s_cortex.Cortex, dirn=dirn01, provinfo=provinfo))
868
+
869
+ self.len(1, await core00.nodes('[inet:asn=0]'))
870
+ await core01.sync()
871
+ self.len(1, await core01.nodes('inet:asn=0'))
872
+
873
+ msgs = await core00.stormlist('aha.pool.add pool00...')
874
+ self.stormHasNoWarnErr(msgs)
875
+ self.stormIsInPrint('Created AHA service pool: pool00.loop.vertex.link', msgs)
876
+
877
+ msgs = await core00.stormlist('aha.pool.svc.add pool00... 01.core...')
878
+ self.stormHasNoWarnErr(msgs)
879
+ self.stormIsInPrint('AHA service (01.core...) added to service pool (pool00.loop.vertex.link)', msgs)
880
+
881
+ msgs = await core00.stormlist('cortex.storm.pool.set --connection-timeout 1 --sync-timeout 1 aha://pool00...')
882
+ self.stormHasNoWarnErr(msgs)
883
+ self.stormIsInPrint('Storm pool configuration set.', msgs)
884
+
885
+ await core00.stormpool.waitready(timeout=12)
886
+
887
+ data = []
888
+ evt = asyncio.Event()
889
+
890
+ async def task():
891
+ async for mesg in core00.behold():
892
+ data.append(mesg)
893
+ if mesg.get('event') == 'cron:stop':
894
+ evt.set()
895
+
896
+ core00.schedCoro(task())
897
+
898
+ q = '$q=$lib.queue.gen(test) for $i in $lib.range(60) { $lib.time.sleep(0.1) $q.put($i) }'
899
+ guid = s_common.guid()
900
+ cdef = {
901
+ 'creator': core00.auth.rootuser.iden, 'iden': guid,
902
+ 'storm': q,
903
+ 'reqs': {'NOW': True},
904
+ 'pool': True,
905
+ }
906
+ await core00.addCronJob(cdef)
907
+
908
+ q = '$q=$lib.queue.gen(test) for $valu in $q.get((0), wait=(true)) { return ($valu) }'
909
+ valu = await core00.callStorm(q, opts={'mirror': False})
910
+ self.eq(valu, 0)
911
+
912
+ opts = {'vars': {'iden': guid}, 'mirror': False}
913
+ get_cron = 'return($lib.cron.get($iden).pack())'
914
+ cdef = await core00.callStorm(get_cron, opts=opts)
915
+ self.true(cdef.get('isrunning'))
916
+
917
+ self.true(await core00.callStorm('return($lib.cron.get($iden).kill())', opts=opts))
918
+
919
+ self.true(await asyncio.wait_for(evt.wait(), timeout=12))
920
+
921
+ cdef00 = await core00.callStorm(get_cron, opts=opts)
922
+ self.false(cdef00.get('isrunning'))
923
+
924
+ cdef01 = await core01.callStorm(get_cron, opts=opts)
925
+ self.false(cdef01.get('isrunning'))
926
+ self.eq(cdef01.get('lastresult'), 'cancelled')
927
+ self.gt(cdef00['laststarttime'], 0)
928
+ self.eq(cdef00['laststarttime'], cdef01['laststarttime'])
@@ -356,6 +356,27 @@ class AstTest(s_test.SynTest):
356
356
  self.len(1, nodes)
357
357
  self.none(nodes[0].get('.seen'))
358
358
 
359
+ # array var filter
360
+ q = '''
361
+ [(test:arrayprop=* :strs=(neato, burrito))
362
+ (test:arrayprop=* :ints=(1,2,3,4,5))] |
363
+ $pvar = "strs"
364
+ +:$pvar*[=neato]
365
+ '''
366
+ nodes = await core.nodes(q)
367
+ self.len(1, nodes)
368
+ self.eq(('neato', 'burrito'), nodes[0].get('strs'))
369
+
370
+ q = '$pvar=ints $avar=4 test:arrayprop +:$pvar*[=$avar]'
371
+ nodes = await core.nodes(q)
372
+ self.len(1, nodes)
373
+ self.eq((1, 2, 3, 4, 5), nodes[0].get('ints'))
374
+
375
+ q = '$pvar=strs $avar=burr test:arrayprop +:$pvar*[^=$avar]'
376
+ nodes = await core.nodes(q)
377
+ self.len(1, nodes)
378
+ self.eq(('neato', 'burrito'), nodes[0].get('strs'))
379
+
359
380
  # Sad paths
360
381
  q = '[test:str=newp -.newp]'
361
382
  await self.asyncraises(s_exc.NoSuchProp, core.nodes(q))
@@ -721,6 +721,8 @@ Queries = [
721
721
  '$foo=({"bar": null})',
722
722
  '$p="names" ps:contact:name=foo [ :$p?+=bar ]',
723
723
  '$p="names" ps:contact:name=foo [ :$p?-=bar ]',
724
+ '$pvar=stuff test:arrayprop +:$pvar*[=neato]',
725
+ '$pvar=ints test:arrayprop +:$pvar*[=$othervar]',
724
726
  ]
725
727
 
726
728
  # Generated with print_parse_list below
@@ -1346,6 +1348,8 @@ _ParseResults = [
1346
1348
  'Query: [SetVarOper: [Const: foo, DollarExpr: [ExprDict: [Const: bar, Const: None]]]]',
1347
1349
  'Query: [SetVarOper: [Const: p, Const: names], LiftPropBy: [Const: ps:contact:name, Const: =, Const: foo], EditPropSet: [RelProp: [VarValue: [Const: p]], Const: ?+=, Const: bar]]',
1348
1350
  'Query: [SetVarOper: [Const: p, Const: names], LiftPropBy: [Const: ps:contact:name, Const: =, Const: foo], EditPropSet: [RelProp: [VarValue: [Const: p]], Const: ?-=, Const: bar]]',
1351
+ 'Query: [SetVarOper: [Const: pvar, Const: stuff], LiftProp: [Const: test:arrayprop], FiltOper: [Const: +, ArrayCond: [RelProp: [VarValue: [Const: pvar]], Const: =, Const: neato]]]',
1352
+ 'Query: [SetVarOper: [Const: pvar, Const: ints], LiftProp: [Const: test:arrayprop], FiltOper: [Const: +, ArrayCond: [RelProp: [VarValue: [Const: pvar]], Const: =, VarValue: [Const: othervar]]]]',
1349
1353
  ]
1350
1354
 
1351
1355
  class GrammarTest(s_t_utils.SynTest):