synapse 2.195.0__py311-none-any.whl → 2.196.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 (46) hide show
  1. synapse/axon.py +72 -5
  2. synapse/common.py +23 -0
  3. synapse/cortex.py +1 -0
  4. synapse/daemon.py +1 -0
  5. synapse/lib/aha.py +159 -4
  6. synapse/lib/cell.py +133 -8
  7. synapse/lib/jsonstor.py +2 -1
  8. synapse/lib/modelrev.py +5 -1
  9. synapse/lib/nexus.py +13 -6
  10. synapse/lib/reflect.py +4 -5
  11. synapse/lib/snap.py +14 -7
  12. synapse/lib/stormlib/aha.py +351 -1
  13. synapse/lib/stormlib/utils.py +37 -0
  14. synapse/lib/stormtypes.py +124 -6
  15. synapse/lib/version.py +2 -2
  16. synapse/models/base.py +3 -0
  17. synapse/models/infotech.py +55 -16
  18. synapse/models/orgs.py +14 -10
  19. synapse/models/risk.py +23 -10
  20. synapse/models/transport.py +8 -3
  21. synapse/telepath.py +12 -0
  22. synapse/tests/test_axon.py +23 -0
  23. synapse/tests/test_common.py +28 -0
  24. synapse/tests/test_datamodel.py +8 -0
  25. synapse/tests/test_lib_aha.py +241 -0
  26. synapse/tests/test_lib_cell.py +61 -0
  27. synapse/tests/test_lib_jsonstor.py +1 -0
  28. synapse/tests/test_lib_modelrev.py +6 -0
  29. synapse/tests/test_lib_nexus.py +24 -2
  30. synapse/tests/test_lib_stormlib_aha.py +188 -0
  31. synapse/tests/test_lib_stormlib_utils.py +14 -0
  32. synapse/tests/test_lib_stormtypes.py +90 -3
  33. synapse/tests/test_model_base.py +2 -0
  34. synapse/tests/test_model_infotech.py +28 -1
  35. synapse/tests/test_model_orgs.py +2 -0
  36. synapse/tests/test_model_risk.py +2 -0
  37. synapse/tests/test_model_transport.py +1 -0
  38. synapse/tests/test_telepath.py +26 -0
  39. synapse/tests/test_tools_aha.py +192 -0
  40. synapse/tools/aha/mirror.py +193 -0
  41. synapse/tools/changelog.py +32 -27
  42. {synapse-2.195.0.dist-info → synapse-2.196.0.dist-info}/METADATA +1 -1
  43. {synapse-2.195.0.dist-info → synapse-2.196.0.dist-info}/RECORD +46 -43
  44. {synapse-2.195.0.dist-info → synapse-2.196.0.dist-info}/LICENSE +0 -0
  45. {synapse-2.195.0.dist-info → synapse-2.196.0.dist-info}/WHEEL +0 -0
  46. {synapse-2.195.0.dist-info → synapse-2.196.0.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ import bz2
3
3
  import gzip
4
4
  import json
5
5
  import base64
6
+ import struct
6
7
  import asyncio
7
8
  import hashlib
8
9
  import binascii
@@ -6467,13 +6468,29 @@ words\tword\twrd'''
6467
6468
  async with self.getTestCore() as core:
6468
6469
 
6469
6470
  viewiden = await core.callStorm('return($lib.view.get().fork().iden)')
6470
- await core.nodes('[ ou:org=* :name=foobar +#hehe ]')
6471
+ q = '[ ou:org=* :name=foobar +#hehe ] $node.data.set(foo, bar) return($node.iden())'
6472
+ basenode = await core.callStorm(q)
6471
6473
 
6472
6474
  opts = {'view': viewiden}
6473
- nodeiden = await core.callStorm('[ ou:org=* :name=foobar +#hehe ] return($node.iden())', opts=opts)
6475
+ nodeiden = await core.callStorm('''
6476
+ [ ou:org=* :name=foobar +#hehe ]
6477
+ $node.data.set(foo, bar)
6478
+ return($node.iden())
6479
+ ''', opts=opts)
6480
+
6481
+ nodeiden2 = await core.callStorm('[test:str=yup] $node.data.set(foo, yup) return ($node.iden())',
6482
+ opts=opts)
6474
6483
 
6475
6484
  self.len(2, await core.nodes('ou:org +:name=foobar +#hehe', opts=opts))
6476
6485
 
6486
+ nodes = await core.nodes('yield $lib.layer.get().liftByNodeData(foo)', opts=opts)
6487
+ self.len(2, nodes)
6488
+ self.eq(set((nodeiden, nodeiden2)), {node.iden() for node in nodes})
6489
+
6490
+ nodes = await core.nodes('yield $lib.layer.get().liftByNodeData(foo)')
6491
+ self.len(1, nodes)
6492
+ self.eq(nodes[0].iden(), basenode)
6493
+
6477
6494
  nodes = await core.nodes('yield $lib.layer.get().liftByProp(ou:org)', opts=opts)
6478
6495
  self.len(1, nodes)
6479
6496
  self.eq(nodes[0].iden(), nodeiden)
@@ -6487,7 +6504,7 @@ words\tword\twrd'''
6487
6504
  self.eq(nodes[0].iden(), nodeiden)
6488
6505
 
6489
6506
  nodes = await core.nodes('yield $lib.layer.get().liftByProp(".created")', opts=opts)
6490
- self.len(1, nodes)
6507
+ self.len(2, nodes)
6491
6508
  self.eq(nodes[0].iden(), nodeiden)
6492
6509
 
6493
6510
  nodes = await core.nodes('yield $lib.layer.get().liftByTag(hehe)', opts=opts)
@@ -7084,3 +7101,73 @@ words\tword\twrd'''
7084
7101
 
7085
7102
  with self.raises(s_exc.BadState):
7086
7103
  await core.callStorm(merging)
7104
+
7105
+ async def test_storm_lib_axon_read_unpack(self):
7106
+
7107
+ async with self.getTestCore() as core:
7108
+
7109
+ visi = await core.auth.addUser('visi')
7110
+
7111
+ orig_axoninfo = core.axoninfo
7112
+ core.axoninfo = {'features': {}}
7113
+ data = struct.pack('>Q', 1)
7114
+ size, sha256 = await core.axon.put(data)
7115
+ sha256_s = s_common.ehex(sha256)
7116
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q"))'
7117
+ await self.asyncraises(s_exc.FeatureNotSupported,
7118
+ core.callStorm(q, opts={'vars': {'sha256': sha256_s}}))
7119
+ core.axoninfo = orig_axoninfo
7120
+
7121
+ data = b'vertex.link'
7122
+ size, sha256 = await core.axon.put(data)
7123
+ sha256_s = s_common.ehex(sha256)
7124
+
7125
+ _, emptyhash = await core.axon.put(b'')
7126
+ emptyhash = s_common.ehex(emptyhash)
7127
+
7128
+ opts = {'user': visi.iden, 'vars': {'sha256': sha256_s, 'emptyhash': emptyhash}}
7129
+ await self.asyncraises(s_exc.AuthDeny,
7130
+ core.callStorm('return($lib.axon.read($sha256, offs=3, size=3))', opts=opts))
7131
+ await visi.addRule((True, ('storm', 'lib', 'axon', 'get')))
7132
+
7133
+ q = 'return($lib.axon.read($sha256, offs=3, size=3))'
7134
+ self.eq(b'tex', await core.callStorm(q, opts=opts))
7135
+ q = 'return($lib.axon.read($sha256, offs=7, size=4))'
7136
+ self.eq(b'link', await core.callStorm(q, opts=opts))
7137
+ q = 'return($lib.axon.read($sha256, offs=11, size=1))'
7138
+ self.eq(b'', await core.callStorm(q, opts=opts))
7139
+
7140
+ q = 'return($lib.axon.read($emptyhash))'
7141
+ self.eq(b'', await core.callStorm(q, opts=opts))
7142
+
7143
+ q = 'return($lib.axon.read($sha256, size=0))'
7144
+ await self.asyncraises(s_exc.BadArg, core.callStorm(q, opts=opts))
7145
+ q = 'return($lib.axon.read($sha256, offs=-1, size=1))'
7146
+ await self.asyncraises(s_exc.BadArg, core.callStorm(q, opts=opts))
7147
+ q = 'return($lib.axon.read($sha256, size=2097152))'
7148
+ await self.asyncraises(s_exc.BadArg, core.callStorm(q, opts=opts))
7149
+
7150
+ intdata = struct.pack('>QQQ', 1, 2, 3)
7151
+ size, sha256 = await core.axon.put(intdata)
7152
+ sha256_s = s_common.ehex(sha256)
7153
+ opts = {'user': visi.iden, 'vars': {'sha256': sha256_s}}
7154
+
7155
+ await visi.delRule((True, ('storm', 'lib', 'axon', 'get')))
7156
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q"))'
7157
+ await self.asyncraises(s_exc.AuthDeny, core.callStorm(q, opts=opts))
7158
+ await visi.addRule((True, ('storm', 'lib', 'axon', 'get')))
7159
+
7160
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q"))'
7161
+ self.eq((1,), await core.callStorm(q, opts=opts))
7162
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q", offs=8))'
7163
+ self.eq((2,), await core.callStorm(q, opts=opts))
7164
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q", offs=16))'
7165
+ self.eq((3,), await core.callStorm(q, opts=opts))
7166
+ q = 'return($lib.axon.unpack($sha256, fmt=">QQ", offs=8))'
7167
+ self.eq((2, 3), await core.callStorm(q, opts=opts))
7168
+
7169
+ q = 'return($lib.axon.unpack($sha256, fmt="not a valid format"))'
7170
+ await self.asyncraises(s_exc.BadArg, core.callStorm(q, opts=opts))
7171
+
7172
+ q = 'return($lib.axon.unpack($sha256, fmt=">Q", offs=24))'
7173
+ await self.asyncraises(s_exc.BadDataValu, core.callStorm(q, opts=opts))
@@ -227,6 +227,7 @@ class BaseTest(s_t_utils.SynTest):
227
227
  :name="FOO Bar"
228
228
  :type=osint
229
229
  :url="https://foo.bar/index.html"
230
+ :ingest:cursor="Woot Woot "
230
231
  :ingest:latest=20241205
231
232
  :ingest:offset=17
232
233
  ]
@@ -238,6 +239,7 @@ class BaseTest(s_t_utils.SynTest):
238
239
  self.eq(sorc.get('name'), 'foo bar')
239
240
  self.eq(sorc.get('url'), 'https://foo.bar/index.html')
240
241
  self.eq(sorc.get('ingest:offset'), 17)
242
+ self.eq(sorc.get('ingest:cursor'), 'Woot Woot ')
241
243
  self.eq(sorc.get('ingest:latest'), 1733356800000)
242
244
 
243
245
  valu = (sorc.ndef[1], ('inet:fqdn', 'woot.com'))
@@ -342,6 +342,7 @@ class InfotechModelTest(s_t_utils.SynTest):
342
342
  :verdict=suspicious
343
343
  :scanner={[ it:prod:softver=* :name="visi scan" ]}
344
344
  :scanner:name="visi scan"
345
+ :categories=("Foo Bar", "baz faz")
345
346
  :signame=omgwtfbbq
346
347
  :target:file=*
347
348
  :target:proc={[ it:exec:proc=* :cmd="foo.exe --bar" ]}
@@ -367,6 +368,7 @@ class InfotechModelTest(s_t_utils.SynTest):
367
368
  self.eq(0x01020304, nodes[0].get('target:ipv4'))
368
369
  self.eq('::1', nodes[0].get('target:ipv6'))
369
370
  self.eq('omgwtfbbq', nodes[0].get('signame'))
371
+ self.eq(('baz faz', 'foo bar'), nodes[0].get('categories'))
370
372
 
371
373
  self.len(1, await core.nodes('it:av:scan:result:scanner:name="visi scan" -> it:host'))
372
374
  self.len(1, await core.nodes('it:av:scan:result:scanner:name="visi scan" -> inet:url'))
@@ -617,11 +619,36 @@ class InfotechModelTest(s_t_utils.SynTest):
617
619
  node = nodes[0]
618
620
  self.eq(node.ndef, ('it:dev:int', 1640531528))
619
621
 
620
- nodes = await core.nodes('[it:sec:cve=CVE-2013-9999 :desc="Some words."]')
622
+ nodes = await core.nodes('''[
623
+ it:sec:cve=CVE-2013-9999
624
+ :desc="Some words."
625
+
626
+ :nist:nvd:source=NistSource
627
+ :nist:nvd:published=2021-10-11
628
+ :nist:nvd:modified=2021-10-11
629
+
630
+ :cisa:kev:name=KevName
631
+ :cisa:kev:desc=KevDesc
632
+ :cisa:kev:action=KevAction
633
+ :cisa:kev:vendor=KevVendor
634
+ :cisa:kev:product=KevProduct
635
+ :cisa:kev:added=2022-01-02
636
+ :cisa:kev:duedate=2022-01-02
637
+ ]''')
621
638
  self.len(1, nodes)
622
639
  node = nodes[0]
623
640
  self.eq(node.ndef, ('it:sec:cve', 'cve-2013-9999'))
624
641
  self.eq(node.get('desc'), 'Some words.')
642
+ self.eq(node.get('nist:nvd:source'), 'nistsource')
643
+ self.eq(node.get('nist:nvd:published'), 1633910400000)
644
+ self.eq(node.get('nist:nvd:modified'), 1633910400000)
645
+ self.eq(node.get('cisa:kev:name'), 'KevName')
646
+ self.eq(node.get('cisa:kev:desc'), 'KevDesc')
647
+ self.eq(node.get('cisa:kev:action'), 'KevAction')
648
+ self.eq(node.get('cisa:kev:vendor'), 'kevvendor')
649
+ self.eq(node.get('cisa:kev:product'), 'kevproduct')
650
+ self.eq(node.get('cisa:kev:added'), 1641081600000)
651
+ self.eq(node.get('cisa:kev:duedate'), 1641081600000)
625
652
 
626
653
  nodes = await core.nodes('[it:sec:cve=$valu]', opts={'vars': {'valu': 'CVE\u20122013\u20131138'}})
627
654
  self.len(1, nodes)
@@ -640,6 +640,7 @@ class OuModelTest(s_t_utils.SynTest):
640
640
  ou:contest:result=(*, *)
641
641
  :rank=1
642
642
  :score=20
643
+ :period=(20250101, 20250102)
643
644
  :url=http://vertex.link/contest/result
644
645
  ]''')
645
646
  self.len(1, nodes)
@@ -647,6 +648,7 @@ class OuModelTest(s_t_utils.SynTest):
647
648
  self.nn(nodes[0].get('participant'))
648
649
  self.eq(1, nodes[0].get('rank'))
649
650
  self.eq(20, nodes[0].get('score'))
651
+ self.eq((1735689600000, 1735776000000), nodes[0].get('period'))
650
652
  self.eq('http://vertex.link/contest/result', nodes[0].get('url'))
651
653
  self.len(1, await core.nodes('ou:contest:result -> ps:contact'))
652
654
  self.len(1, await core.nodes('ou:contest:result -> ou:contest'))
@@ -111,12 +111,14 @@ class RiskModelTest(s_t_utils.SynTest):
111
111
  :cvss:v3 ?= "newp3.1"
112
112
  :priority=high
113
113
  :severity=high
114
+ :tag=cno.vuln.woot
114
115
  ]''')
115
116
 
116
117
  self.none(node.get('cvss:v2'))
117
118
  self.none(node.get('cvss:v3'))
118
119
  self.eq(40, node.get('severity'))
119
120
  self.eq(40, node.get('priority'))
121
+ self.eq('cno.vuln.woot', node.get('tag'))
120
122
 
121
123
  with self.raises(s_exc.BadTypeValu):
122
124
  node = await addNode(f'''[
@@ -110,6 +110,7 @@ class TransportTest(s_test.SynTest):
110
110
  self.nn(vessel.get('operator'))
111
111
 
112
112
  self.len(1, await core.nodes('transport:sea:vessel:imo^="IMO 123"'))
113
+ self.len(1, await core.nodes('transport:sea:vessel :name -> entity:name'))
113
114
  self.len(1, await core.nodes('transport:sea:vessel -> transport:sea:vessel:type:taxonomy'))
114
115
 
115
116
  seatelem = (await core.nodes('''[
@@ -54,6 +54,9 @@ class Beep:
54
54
  def beep(self):
55
55
  return f'{self.path}: beep'
56
56
 
57
+ def genbeep(self):
58
+ yield self.beep()
59
+
57
60
  class Foo:
58
61
 
59
62
  def __init__(self):
@@ -138,6 +141,9 @@ class TeleApi:
138
141
  def getFooBar(self, x, y):
139
142
  return x - y
140
143
 
144
+ def genGetFooBar(self, x, y):
145
+ yield self.getFooBar(x, y)
146
+
141
147
  async def customshare(self):
142
148
  return await CustomShare.anit(self.link, 42)
143
149
 
@@ -160,6 +166,11 @@ class TeleAware(s_telepath.Aware):
160
166
 
161
167
  return self._initBeep(path[0])
162
168
 
169
+ async def getTeleFeats(self):
170
+ return {
171
+ 'aware': 1,
172
+ }
173
+
163
174
  class TeleAuth(s_telepath.Aware):
164
175
 
165
176
  def getTeleApi(self, link, mesg, path):
@@ -632,7 +643,15 @@ class TeleTest(s_t_utils.SynTest):
632
643
  self.len(1, snfo)
633
644
  self.eq(snfo[0].get('items'), {None: 'synapse.tests.test_telepath.TeleApi'})
634
645
 
646
+ self.true(proxy._hasTeleFeat('aware'))
647
+ self.false(proxy._hasTeleFeat('aware', vers=2))
648
+ self.false(proxy._hasTeleFeat('newp'))
649
+
650
+ self.true(proxy._hasTeleMeth('getFooBar'))
651
+ self.false(proxy._hasTeleMeth('getBarBaz'))
652
+
635
653
  self.eq(10, await proxy.getFooBar(20, 10))
654
+ self.eq([10], [m async for m in await proxy.genGetFooBar(20, 10)])
636
655
 
637
656
  # check a custom share works
638
657
  obj = await proxy.customshare()
@@ -674,7 +693,14 @@ class TeleTest(s_t_utils.SynTest):
674
693
 
675
694
  # check that a dynamic share works
676
695
  async with await self.getTestProxy(dmon, 'woke/up') as proxy:
696
+ self.isin('synapse.tests.test_telepath.Beep', proxy._getClasses())
697
+ self.notin('synapse.tests.test_telepath.TeleApi', proxy._getClasses())
677
698
  self.eq('up: beep', await proxy.beep())
699
+ self.eq(['up: beep'], [m async for m in await proxy.genbeep()])
700
+ # Telepath features are a function of the base object, not the result of getTeleApi
701
+ self.true(proxy._hasTeleFeat('aware'))
702
+ self.false(proxy._hasTeleFeat('aware', vers=2))
703
+ self.false(proxy._hasTeleFeat('newp'))
678
704
 
679
705
  async def test_telepath_auth(self):
680
706
 
@@ -3,14 +3,17 @@ import shutil
3
3
 
4
4
  from unittest import mock
5
5
 
6
+ import synapse.exc as s_exc
6
7
  import synapse.common as s_common
7
8
  import synapse.lib.cell as s_cell
9
+ import synapse.lib.version as s_version
8
10
 
9
11
  import synapse.tests.utils as s_t_utils
10
12
 
11
13
  import synapse.tools.aha.list as s_a_list
12
14
  import synapse.tools.aha.clone as s_a_clone
13
15
  import synapse.tools.aha.enroll as s_a_enroll
16
+ import synapse.tools.aha.mirror as s_a_mirror
14
17
  import synapse.tools.aha.easycert as s_a_easycert
15
18
  import synapse.tools.aha.provision.user as s_a_provision_user
16
19
 
@@ -170,3 +173,192 @@ class AhaToolsTest(s_t_utils.SynTest):
170
173
 
171
174
  teleyaml = s_common.yamlload(syndir, 'telepath.yaml')
172
175
  self.eq(teleyaml.get('version'), 1)
176
+
177
+ async def test_aha_mirror(self):
178
+
179
+ async with self.getTestAha() as aha:
180
+
181
+ base_svcinfo = {
182
+ 'iden': 'test_iden',
183
+ 'leader': 'leader',
184
+ 'urlinfo': {
185
+ 'scheme': 'tcp',
186
+ 'host': '127.0.0.1',
187
+ 'port': 0,
188
+ 'hostname': 'test.host'
189
+ }
190
+ }
191
+
192
+ conf_no_iden = {'aha:provision': await aha.addAhaSvcProv('no.iden')}
193
+ async with self.getTestCell(s_cell.Cell, conf=conf_no_iden) as cell_no_iden:
194
+ svcinfo = {k: v for k, v in base_svcinfo.items() if k != 'iden'}
195
+ await aha.addAhaSvc('no.iden', svcinfo)
196
+
197
+ argv = ['--url', aha.getLocalUrl()]
198
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
199
+ self.eq(retn, 0)
200
+ outp.expect('Service Mirror Groups:')
201
+ self.notin('no.iden', str(outp))
202
+
203
+ conf_no_host = {'aha:provision': await aha.addAhaSvcProv('no.host')}
204
+ async with self.getTestCell(s_cell.Cell, conf=conf_no_host) as cell_no_host:
205
+ svcinfo = dict(base_svcinfo)
206
+ svcinfo['urlinfo'] = {k: v for k, v in base_svcinfo['urlinfo'].items() if k != 'hostname'}
207
+ await aha.addAhaSvc('no.host', svcinfo)
208
+
209
+ argv = ['--url', aha.getLocalUrl()]
210
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
211
+ self.eq(retn, 0)
212
+ outp.expect('Service Mirror Groups:')
213
+ self.notin('no.host', str(outp))
214
+
215
+ conf_no_leader = {'aha:provision': await aha.addAhaSvcProv('no.leader')}
216
+ async with self.getTestCell(s_cell.Cell, conf=conf_no_leader) as cell_no_leader:
217
+ svcinfo = {k: v for k, v in base_svcinfo.items() if k != 'leader'}
218
+ await aha.addAhaSvc('no.leader', svcinfo)
219
+
220
+ argv = ['--url', aha.getLocalUrl()]
221
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
222
+ self.eq(retn, 0)
223
+ outp.expect('Service Mirror Groups:')
224
+ self.notin('no.leader', str(outp))
225
+
226
+ conf_no_primary = {'aha:provision': await aha.addAhaSvcProv('no.primary')}
227
+ async with self.getTestCell(s_cell.Cell, conf=conf_no_primary) as cell_no_primary:
228
+ svcinfo = dict(base_svcinfo)
229
+ svcinfo['urlinfo']['hostname'] = 'nonexistent.host'
230
+ await aha.addAhaSvc('no.primary', svcinfo)
231
+
232
+ async with aha.waiter(3, 'aha:svcadd', timeout=10):
233
+
234
+ conf = {'aha:provision': await aha.addAhaSvcProv('00.cell')}
235
+ cell00 = await aha.enter_context(self.getTestCell(conf=conf))
236
+
237
+ conf = {'aha:provision': await aha.addAhaSvcProv('01.cell', {'mirror': 'cell'})}
238
+ cell01 = await aha.enter_context(self.getTestCell(conf=conf))
239
+
240
+ await cell01.sync()
241
+
242
+ ahaurl = aha.getLocalUrl()
243
+
244
+ argv = ['--url', ahaurl]
245
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
246
+ self.eq(retn, 0)
247
+ outp.expect('Service Mirror Groups:')
248
+ outp.expect('00.cell.synapse')
249
+ outp.expect('01.cell.synapse')
250
+ outp.expect('Group Status: In Sync')
251
+
252
+ argv = ['--url', ahaurl, '--timeout', '30']
253
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
254
+ self.eq(retn, 0)
255
+
256
+ with mock.patch('synapse.telepath.Proxy._hasTeleFeat',
257
+ return_value=False):
258
+ argv = ['--url', ahaurl]
259
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
260
+ self.eq(retn, 1)
261
+ outp.expect(f'Service at {ahaurl} does not support the required callpeers feature.')
262
+
263
+ with mock.patch('synapse.telepath.Proxy._hasTeleFeat',
264
+ side_effect=s_exc.NoSuchMeth(name='_hasTeleFeat')):
265
+ argv = ['--url', ahaurl]
266
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
267
+ self.eq(retn, 1)
268
+ outp.expect(f'Service at {ahaurl} does not support the required callpeers feature.')
269
+
270
+ argv = ['--url', 'tcp://newp:1234/']
271
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
272
+ self.eq(retn, 1)
273
+ outp.expect('ERROR:')
274
+
275
+ async def mockCellInfo():
276
+ return {
277
+ 'cell': {'ready': True, 'nexsindx': 10, 'uplink': None},
278
+ 'synapse': {'verstring': s_version.verstring},
279
+ }
280
+
281
+ async def mockOutOfSyncCellInfo():
282
+ return {
283
+ 'cell': {'ready': True, 'nexsindx': 5, 'uplink': cell00.iden},
284
+ 'synapse': {'verstring': s_version.verstring},
285
+ }
286
+
287
+ with mock.patch.object(cell00, 'getCellInfo', mockCellInfo):
288
+ with mock.patch.object(cell01, 'getCellInfo', mockOutOfSyncCellInfo):
289
+ async def mock_call_aha(*args, **kwargs):
290
+ todo = args[1]
291
+ if todo[0] == 'waitNexsOffs':
292
+ yield ('00.cell.synapse', (True, True))
293
+ yield ('01.cell.synapse', (True, True))
294
+ elif todo[0] == 'getCellInfo':
295
+ if not hasattr(mock_call_aha, 'called'):
296
+ mock_call_aha.called = True
297
+ yield ('00.cell.synapse', (True, await mockCellInfo()))
298
+ yield ('01.cell.synapse', (True, await mockOutOfSyncCellInfo()))
299
+ else:
300
+ yield ('00.cell.synapse', (True, await mockCellInfo()))
301
+ yield ('01.cell.synapse', (True, await mockCellInfo()))
302
+
303
+ with mock.patch.object(aha, 'callAhaPeerApi', mock_call_aha):
304
+ argv = ['--url', ahaurl, '--wait']
305
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
306
+ self.eq(retn, 0)
307
+ outp.expect('Group Status: Out of Sync')
308
+ outp.expect('Updated status:')
309
+ outp.expect('Group Status: In Sync')
310
+
311
+ with mock.patch.object(cell00, 'getCellInfo', mockCellInfo):
312
+ with mock.patch.object(cell01, 'getCellInfo', mockOutOfSyncCellInfo):
313
+ argv = ['--url', ahaurl, '--timeout', '1']
314
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
315
+ self.eq(retn, 0)
316
+ outp.expect('Group Status: Out of Sync')
317
+
318
+ async with self.getTestCore() as core:
319
+ curl = core.getLocalUrl()
320
+ argv = ['--url', curl]
321
+ with mock.patch('synapse.telepath.Proxy._hasTeleFeat',
322
+ return_value=True):
323
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
324
+ self.eq(1, retn)
325
+ outp.expect(f'Service at {curl} is not an Aha server')
326
+
327
+ async with aha.waiter(1, 'aha:svcadd', timeout=10):
328
+
329
+ conf = {'aha:provision': await aha.addAhaSvcProv('02.cell', {'mirror': 'cell'})}
330
+ cell02 = await aha.enter_context(self.getTestCell(conf=conf))
331
+ await cell02.sync()
332
+
333
+ async def mock_failed_api(*args, **kwargs):
334
+ yield ('00.cell.synapse', (True, {'cell': {'ready': True, 'nexsindx': 10}}))
335
+ yield ('01.cell.synapse', (False, 'error'))
336
+ yield ('02.cell.synapse', (True, {'cell': {'ready': True, 'nexsindx': 12}}))
337
+
338
+ with mock.patch.object(aha, 'callAhaPeerApi', mock_failed_api):
339
+ argv = ['--url', ahaurl, '--timeout', '1']
340
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
341
+ outp.expect('00.cell.synapse leader True True 127.0.0.1', whitespace=False)
342
+ outp.expect('nexsindx 10', whitespace=False)
343
+ outp.expect('02.cell.synapse leader True True 127.0.0.1', whitespace=False)
344
+ outp.expect('nexsindx 12', whitespace=False)
345
+ outp.expect('01.cell.synapse <unknown> True True', whitespace=False)
346
+ outp.expect('<unknown> <unknown>', whitespace=False)
347
+
348
+ self.eq(s_a_mirror.timeout_type('30'), 30)
349
+ self.eq(s_a_mirror.timeout_type('0'), 0)
350
+
351
+ with self.raises(s_exc.BadArg) as cm:
352
+ s_a_mirror.timeout_type('-1')
353
+ self.isin('is not a valid non-negative integer', cm.exception.get('mesg'))
354
+
355
+ with self.raises(s_exc.BadArg) as cm:
356
+ s_a_mirror.timeout_type('foo')
357
+ self.isin('is not a valid non-negative integer', cm.exception.get('mesg'))
358
+
359
+ synerr = s_exc.SynErr(mesg='Oof')
360
+ with mock.patch('synapse.telepath.openurl', side_effect=synerr):
361
+ argv = ['--url', 'tcp://test:1234/']
362
+ retn, outp = await self.execToolMain(s_a_mirror.main, argv)
363
+ self.eq(retn, 1)
364
+ outp.expect('ERROR: Oof')