synapse 2.177.0__py311-none-any.whl → 2.179.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 (73) hide show
  1. synapse/cortex.py +170 -31
  2. synapse/datamodel.py +47 -1
  3. synapse/exc.py +1 -0
  4. synapse/lib/aha.py +362 -88
  5. synapse/lib/ast.py +26 -22
  6. synapse/lib/base.py +39 -12
  7. synapse/lib/cell.py +315 -119
  8. synapse/lib/config.py +15 -11
  9. synapse/lib/coro.py +27 -0
  10. synapse/lib/drive.py +551 -0
  11. synapse/lib/layer.py +0 -5
  12. synapse/lib/link.py +1 -1
  13. synapse/lib/lmdbslab.py +3 -3
  14. synapse/lib/nexus.py +24 -12
  15. synapse/lib/schemas.py +39 -0
  16. synapse/lib/snap.py +17 -7
  17. synapse/lib/storm.py +3 -1
  18. synapse/lib/stormhttp.py +1 -0
  19. synapse/lib/stormlib/imap.py +6 -2
  20. synapse/lib/stormlib/modelext.py +29 -3
  21. synapse/lib/stormlib/smtp.py +12 -2
  22. synapse/lib/stormlib/stix.py +40 -17
  23. synapse/lib/stormlib/vault.py +2 -2
  24. synapse/lib/stormtypes.py +1 -1
  25. synapse/lib/types.py +9 -0
  26. synapse/lib/version.py +2 -2
  27. synapse/lookup/pe.py +303 -38
  28. synapse/models/dns.py +24 -1
  29. synapse/models/geospace.py +4 -1
  30. synapse/models/infotech.py +26 -1
  31. synapse/telepath.py +32 -17
  32. synapse/tests/files/aha/certs/cas/synapse.crt +28 -0
  33. synapse/tests/files/aha/certs/cas/synapse.key +51 -0
  34. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.crt +30 -0
  35. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.key +51 -0
  36. synapse/tests/files/aha/certs/users/root@synapse.crt +29 -0
  37. synapse/tests/files/aha/certs/users/root@synapse.key +51 -0
  38. synapse/tests/files/rstorm/testsvc.py +1 -1
  39. synapse/tests/test_axon.py +1 -1
  40. synapse/tests/test_cortex.py +67 -60
  41. synapse/tests/test_lib_agenda.py +3 -3
  42. synapse/tests/test_lib_aha.py +353 -490
  43. synapse/tests/test_lib_base.py +20 -0
  44. synapse/tests/test_lib_cell.py +273 -22
  45. synapse/tests/test_lib_config.py +4 -3
  46. synapse/tests/test_lib_coro.py +12 -0
  47. synapse/tests/test_lib_nexus.py +8 -0
  48. synapse/tests/test_lib_stormhttp.py +40 -0
  49. synapse/tests/test_lib_stormlib_aha.py +35 -35
  50. synapse/tests/test_lib_stormlib_cell.py +4 -15
  51. synapse/tests/test_lib_stormlib_imap.py +14 -3
  52. synapse/tests/test_lib_stormlib_modelext.py +55 -3
  53. synapse/tests/test_lib_stormlib_smtp.py +51 -0
  54. synapse/tests/test_lib_stormlib_stix.py +15 -0
  55. synapse/tests/test_lib_stormlib_vault.py +11 -1
  56. synapse/tests/test_lib_stormtypes.py +5 -0
  57. synapse/tests/test_lib_types.py +9 -0
  58. synapse/tests/test_model_dns.py +8 -0
  59. synapse/tests/test_model_geospace.py +3 -1
  60. synapse/tests/test_model_infotech.py +47 -0
  61. synapse/tests/test_model_syn.py +11 -0
  62. synapse/tests/test_tools_aha.py +78 -101
  63. synapse/tests/test_utils_stormcov.py +1 -1
  64. synapse/tests/utils.py +86 -120
  65. synapse/tools/aha/clone.py +50 -0
  66. synapse/tools/aha/enroll.py +2 -1
  67. synapse/tools/backup.py +2 -2
  68. synapse/tools/changelog.py +31 -1
  69. {synapse-2.177.0.dist-info → synapse-2.179.0.dist-info}/METADATA +48 -48
  70. {synapse-2.177.0.dist-info → synapse-2.179.0.dist-info}/RECORD +73 -65
  71. {synapse-2.177.0.dist-info → synapse-2.179.0.dist-info}/WHEEL +1 -1
  72. {synapse-2.177.0.dist-info → synapse-2.179.0.dist-info}/LICENSE +0 -0
  73. {synapse-2.177.0.dist-info → synapse-2.179.0.dist-info}/top_level.txt +0 -0
synapse/lib/cell.py CHANGED
@@ -37,6 +37,7 @@ import synapse.lib.hive as s_hive
37
37
  import synapse.lib.link as s_link
38
38
  import synapse.lib.cache as s_cache
39
39
  import synapse.lib.const as s_const
40
+ import synapse.lib.drive as s_drive
40
41
  import synapse.lib.nexus as s_nexus
41
42
  import synapse.lib.queue as s_queue
42
43
  import synapse.lib.scope as s_scope
@@ -910,7 +911,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
910
911
  },
911
912
  'nexslog:async': {
912
913
  'default': True,
913
- 'description': 'Set to false to disable async memory mapping of the nexus change log.',
914
+ 'description': 'Deprecated. This option ignored.',
914
915
  'type': 'boolean',
915
916
  'hidedocs': True,
916
917
  'hidecmdl': True,
@@ -967,7 +968,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
967
968
  'type': 'string',
968
969
  },
969
970
  'aha:network': {
970
- 'description': 'The AHA service network. This makes aha:name/aha:leader relative names.',
971
+ 'description': 'The AHA service network.',
971
972
  'type': 'string',
972
973
  },
973
974
  'aha:registry': {
@@ -1111,6 +1112,9 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1111
1112
  self._checkspace = s_coro.Event()
1112
1113
  self._reloadfuncs = {} # name -> func
1113
1114
 
1115
+ self.nexslock = asyncio.Lock()
1116
+ self.netready = asyncio.Event()
1117
+
1114
1118
  self.conf = self._initCellConf(conf)
1115
1119
 
1116
1120
  self.minfree = self.conf.get('limit:disk:free')
@@ -1181,23 +1185,32 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1181
1185
  self.backlastexc = None # err, errmsg, errtrace of last backup
1182
1186
 
1183
1187
  if self.conf.get('mirror') and not self.conf.get('nexslog:en'):
1184
- mesg = 'Mirror mode requires nexslog:en=True'
1185
- raise s_exc.BadConfValu(mesg=mesg)
1188
+ self.modCellConf({'nexslog:en': True})
1186
1189
 
1187
- # construct our nexsroot instance ( but do not start it )
1188
1190
  await s_nexus.Pusher.__anit__(self, self.iden)
1189
1191
 
1190
1192
  self._initCertDir()
1191
1193
 
1192
- root = await self._ctorNexsRoot()
1194
+ await self.enter_context(s_telepath.loadTeleCell(self.dirn))
1193
1195
 
1194
- # mutually assured destruction with our nexs root
1195
- self.onfini(root.fini)
1196
- root.onfini(self.fini)
1196
+ await self._initCellSlab(readonly=readonly)
1197
1197
 
1198
- self.setNexsRoot(root)
1198
+ # initialize network daemons (but do not listen yet)
1199
+ # to allow registration of callbacks and shared objects
1200
+ await self._initCellHttp()
1201
+ await self._initCellDmon()
1202
+
1203
+ await self.initServiceEarly()
1204
+
1205
+ nexsroot = await self._ctorNexsRoot()
1206
+
1207
+ self.setNexsRoot(nexsroot)
1208
+
1209
+ async def fini():
1210
+ await self.nexsroot.fini()
1211
+
1212
+ self.onfini(fini)
1199
1213
 
1200
- await self._initCellSlab(readonly=readonly)
1201
1214
  self.apikeydb = self.slab.initdb('user:apikeys') # apikey -> useriden
1202
1215
  self.usermetadb = self.slab.initdb('user:meta') # useriden + <valu> -> dict valu
1203
1216
  self.rolemetadb = self.slab.initdb('role:meta') # roleiden + <valu> -> dict valu
@@ -1272,14 +1285,10 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1272
1285
  # initialize network backend infrastructure
1273
1286
  await self._initAhaRegistry()
1274
1287
 
1275
- # initialize network daemons (but do not listen yet)
1276
- # to allow registration of callbacks and shared objects
1277
- # within phase 2/4.
1278
- await self._initCellHttp()
1279
- await self._initCellDmon()
1280
-
1281
1288
  # phase 2 - service storage
1289
+ await self.initCellStorage()
1282
1290
  await self.initServiceStorage()
1291
+
1283
1292
  # phase 3 - nexus subsystem
1284
1293
  await self.initNexusSubsystem()
1285
1294
 
@@ -1612,10 +1621,10 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1612
1621
 
1613
1622
  async def _runFreeSpaceLoop(self):
1614
1623
 
1615
- nexsroot = self.getCellNexsRoot()
1616
-
1617
1624
  while not self.isfini:
1618
1625
 
1626
+ nexsroot = self.getCellNexsRoot()
1627
+
1619
1628
  self._checkspace.clear()
1620
1629
 
1621
1630
  disk = shutil.disk_usage(self.dirn)
@@ -1662,37 +1671,61 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1662
1671
 
1663
1672
  await self.waitfini(self.SYSCTL_CHECK_FREQ)
1664
1673
 
1665
- def _getAhaAdmin(self):
1666
- name = self.conf.get('aha:admin')
1667
- if name is not None:
1668
- return name
1669
-
1670
1674
  async def _initAhaRegistry(self):
1671
1675
 
1672
- ahaurl = self.conf.get('aha:registry')
1673
- if ahaurl is not None:
1676
+ ahaurls = self.conf.get('aha:registry')
1677
+ if ahaurls is not None:
1678
+
1679
+ await s_telepath.addAhaUrl(ahaurls)
1680
+ if self.ahaclient is not None:
1681
+ await self.ahaclient.fini()
1682
+
1683
+ async def onlink(proxy):
1684
+ ahauser = self.conf.get('aha:user', 'root')
1685
+ newurls = await proxy.getAhaUrls(user=ahauser)
1686
+ oldurls = self.conf.get('aha:registry')
1687
+ if isinstance(oldurls, str):
1688
+ oldurls = (oldurls,)
1689
+ elif isinstance(oldurls, list):
1690
+ oldurls = tuple(oldurls)
1691
+ if newurls and newurls != oldurls:
1692
+ if oldurls[0].startswith('tcp://'):
1693
+ s_common.deprecated('aha:registry: tcp:// client values.')
1694
+ logger.warning('tcp:// based aha:registry options are deprecated and will be removed in Synapse v3.0.0')
1695
+ return
1674
1696
 
1675
- info = await s_telepath.addAhaUrl(ahaurl)
1676
- if self.ahaclient is None:
1677
- self.ahaclient = await s_telepath.Client.anit(info.get('url'))
1678
- self.ahaclient._fini_atexit = True
1679
- self.onfini(self.ahaclient)
1697
+ self.modCellConf({'aha:registry': newurls})
1698
+ self.ahaclient.setBootUrls(newurls)
1680
1699
 
1681
- async def finiaha():
1682
- await s_telepath.delAhaUrl(ahaurl)
1700
+ self.ahaclient = await s_telepath.Client.anit(ahaurls, onlink=onlink)
1701
+ self.onfini(self.ahaclient)
1683
1702
 
1684
- self.onfini(finiaha)
1703
+ async def fini():
1704
+ await s_telepath.delAhaUrl(ahaurls)
1705
+
1706
+ self.ahaclient.onfini(fini)
1685
1707
 
1708
+ ahaadmin = self.conf.get('aha:admin')
1686
1709
  ahauser = self.conf.get('aha:user')
1687
- ahanetw = self.conf.get('aha:network')
1688
1710
 
1689
- ahaadmin = self._getAhaAdmin()
1690
1711
  if ahaadmin is not None:
1691
1712
  await self._addAdminUser(ahaadmin)
1692
1713
 
1693
1714
  if ahauser is not None:
1694
1715
  await self._addAdminUser(ahauser)
1695
1716
 
1717
+ def _getDmonListen(self):
1718
+
1719
+ lisn = self.conf.get('dmon:listen', s_common.novalu)
1720
+ if lisn is not s_common.novalu:
1721
+ return lisn
1722
+
1723
+ ahaname = self.conf.get('aha:name')
1724
+ ahanetw = self.conf.get('aha:network')
1725
+ if ahaname is not None and ahanetw is not None:
1726
+ hostname = f'{ahaname}.{ahanetw}'
1727
+ return f'ssl://0.0.0.0:0?hostname={hostname}&ca={ahanetw}'
1728
+
1696
1729
  async def _addAdminUser(self, username):
1697
1730
  # add the user in a pre-nexus compatible way
1698
1731
  user = await self.auth.getUserByName(username)
@@ -1708,6 +1741,132 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1708
1741
  if user.isLocked():
1709
1742
  await user.setLocked(False, logged=False)
1710
1743
 
1744
+ async def initServiceEarly(self):
1745
+ pass
1746
+
1747
+ async def initCellStorage(self):
1748
+ self.drive = await s_drive.Drive.anit(self.slab, 'celldrive')
1749
+ self.onfini(self.drive.fini)
1750
+
1751
+ async def addDriveItem(self, info, path=None, reldir=s_drive.rootdir):
1752
+
1753
+ iden = info.get('iden')
1754
+ if iden is None:
1755
+ info['iden'] = s_common.guid()
1756
+
1757
+ info.setdefault('created', s_common.now())
1758
+ info.setdefault('creator', self.auth.rootuser.iden)
1759
+
1760
+ return await self._push('drive:add', info, path=path, reldir=reldir)
1761
+
1762
+ @s_nexus.Pusher.onPush('drive:add')
1763
+ async def _addDriveItem(self, info, path=None, reldir=s_drive.rootdir):
1764
+
1765
+ # replay safety...
1766
+ iden = info.get('iden')
1767
+ if self.drive.hasItemInfo(iden): # pragma: no cover
1768
+ return await self.drive.getItemPath(iden)
1769
+
1770
+ return await self.drive.addItemInfo(info, path=path, reldir=reldir)
1771
+
1772
+ async def getDriveInfo(self, iden):
1773
+ return self.drive.getItemInfo(iden)
1774
+
1775
+ async def getDrivePath(self, path, reldir=s_drive.rootdir):
1776
+ '''
1777
+ Return a list of drive info elements for each step in path.
1778
+
1779
+ This may be used as a sort of "open" which returns all the
1780
+ path info entries. You may then operate directly on drive iden
1781
+ entries and/or check easyperm entries on them before you do...
1782
+ '''
1783
+ return await self.drive.getPathInfo(path, reldir=reldir)
1784
+
1785
+ async def addDrivePath(self, path, perm=None, reldir=s_drive.rootdir):
1786
+ '''
1787
+ Create the given path using the specified permissions.
1788
+
1789
+ The specified permissions are only used when creating new directories.
1790
+
1791
+ NOTE: We must do this outside the Drive class to allow us to generate
1792
+ iden and tick but remain nexus compatible.
1793
+ '''
1794
+ tick = s_common.now()
1795
+ user = self.auth.rootuser.iden
1796
+ path = self.drive.getPathNorm(path)
1797
+
1798
+ if perm is None:
1799
+ perm = {'users': {}, 'roles': {}}
1800
+
1801
+ for name in path:
1802
+
1803
+ info = self.drive.getStepInfo(reldir, name)
1804
+ await asyncio.sleep(0)
1805
+
1806
+ if info is not None:
1807
+ reldir = info.get('iden')
1808
+ continue
1809
+
1810
+ info = {
1811
+ 'name': name,
1812
+ 'perm': perm,
1813
+ 'iden': s_common.guid(),
1814
+ 'created': tick,
1815
+ 'creator': user,
1816
+ }
1817
+ pathinfo = await self.addDriveItem(info, reldir=reldir)
1818
+ reldir = pathinfo[-1].get('iden')
1819
+
1820
+ return await self.drive.getItemPath(reldir)
1821
+
1822
+ async def getDriveData(self, iden, vers=None):
1823
+ '''
1824
+ Return the data associated with the drive item by iden.
1825
+ If vers is specified, return that specific version.
1826
+ '''
1827
+ return self.drive.getItemData(iden, vers=vers)
1828
+
1829
+ async def getDriveDataVersions(self, iden):
1830
+ async for item in self.drive.getItemDataVersions(iden):
1831
+ yield item
1832
+
1833
+ @s_nexus.Pusher.onPushAuto('drive:del')
1834
+ async def delDriveInfo(self, iden):
1835
+ if self.drive.getItemInfo(iden) is not None:
1836
+ await self.drive.delItemInfo(iden)
1837
+
1838
+ @s_nexus.Pusher.onPushAuto('drive:set:perm')
1839
+ async def setDriveInfoPerm(self, iden, perm):
1840
+ return self.drive.setItemPerm(iden, perm)
1841
+
1842
+ @s_nexus.Pusher.onPushAuto('drive:set:path')
1843
+ async def setDriveInfoPath(self, iden, path):
1844
+
1845
+ path = self.drive.getPathNorm(path)
1846
+ pathinfo = await self.drive.getItemPath(iden)
1847
+ if path == [p.get('name') for p in pathinfo]:
1848
+ return pathinfo
1849
+
1850
+ return await self.drive.setItemPath(iden, path)
1851
+
1852
+ @s_nexus.Pusher.onPushAuto('drive:data:set')
1853
+ async def setDriveData(self, iden, versinfo, data):
1854
+ return self.drive.setItemData(iden, versinfo, data)
1855
+
1856
+ async def delDriveData(self, iden, vers=None):
1857
+ if vers is None:
1858
+ info = self.drive.reqItemInfo(iden)
1859
+ vers = info.get('version')
1860
+ return await self._push('drive:data:del', iden, vers)
1861
+
1862
+ @s_nexus.Pusher.onPush('drive:data:del')
1863
+ async def _delDriveData(self, iden, vers):
1864
+ return self.drive.delItemData(iden, vers)
1865
+
1866
+ async def getDriveKids(self, iden):
1867
+ async for info in self.drive.getItemKids(iden):
1868
+ yield info
1869
+
1711
1870
  async def initServiceStorage(self):
1712
1871
  pass
1713
1872
 
@@ -1720,7 +1879,11 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1720
1879
  if self.minfree is not None:
1721
1880
  self.schedCoro(self._runFreeSpaceLoop())
1722
1881
 
1723
- async def initServiceNetwork(self):
1882
+ async def _bindDmonListen(self):
1883
+
1884
+ # functionalized so downstream code can bind early.
1885
+ if self.sockaddr is not None:
1886
+ return
1724
1887
 
1725
1888
  # start a unix local socket daemon listener
1726
1889
  sockpath = os.path.join(self.dirn, 'sock')
@@ -1728,24 +1891,25 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1728
1891
 
1729
1892
  try:
1730
1893
  await self.dmon.listen(sockurl)
1731
- except asyncio.CancelledError: # pragma: no cover TODO: remove once >= py 3.8 only
1732
- raise
1733
1894
  except OSError as e:
1734
1895
  logger.error(f'Failed to listen on unix socket at: [{sockpath}][{e}]')
1735
1896
  logger.error('LOCAL UNIX SOCKET WILL BE UNAVAILABLE')
1736
1897
  except Exception: # pragma: no cover
1737
1898
  logging.exception('Unknown dmon listen error.')
1738
- raise
1739
1899
 
1740
- self.sockaddr = None
1741
-
1742
- turl = self.conf.get('dmon:listen')
1900
+ turl = self._getDmonListen()
1743
1901
  if turl is not None:
1744
- self.sockaddr = await self.dmon.listen(turl)
1745
1902
  logger.info(f'dmon listening: {turl}')
1903
+ self.sockaddr = await self.dmon.listen(turl)
1904
+
1905
+ async def initServiceNetwork(self):
1906
+
1907
+ await self._bindDmonListen()
1746
1908
 
1747
1909
  await self._initAhaService()
1748
1910
 
1911
+ self.netready.set()
1912
+
1749
1913
  port = self.conf.get('https:port')
1750
1914
  if port is not None:
1751
1915
  await self.addHttpsPort(port)
@@ -1804,27 +1968,34 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1804
1968
  if ahaname is None:
1805
1969
  return
1806
1970
 
1807
- ahalead = self.conf.get('aha:leader')
1808
1971
  ahanetw = self.conf.get('aha:network')
1972
+ if ahanetw is None:
1973
+ return
1809
1974
 
1810
1975
  ahainfo = await self.getAhaInfo()
1811
1976
  if ahainfo is None:
1812
1977
  return
1813
1978
 
1979
+ ahalead = self.conf.get('aha:leader')
1980
+
1814
1981
  self.ahasvcname = f'{ahaname}.{ahanetw}'
1815
1982
 
1816
1983
  async def _runAhaRegLoop():
1817
1984
 
1818
1985
  while not self.isfini:
1986
+
1819
1987
  try:
1820
1988
  proxy = await self.ahaclient.proxy()
1989
+
1821
1990
  info = await self.getAhaInfo()
1822
1991
  await proxy.addAhaSvc(ahaname, info, network=ahanetw)
1823
1992
  if self.isactive and ahalead is not None:
1824
1993
  await proxy.addAhaSvc(ahalead, info, network=ahanetw)
1994
+
1825
1995
  except Exception as e:
1826
1996
  logger.exception(f'Error registering service {self.ahasvcname} with AHA: {e}')
1827
1997
  await self.waitfini(1)
1998
+
1828
1999
  else:
1829
2000
  await proxy.waitfini()
1830
2001
 
@@ -1905,6 +2076,11 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1905
2076
  async def setNexsIndx(self, indx):
1906
2077
  return await self.nexsroot.setindex(indx)
1907
2078
 
2079
+ def getMyUrl(self, user='root'):
2080
+ host = self.conf.req('aha:name')
2081
+ network = self.conf.req('aha:network')
2082
+ return f'aha://{host}.{network}'
2083
+
1908
2084
  async def promote(self, graceful=False):
1909
2085
  '''
1910
2086
  Transform this cell from a passive follower to
@@ -1915,48 +2091,39 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1915
2091
  mesg = 'promote() called on non-mirror'
1916
2092
  raise s_exc.BadConfValu(mesg=mesg)
1917
2093
 
1918
- ahaname = self.conf.get('aha:name')
1919
- logger.warning(f'PROMOTION: Performing leadership promotion graceful={graceful} ahaname={ahaname}')
2094
+ _dispname = f' ahaname={self.conf.get("aha:name")}' if self.conf.get('aha:name') else ''
2095
+ logger.warning(f'PROMOTION: Performing leadership promotion graceful={graceful}{_dispname}.')
1920
2096
 
1921
2097
  if graceful:
1922
2098
 
1923
- if ahaname is None: # pragma: no cover
1924
- mesg = 'Cannot gracefully promote without aha:name configured.'
1925
- raise s_exc.BadArg(mesg=mesg)
2099
+ myurl = self.getMyUrl()
1926
2100
 
1927
- ahanetw = self.conf.get('aha:network')
1928
- if ahanetw is None: # pragma: no cover
1929
- mesg = 'Cannot gracefully promote without aha:network configured.'
1930
- raise s_exc.BadArg(mesg=mesg)
1931
-
1932
- myurl = f'aha://{ahaname}.{ahanetw}'
1933
- logger.debug(f'PROMOTION: Connecting to {mirurl} to request leadership handoff to ahaname={ahaname}')
2101
+ logger.debug(f'PROMOTION: Connecting to {mirurl} to request leadership handoff{_dispname}.')
1934
2102
  async with await s_telepath.openurl(mirurl) as lead:
1935
- logger.debug(f'PROMOTION: Requesting leadership handoff to ahaname={ahaname}')
1936
2103
  await lead.handoff(myurl)
1937
- logger.warning(f'PROMOTION: Completed leadership handoff to ahaname={ahaname}')
2104
+ logger.warning(f'PROMOTION: Completed leadership handoff to {myurl}{_dispname}')
1938
2105
  return
1939
2106
 
1940
- logger.debug(f'PROMOTION: Clearing mirror configuration for ahaname={ahaname}')
2107
+ logger.debug(f'PROMOTION: Clearing mirror configuration{_dispname}.')
1941
2108
  self.modCellConf({'mirror': None})
1942
2109
 
1943
- logger.debug(f'PROMOTION: Promoting the nexus root for ahaname={ahaname}')
2110
+ logger.debug(f'PROMOTION: Promoting the nexus root{_dispname}.')
1944
2111
  await self.nexsroot.promote()
1945
2112
 
1946
- logger.debug(f'PROMOTION: Setting the cell as active ahaname={ahaname}')
2113
+ logger.debug(f'PROMOTION: Setting the cell as active{_dispname}.')
1947
2114
  await self.setCellActive(True)
1948
2115
 
1949
- logger.warning(f'PROMOTION: Finished leadership promotion ahaname={ahaname}')
2116
+ logger.warning(f'PROMOTION: Finished leadership promotion{_dispname}.')
1950
2117
 
1951
2118
  async def handoff(self, turl, timeout=30):
1952
2119
  '''
1953
2120
  Hand off leadership to a mirror in a transactional fashion.
1954
2121
  '''
1955
- ahaname = self.conf.get("aha:name")
1956
- logger.warning(f'HANDOFF: Performing leadership handoff to {s_urlhelp.sanitizeUrl(turl)} from ahaname={ahaname}')
2122
+ _dispname = f' ahaname={self.conf.get("aha:name")}' if self.conf.get('aha:name') else ''
2123
+ logger.warning(f'HANDOFF: Performing leadership handoff to {s_urlhelp.sanitizeUrl(turl)}{_dispname}.')
1957
2124
  async with await s_telepath.openurl(turl) as cell:
1958
2125
 
1959
- logger.debug(f'HANDOFF: Connected to {s_urlhelp.sanitizeUrl(turl)} from ahaname={ahaname}')
2126
+ logger.debug(f'HANDOFF: Connected to {s_urlhelp.sanitizeUrl(turl)}{_dispname}.')
1960
2127
 
1961
2128
  if self.iden != await cell.getCellIden(): # pragma: no cover
1962
2129
  mesg = 'Mirror handoff remote cell iden does not match!'
@@ -1966,33 +2133,34 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1966
2133
  mesg = 'Cannot handoff mirror leadership to myself!'
1967
2134
  raise s_exc.BadArg(mesg=mesg)
1968
2135
 
1969
- logger.debug(f'HANDOFF: Obtaining nexus applylock ahaname={ahaname}')
2136
+ logger.debug(f'HANDOFF: Obtaining nexus lock{_dispname}.')
1970
2137
 
1971
- async with self.nexsroot.applylock:
2138
+ async with self.nexslock:
1972
2139
 
1973
- logger.debug(f'HANDOFF: Obtained nexus applylock ahaname={ahaname}')
2140
+ logger.debug(f'HANDOFF: Obtained nexus lock{_dispname}.')
1974
2141
  indx = await self.getNexsIndx()
1975
2142
 
1976
- logger.debug(f'HANDOFF: Waiting {timeout} seconds for mirror to reach {indx=}, ahaname={ahaname}')
2143
+ logger.debug(f'HANDOFF: Waiting {timeout} seconds for mirror to reach {indx=}{_dispname}.')
1977
2144
  if not await cell.waitNexsOffs(indx - 1, timeout=timeout): # pragma: no cover
1978
2145
  mndx = await cell.getNexsIndx()
1979
2146
  mesg = f'Remote mirror did not catch up in time: {mndx}/{indx}.'
1980
2147
  raise s_exc.NotReady(mesg=mesg)
1981
2148
 
1982
- logger.debug(f'HANDOFF: Mirror has caught up to the current leader, performing promotion ahaname={ahaname}')
2149
+ logger.debug(f'HANDOFF: Mirror has caught up to the current leader, performing promotion{_dispname}.')
1983
2150
  await cell.promote()
1984
2151
 
1985
- logger.debug(f'HANDOFF: Setting the service as inactive ahaname={ahaname}')
2152
+ logger.debug(f'HANDOFF: Setting the service as inactive{_dispname}.')
1986
2153
  await self.setCellActive(False)
1987
2154
 
1988
- logger.debug(f'HANDOFF: Configuring service to use the new leader as its mirror ahaname={ahaname}')
2155
+ logger.debug(f'HANDOFF: Configuring service to sync from new leader{_dispname}.')
1989
2156
  self.modCellConf({'mirror': turl})
1990
2157
 
1991
- logger.debug(f'HANDOFF: Restarting the nexus ahaname={ahaname}')
2158
+ logger.debug(f'HANDOFF: Restarting the nexus{_dispname}.')
1992
2159
  await self.nexsroot.startup()
1993
2160
 
1994
- logger.debug(f'HANDOFF: Released nexus applylock ahaname={ahaname}')
1995
- logger.warning(f'HANDOFF: Done performing the leadership handoff with {s_urlhelp.sanitizeUrl(turl)} ahaname={self.conf.get("aha:name")}')
2161
+ logger.debug(f'HANDOFF: Released nexus lock{_dispname}.')
2162
+
2163
+ logger.warning(f'HANDOFF: Done performing the leadership handoff with {s_urlhelp.sanitizeUrl(turl)}{_dispname}.')
1996
2164
 
1997
2165
  async def reqAhaProxy(self, timeout=None):
1998
2166
  if self.ahaclient is None:
@@ -2025,7 +2193,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2025
2193
  await proxy.fini()
2026
2194
  return
2027
2195
 
2028
- ahanetw = self.conf.get('aha:network')
2196
+ ahanetw = self.conf.req('aha:network')
2029
2197
  try:
2030
2198
  await proxy.addAhaSvc(ahalead, ahainfo, network=ahanetw)
2031
2199
  except asyncio.CancelledError: # pragma: no cover
@@ -2292,7 +2460,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2292
2460
 
2293
2461
  try:
2294
2462
 
2295
- async with self.nexsroot.applylock:
2463
+ async with self.nexslock:
2296
2464
 
2297
2465
  logger.debug('Syncing LMDB Slabs')
2298
2466
 
@@ -3007,7 +3175,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3007
3175
  sslctx = self.initSslCtx(certpath, pkeypath)
3008
3176
 
3009
3177
  kwargs = {
3010
- 'xheaders': self.conf.reqConfValu('https:parse:proxy:remoteip')
3178
+ 'xheaders': self.conf.req('https:parse:proxy:remoteip')
3011
3179
  }
3012
3180
  serv = self.wapp.listen(port, address=addr, ssl_options=sslctx, **kwargs)
3013
3181
  self.httpds.append(serv)
@@ -3158,9 +3326,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3158
3326
 
3159
3327
  async def _initCellDmon(self):
3160
3328
 
3161
- ahainfo = {
3162
- 'name': self.ahasvcname
3163
- }
3329
+ ahainfo = {'name': self.ahasvcname}
3164
3330
 
3165
3331
  self.dmon = await s_daemon.Daemon.anit(ahainfo=ahainfo)
3166
3332
  self.dmon.share('*', self)
@@ -3174,6 +3340,15 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3174
3340
 
3175
3341
  return hive
3176
3342
 
3343
+ async def _initSlabFile(self, path, readonly=False, ephemeral=False):
3344
+ slab = await s_lmdbslab.Slab.anit(path, map_size=SLAB_MAP_SIZE, readonly=readonly)
3345
+ slab.addResizeCallback(self.checkFreeSpace)
3346
+ fini = slab.fini
3347
+ if ephemeral:
3348
+ fini = slab
3349
+ self.onfini(fini)
3350
+ return slab
3351
+
3177
3352
  async def _initCellSlab(self, readonly=False):
3178
3353
 
3179
3354
  s_common.gendir(self.dirn, 'slabs')
@@ -3185,10 +3360,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3185
3360
  _slab.initdb('hive')
3186
3361
  await _slab.fini()
3187
3362
 
3188
- self.slab = await s_lmdbslab.Slab.anit(path, map_size=SLAB_MAP_SIZE, readonly=readonly)
3189
- self.slab.addResizeCallback(self.checkFreeSpace)
3190
-
3191
- self.onfini(self.slab.fini)
3363
+ self.slab = await self._initSlabFile(path)
3192
3364
 
3193
3365
  async def _initCellAuth(self):
3194
3366
 
@@ -3598,13 +3770,24 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3598
3770
  return pars
3599
3771
 
3600
3772
  async def _initCellBoot(self):
3773
+ # NOTE: best hook point for custom provisioning
3601
3774
 
3602
- pnfo = await self._bootCellProv()
3775
+ isok, pnfo = await self._bootCellProv()
3603
3776
 
3604
3777
  # check this before we setup loadTeleCell()
3605
3778
  if not self._mustBootMirror():
3606
3779
  return
3607
3780
 
3781
+ if not isok:
3782
+ # The way that we get to this requires the following states to be true:
3783
+ # 1. self.dirn/cell.guid file is NOT present in the service directory.
3784
+ # 2. mirror config is present.
3785
+ # 3. aha:provision config is not set OR the aha:provision guid matches the self.dirn/prov.done file.
3786
+ mesg = 'Service has been configured to boot from an upstream mirror, but has entered into an invalid ' \
3787
+ 'state. This may have been caused by manipulation of the service storage or an error during a ' \
3788
+ f'backup / restore operation. {pnfo.get("mesg")}'
3789
+ raise s_exc.FatalErr(mesg=mesg)
3790
+
3608
3791
  async with s_telepath.loadTeleCell(self.dirn):
3609
3792
  await self._bootCellMirror(pnfo)
3610
3793
 
@@ -3736,7 +3919,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3736
3919
 
3737
3920
  provurl = self.conf.get('aha:provision')
3738
3921
  if provurl is None:
3739
- return
3922
+ return False, {'mesg': 'No aha:provision configuration has been provided to allow the service to '
3923
+ 'bootstrap via AHA.'}
3740
3924
 
3741
3925
  doneiden = None
3742
3926
 
@@ -3749,7 +3933,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3749
3933
  providen = urlinfo.get('path').strip('/')
3750
3934
 
3751
3935
  if doneiden == providen:
3752
- return
3936
+ return False, {'mesg': f'The aha:provision URL guid matches the service prov.done guid, '
3937
+ f'aha:provision={provurl}'}
3753
3938
 
3754
3939
  logger.info(f'Provisioning {self.getCellType()} from AHA service.')
3755
3940
 
@@ -3809,7 +3994,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3809
3994
 
3810
3995
  logger.info(f'Done provisioning {self.getCellType()} AHA service.')
3811
3996
 
3812
- return provconf, providen
3997
+ return True, {'conf': provconf, 'iden': providen}
3813
3998
 
3814
3999
  async def _bootProvConf(self, provconf):
3815
4000
  '''
@@ -3891,23 +4076,14 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3891
4076
  await self.nexsroot.enNexsLog()
3892
4077
  await self.sync()
3893
4078
 
3894
- async def _bootCellMirror(self, pnfo):
3895
- # this function must assume almost nothing is initialized
3896
- # but that's ok since it will only run rarely.
3897
- # It assumes it has a tuple of (provisioning configuration, provisioning iden) available
3898
- murl = self.conf.reqConfValu('mirror')
3899
- provconf, providen = pnfo
3900
-
3901
- logger.warning(f'Bootstrap mirror from: {murl} (this could take a while!)')
4079
+ async def _initCloneCell(self, proxy):
3902
4080
 
3903
4081
  tarpath = s_common.genpath(self.dirn, 'tmp', 'bootstrap.tgz')
3904
-
3905
4082
  try:
3906
4083
 
3907
- async with await s_telepath.openurl(murl) as cell:
3908
- await cell.readyToMirror()
4084
+ await proxy.readyToMirror()
3909
4085
  with s_common.genfile(tarpath) as fd:
3910
- async for byts in cell.iterNewBackupArchive(remove=True):
4086
+ async for byts in proxy.iterNewBackupArchive(remove=True):
3911
4087
  fd.write(byts)
3912
4088
 
3913
4089
  with tarfile.open(tarpath) as tgz:
@@ -3922,6 +4098,18 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3922
4098
  if os.path.isfile(tarpath):
3923
4099
  os.unlink(tarpath)
3924
4100
 
4101
+ async def _bootCellMirror(self, pnfo):
4102
+ # this function must assume almost nothing is initialized
4103
+ # but that's ok since it will only run rarely.
4104
+ # It assumes it has a tuple of (provisioning configuration, provisioning iden) available
4105
+ murl = self.conf.req('mirror')
4106
+ provconf, providen = pnfo.get('conf'), pnfo.get('iden')
4107
+
4108
+ logger.warning(f'Bootstrap mirror from: {murl} (this could take a while!)')
4109
+
4110
+ async with await s_telepath.openurl(murl) as proxy:
4111
+ await self._initCloneCell(proxy)
4112
+
3925
4113
  # Remove aha:provision from cell.yaml if it exists and the iden differs.
3926
4114
  mnfo = s_common.yamlload(self.dirn, 'cell.yaml')
3927
4115
  if mnfo:
@@ -4017,15 +4205,12 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
4017
4205
 
4018
4206
  try:
4019
4207
 
4020
- if 'dmon:listen' not in cell.conf:
4021
- await cell.dmon.listen(opts.telepath)
4022
- logger.info(f'...{cell.getCellType()} API (telepath): {opts.telepath}')
4023
- else:
4024
- lisn = cell.conf.get('dmon:listen')
4025
- if lisn is None:
4026
- lisn = cell.getLocalUrl()
4208
+ turl = cell._getDmonListen()
4209
+ if turl is None:
4210
+ turl = opts.telepath
4211
+ await cell.dmon.listen(turl)
4027
4212
 
4028
- logger.info(f'...{cell.getCellType()} API (telepath): {lisn}')
4213
+ logger.info(f'...{cell.getCellType()} API (telepath): {turl}')
4029
4214
 
4030
4215
  if 'https:port' not in cell.conf:
4031
4216
  await cell.addHttpsPort(opts.https)
@@ -4267,6 +4452,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
4267
4452
  'type': self.getCellType(),
4268
4453
  'iden': self.getCellIden(),
4269
4454
  'active': self.isactive,
4455
+ 'started': self.startms,
4270
4456
  'ready': self.nexsroot.ready.is_set(),
4271
4457
  'commit': self.COMMIT,
4272
4458
  'version': self.VERSION,
@@ -4684,18 +4870,28 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
4684
4870
  sslctx.check_hostname = False
4685
4871
  sslctx.verify_mode = ssl.CERT_NONE
4686
4872
 
4687
- if not opts['client_cert']:
4688
- return sslctx
4873
+ # crypto functions require reading certs/keys from disk so make a temp dir
4874
+ # to save any certs/keys to disk so they can be read.
4875
+ with self.getTempDir() as tmpdir:
4876
+ if opts.get('ca_cert'):
4877
+ ca_cert = opts.get('ca_cert').encode()
4878
+ with tempfile.NamedTemporaryFile(dir=tmpdir, mode='wb', delete=False) as fh:
4879
+ fh.write(ca_cert)
4880
+ try:
4881
+ sslctx.load_verify_locations(cafile=fh.name)
4882
+ except Exception as e: # pragma: no cover
4883
+ raise s_exc.BadArg(mesg=f'Error loading CA cert: {str(e)}') from None
4689
4884
 
4690
- client_cert = opts['client_cert'].encode()
4885
+ if not opts['client_cert']:
4886
+ return sslctx
4691
4887
 
4692
- if opts['client_key']:
4693
- client_key = opts['client_key'].encode()
4694
- else:
4695
- client_key = None
4696
- client_key_path = None
4888
+ client_cert = opts['client_cert'].encode()
4697
4889
 
4698
- with self.getTempDir() as tmpdir:
4890
+ if opts['client_key']:
4891
+ client_key = opts['client_key'].encode()
4892
+ else:
4893
+ client_key = None
4894
+ client_key_path = None
4699
4895
 
4700
4896
  with tempfile.NamedTemporaryFile(dir=tmpdir, mode='wb', delete=False) as fh:
4701
4897
  fh.write(client_cert)