import textwrap
import synapse.exc as s_exc
import synapse.common as s_common
import synapse.lib.stormtypes as s_stormtypes
[docs]
@s_stormtypes.registry.registerLib
class AhaLib(s_stormtypes.Lib):
'''
A Storm Library for interacting with AHA.
'''
_storm_locals = (
{'name': 'del', 'desc': '''Delete a service from AHA.
Examples:
Deleting a service with its relative name::
$lib.aha.del(00.mysvc...)
Deleting a service with its full name::
$lib.aha.del(00.mysvc.loop.vertex.link)
''',
'type': {'type': 'function', '_funcname': '_methAhaDel',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the service to delete. It is easiest to use the relative name of a service, ending with "...".', },
),
'returns': {'type': 'null'}}},
{'name': 'get', 'desc': '''Get information about an AHA service.
Examples:
Getting service information with a relative name::
$lib.aha.get(00.cortex...)
Getting service information with its full name::
$lib.aha.get(00.cortex.loop.vertex.link)
''',
'type': {'type': 'function', '_funcname': '_methAhaGet',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the AHA service to look up. It is easiest to use the relative name of a service, ending with "...".', },
{'name': 'filters', 'type': 'dict', 'default': None,
'desc': 'An optional dictionary of filters to use when resolving the AHA service.'}
),
'returns': {'type': ('null', 'dict'),
'desc': 'The AHA service information dictionary, or $lib.null.', }}},
{'name': 'list', 'desc': 'Enumerate all of the AHA services.',
'type': {'type': 'function', '_funcname': '_methAhaList', 'args': (),
'returns': {'name': 'Yields', 'type': 'list',
'desc': 'The AHA service dictionaries.', }}},
{'name': 'callPeerApi', 'desc': '''Call an API on all peers (leader and mirrors) of an AHA service and yield the responses from each.
Examples:
Call getCellInfo on an AHA service::
$todo = $lib.utils.todo('getCellInfo')
for $info in $lib.aha.callPeerApi(cortex..., $todo) {
$lib.print($info)
}
Call getCellInfo on an AHA service, skipping the invoking service::
$todo = $lib.utils.todo('getCellInfo')
for $info in $lib.aha.callPeerApi(cortex..., $todo, skiprun=$lib.cell.getCellInfo().cell.run) {
$lib.print($info)
}
Call method with arguments::
$todo = $lib.utils.todo(('method', ([1, 2]), ({'foo': 'bar'})))
for $info in $lib.aha.callPeerApi(cortex..., $todo) {
$lib.print($info)
}
''',
'type': {'type': 'function', '_funcname': '_methCallPeerApi',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the AHA service to call. It is easiest to use the relative name of a service, ending with "...".', },
{'name': 'todo', 'type': 'list',
'desc': 'The todo tuple (name, args, kwargs).'},
{'name': 'timeout', 'type': 'int', 'default': None,
'desc': 'Optional timeout in seconds.'},
{'name': 'skiprun', 'type': 'str', 'default': None,
'desc': '''Optional run ID argument that allows skipping results from a specific service run ID.
This is most often used to omit the invoking service from the results, ensuring that only responses from other services are included.
'''},
),
'returns': {'name': 'yields', 'type': 'list',
'desc': 'Yields the results of the API calls as tuples of (svcname, (ok, info)).', }}},
{'name': 'callPeerGenr', 'desc': '''Call a generator API on all peers (leader and mirrors) of an AHA service and yield the responses from each.
Examples:
Call getNexusChanges on an AHA service::
$todo = $lib.utils.todo('getNexusChanges', (0), wait=$lib.false)
for $info in $lib.aha.callPeerGenr(cortex..., $todo) {
$lib.print($info)
}
Call getNexusChanges on an AHA service, skipping the invoking service::
$todo = $lib.utils.todo('getNexusChanges', (0), wait=$lib.false)
for $info in $lib.aha.callPeerGenr(cortex..., $todo, skiprun=$lib.cell.getCellInfo().cell.run) {
$lib.print($info)
}
''',
'type': {'type': 'function', '_funcname': '_methCallPeerGenr',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the AHA service to call. It is easiest to use the relative name of a service, ending with "...".', },
{'name': 'todo', 'type': 'list',
'desc': 'The todo tuple (name, args, kwargs).'},
{'name': 'timeout', 'type': 'int', 'default': None,
'desc': 'Optional timeout in seconds.'},
{'name': 'skiprun', 'type': 'str', 'default': None,
'desc': '''Optional run ID argument that allows skipping results from a specific service run ID.
This is most often used to omit the invoking service from the results, ensuring that only responses from other services are included.
'''},
),
'returns': {'name': 'yields', 'type': 'list',
'desc': 'Yields the results of the API calls as tuples containing (svcname, (ok, info)).', }}}
)
_storm_lib_path = ('aha',)
[docs]
def getObjLocals(self):
return {
'del': self._methAhaDel,
'get': self._methAhaGet,
'list': self._methAhaList,
'callPeerApi': self._methCallPeerApi,
'callPeerGenr': self._methCallPeerGenr,
}
@s_stormtypes.stormfunc(readonly=True)
async def _methAhaList(self):
self.runt.reqAdmin()
proxy = await self.runt.snap.core.reqAhaProxy()
async for info in proxy.getAhaSvcs():
yield info
async def _methAhaDel(self, svcname):
self.runt.reqAdmin()
svcname = await s_stormtypes.tostr(svcname)
proxy = await self.runt.snap.core.reqAhaProxy()
svc = await proxy.getAhaSvc(svcname)
if svc is None:
raise s_exc.NoSuchName(mesg=f'No AHA service for {svcname=}')
if svc.get('services'): # It is an AHA Pool!
mesg = f'Cannot use $lib.aha.del() to remove an AHA Pool. Use $lib.aha.pool.del(); {svcname=}'
raise s_exc.BadArg(mesg=mesg)
return await proxy.delAhaSvc(svc.get('svcname'), network=svc.get('svcnetw'))
@s_stormtypes.stormfunc(readonly=True)
async def _methAhaGet(self, svcname, filters=None):
self.runt.reqAdmin()
svcname = await s_stormtypes.tostr(svcname)
filters = await s_stormtypes.toprim(filters)
proxy = await self.runt.snap.core.reqAhaProxy()
return await proxy.getAhaSvc(svcname, filters=filters)
async def _methCallPeerApi(self, svcname, todo, timeout=None, skiprun=None):
'''
Call an API on an AHA service.
Args:
svcname (str): The name of the AHA service to call.
todo (list): The todo tuple from $lib.utils.todo().
timeout (int): Optional timeout in seconds.
skiprun (str): Optional run ID argument allows skipping self-enumeration.
'''
svcname = await s_stormtypes.tostr(svcname)
todo = await s_stormtypes.toprim(todo)
timeout = await s_stormtypes.toint(timeout, noneok=True)
skiprun = await s_stormtypes.tostr(skiprun, noneok=True)
proxy = await self.runt.snap.core.reqAhaProxy()
svc = await proxy.getAhaSvc(svcname)
if svc is None:
raise s_exc.NoSuchName(mesg=f'No AHA service found for {svcname}')
svcinfo = svc.get('svcinfo')
svciden = svcinfo.get('iden')
if svciden is None:
raise s_exc.NoSuchName(mesg=f'Service {svcname} has no iden')
async for svcname, (ok, info) in proxy.callAhaPeerApi(svciden, todo, timeout=timeout, skiprun=skiprun):
yield (svcname, (ok, info))
async def _methCallPeerGenr(self, svcname, todo, timeout=None, skiprun=None):
'''
Call a generator API on an AHA service.
Args:
svcname (str): The name of the AHA service to call.
todo (list): The todo tuple from $lib.utils.todo().
timeout (int): Optional timeout in seconds.
skiprun (str): Optional run ID argument allows skipping self-enumeration.
'''
svcname = await s_stormtypes.tostr(svcname)
todo = await s_stormtypes.toprim(todo)
timeout = await s_stormtypes.toint(timeout, noneok=True)
skiprun = await s_stormtypes.tostr(skiprun, noneok=True)
proxy = await self.runt.snap.core.reqAhaProxy()
svc = await proxy.getAhaSvc(svcname)
if svc is None:
raise s_exc.NoSuchName(mesg=f'No AHA service found for {svcname}')
svcinfo = svc.get('svcinfo')
svciden = svcinfo.get('iden')
if svciden is None:
raise s_exc.NoSuchName(mesg=f'Service {svcname} has no iden')
async for svcname, (ok, info) in proxy.callAhaPeerGenr(svciden, todo, timeout=timeout, skiprun=skiprun):
yield (svcname, (ok, info))
[docs]
@s_stormtypes.registry.registerLib
class AhaPoolLib(s_stormtypes.Lib):
'''
A Storm Library for interacting with AHA service pools.
'''
_storm_locals = (
{'name': 'add', 'desc': '''Add a new AHA service pool.
Examples:
Add a pool via its relative name::
$lib.aha.pool.add(pool00.cortex...)
''',
'type': {'type': 'function', '_funcname': '_methPoolAdd',
'args': (
{'name': 'name', 'type': 'str',
'desc': 'The name of the pool to add. It is easiest to use the relative name of a pool, ending with "...".', },
),
'returns': {'type': 'aha:pool'}}},
{'name': 'del', 'desc': '''Delete an existing AHA service pool.
Examples:
Delete a pool via its relative name::
$lib.aha.pool.del(pool00.cortex...)
''',
'type': {'type': 'function', '_funcname': '_methPoolDel',
'args': (
{'name': 'name', 'type': 'str',
'desc': 'The name of the pool to delete. It is easiest to use the relative name of a pool, ending with "...".', },
),
'returns': {'type': 'dict', 'desc': 'The AHA pool definition that was deleted.'}}},
{'name': 'get', 'desc': 'Get an existing AHA service pool.',
'type': {'type': 'function', '_funcname': '_methPoolGet',
'args': (
{'name': 'name', 'type': 'str',
'desc': 'The name of the pool to get. It is easiest to use the relative name of a pool, ending with "...".', },
),
'returns': {'type': ['null', 'aha:pool'], 'desc': 'The pool if it exists, or $lib.null.'}}},
{'name': 'list', 'desc': 'Enumerate all of the AHA service pools.',
'type': {'type': 'function', '_funcname': '_methPoolList',
'returns': {'name': 'yields', 'type': 'aha:pool'}}},
)
_storm_lib_path = ('aha', 'pool')
[docs]
def getObjLocals(self):
return {
'add': self._methPoolAdd,
'del': self._methPoolDel,
'get': self._methPoolGet,
'list': self._methPoolList,
}
async def _methPoolAdd(self, name):
self.runt.reqAdmin()
name = await s_stormtypes.tostr(name)
proxy = await self.runt.snap.core.reqAhaProxy()
poolinfo = {'creator': self.runt.user.iden}
poolinfo = await proxy.addAhaPool(name, poolinfo)
return AhaPool(self.runt, poolinfo)
async def _methPoolDel(self, name):
self.runt.reqAdmin()
name = await s_stormtypes.tostr(name)
proxy = await self.runt.snap.core.reqAhaProxy()
return await proxy.delAhaPool(name)
@s_stormtypes.stormfunc(readonly=True)
async def _methPoolGet(self, name):
self.runt.reqAdmin()
name = await s_stormtypes.tostr(name)
proxy = await self.runt.snap.core.reqAhaProxy()
poolinfo = await proxy.getAhaPool(name)
if poolinfo is not None:
return AhaPool(self.runt, poolinfo)
@s_stormtypes.stormfunc(readonly=True)
async def _methPoolList(self):
self.runt.reqAdmin()
proxy = await self.runt.snap.core.reqAhaProxy()
async for poolinfo in proxy.getAhaPools():
yield AhaPool(self.runt, poolinfo)
[docs]
@s_stormtypes.registry.registerType
class AhaPool(s_stormtypes.StormType):
'''
Implements the Storm API for an AHA pool.
'''
_storm_locals = (
{'name': 'add', 'desc': '''Add a service to the AHA pool
Examples:
Add a service to a pool with its relative name::
$pool = $lib.aha.pool.get(pool00.cortex...)
$pool.add(00.cortex...)
''',
'type': {'type': 'function', '_funcname': '_methPoolSvcAdd',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the AHA service to add. It is easiest to use the relative name of a service, ending with "...".', },
),
'returns': {'type': 'null', }}},
{'name': 'del', 'desc': '''Remove a service from the AHA pool.
Examples:
Remove a service from a pool with its relative name::
$pool = $lib.aha.pool.get(pool00.cortex...)
$pool.del(00.cortex...)
''',
'type': {'type': 'function', '_funcname': '_methPoolSvcDel',
'args': (
{'name': 'svcname', 'type': 'str',
'desc': 'The name of the AHA service to remove. It is easiest to use the relative name of a service, ending with "...".', },
),
'returns': {'type': ['null', 'str'], 'desc': 'The service removed from the pool or null if a service was not removed.'}}},
)
_storm_typename = 'aha:pool'
def __init__(self, runt, poolinfo):
s_stormtypes.StormType.__init__(self)
self.runt = runt
self.poolinfo = poolinfo
self.locls.update({
'add': self._methPoolSvcAdd,
'del': self._methPoolSvcDel,
})
[docs]
async def stormrepr(self):
return f'{self._storm_typename}: {self.poolinfo.get("name")}'
async def _derefGet(self, name):
return self.poolinfo.get(name)
async def _methPoolSvcAdd(self, svcname):
self.runt.reqAdmin()
svcname = await s_stormtypes.tostr(svcname)
proxy = await self.runt.snap.core.reqAhaProxy()
poolname = self.poolinfo.get('name')
poolinfo = {'creator': self.runt.user.iden}
poolinfo = await proxy.addAhaPoolSvc(poolname, svcname, poolinfo)
self.poolinfo.update(poolinfo)
async def _methPoolSvcDel(self, svcname):
self.runt.reqAdmin()
svcname = await s_stormtypes.tostr(svcname)
proxy = await self.runt.snap.core.reqAhaProxy()
poolname = self.poolinfo.get('name')
newinfo = await proxy.delAhaPoolSvc(poolname, svcname)
tname = svcname
if tname.endswith('...'):
tname = tname[:-2]
deleted_service = None
deleted_services = [svc for svc in self.poolinfo.get('services').keys()
if svc not in newinfo.get('services') and svc.startswith(tname)]
if deleted_services:
deleted_service = deleted_services[0]
self.poolinfo = newinfo
return deleted_service
stormcmds = (
{
'name': 'aha.pool.list',
'descr': 'Display a list of AHA service pools and their services.',
'storm': '''
$count = (0)
for $pool in $lib.aha.pool.list() {
$count = ($count + 1)
$lib.print(`Pool: {$pool.name}`)
for ($svcname, $svcinfo) in $pool.services {
$lib.print(` {$svcname}`)
}
}
$lib.print(`{$count} pools.`)
''',
},
{
'name': 'aha.pool.add',
'descr': 'Create an AHA service pool configuration.',
'cmdargs': (
('name', {'help': 'The name of the new AHA service pool.'}),
),
'storm': '''
$pool = $lib.aha.pool.add($cmdopts.name)
$lib.print(`Created AHA service pool: {$pool.name}`)
'''
},
{
'name': 'aha.pool.del',
'descr': 'Delete an AHA service pool configuration.',
'cmdargs': (
('name', {'help': 'The name of the AHA pool to delete.'}),
),
'storm': '''
$pool = $lib.aha.pool.del($cmdopts.name)
if $pool { $lib.print(`Removed AHA service pool: {$pool.name}`) }
''',
},
{
'name': 'aha.pool.svc.add',
'descr': '''
Add an AHA service to a service pool.
Examples:
// add 00.cortex... to the existing pool named pool.cortex
aha.pool.svc.add pool.cortex... 00.cortex...
''',
'cmdargs': (
('poolname', {'help': 'The name of the AHA pool.'}),
('svcname', {'help': 'The name of the AHA service.'}),
),
'storm': '''
$pool = $lib.aha.pool.get($cmdopts.poolname)
if (not $pool) { $lib.exit(`No AHA service pool named: {$cmdopts.poolname}`) }
$pool.add($cmdopts.svcname)
$lib.print(`AHA service ({$cmdopts.svcname}) added to service pool ({$pool.name})`)
''',
},
{
'name': 'aha.pool.svc.del',
'descr': 'Remove an AHA service from a service pool.',
'cmdargs': (
('poolname', {'help': 'The name of the AHA pool.'}),
('svcname', {'help': 'The name of the AHA service.'}),
),
'storm': '''
$pool = $lib.aha.pool.get($cmdopts.poolname)
if (not $pool) { $lib.exit(`No AHA service pool named: {$cmdopts.poolname}`) }
$svc = $pool.del($cmdopts.svcname)
if $svc {
$lib.print(`AHA service ({$svc}) removed from service pool ({$pool.name})`)
} else {
$lib.print(`Did not remove ({$cmdopts.svcname}) from the service pool.`)
}
''',
},
{
'name': 'aha.svc.stat',
'descr': '''Show all information for a specific AHA service.
If the --nexus argument is given, the Cortex will attempt to connect the service and report the Nexus offset of the service.
The ready value indicates that a service has entered into the realtime change window for synchronizing changes from its leader.
''',
'cmdargs': (
('svc', {'help': 'The service to inspect.'}),
('--nexus', {'help': 'Try to connect to online services and report their nexus offset.',
'default': False, 'action': 'store_true'}),
),
'storm': '''
function _getNexus(svcname) {
$_url = `aha://{$svcname}/`
try {
$_prox = $lib.telepath.open($_url)
$_info = $_prox.getCellInfo()
return ( $_info.cell.nexsindx )
} catch * as _err {
$_emsg = $_err.mesg
if ($_emsg = null ) {
$_emsg = `{$_err}`
}
return ( $_emsg )
}
}
$svc = $lib.aha.get($cmdopts.svc)
if ($svc = null) {
$lib.print(`No service found for: "{$cmdopts.svc}"`)
} else {
$services = $svc.services
if $services {
$lib.print(`Resolved {$cmdopts.svc} to an AHA Pool.\n`)
$lib.print(`The pool currently has {$lib.len($services)} members.`)
$lib.print(`AHA Pool: {$svc.name}`)
for ($_svcname, $_svcinfo) in $services {
$lib.print(`Member: {$_svcname}`)
}
} else {
$lib.print(`Resolved {$cmdopts.svc} to an AHA Service.\n`)
$svcinfo = $svc.svcinfo
$leader = $svcinfo.leader
if ($leader = null) {
$leader = 'Service did not register itself with a leader name.'
}
$online = false
if $svcinfo.online {
$online = true
}
$ready = 'null'
if $lib.dict.has($svcinfo, ready) {
$ready = `{$svcinfo.ready}`
}
$lib.print(`Name: {$svc.name}`)
$lib.print(`Online: {$online}`)
$lib.print(`Ready: {$ready}`)
$lib.print(`Run iden: {$svcinfo.run}`)
$lib.print(`Cell iden: {$svcinfo.iden}`)
$lib.print(`Leader: {$leader}`)
if $cmdopts.nexus {
if $svcinfo.online {
$nexusOffset = $_getNexus($svc.name)
} else {
$nexusOffset = 'Service is not online. Will not attempt to retrieve its nexus offset.'
}
$lib.print(`Nexus: {$nexusOffset}`)
}
$lib.print('Connection information:')
$urlinfo = $svcinfo.urlinfo
$keys = $lib.dict.keys($urlinfo)
$keys.sort()
for $k in $keys {
$dk = `{$k}:`
$dk = $dk.ljust(12)
$lib.print(` {$dk}{$urlinfo.$k}`)
}
}
}
'''
},
{
'name': 'aha.svc.list',
'descr': '''List AHA services.
If the --nexus argument is given, the Cortex will attempt to connect to each service and report the Nexus offset of the service.
The ready column indicates that a service has entered into the realtime change window for synchronizing changes from its leader.''',
'cmdargs': (
('--nexus', {'help': 'Try to connect to online services and report their nexus offset.',
'default': False, 'action': 'store_true'}),
),
'storm': '''
function _getNexus(svcname) {
$_url = `aha://{$svcname}/`
try {
$_prox = $lib.telepath.open($_url)
$_info = $_prox.getCellInfo()
return ( $_info.cell.nexsindx )
} catch * as _err {
$_emsg = $_err.mesg
if ($_emsg = null ) {
$_emsg = `{$_err}`
}
return ( $_emsg )
}
}
$svcs = ()
for $svc in $lib.aha.list() {
$svcs.append($svc)
}
if ($lib.len($svcs) = 0) {
$lib.print('No AHA services registered.')
}
else {
$columns = 'Name Leader Online Ready Host Port '
if $cmdopts.nexus {
$columns = `{$columns} Nexus`
}
$leaders = $lib.set()
for $info in $svcs {
$svcinfo = $info.svcinfo
if $svcinfo {
if ($info.svcname = $svcinfo.leader) {
$leaders.add($svcinfo.run)
}
}
}
$lib.print($columns)
for $info in $svcs {
$name = $info.name
$nexusOffset = (null)
$svcinfo = $info.svcinfo
if $cmdopts.nexus {
if $svcinfo.online {
$nexusOffset = $_getNexus($name)
} else {
$nexusOffset = '<offline>'
}
}
$name=$name.ljust(45)
$online = false
if $svcinfo.online {
$online = true
}
$online = $online.ljust(6)
$urlinfo = $svcinfo.urlinfo
$host = $urlinfo.host
$host = $host.ljust(15)
$port = $lib.cast(str, $urlinfo.port) // Cast to str
$port = $port.ljust(5)
$ready = 'null'
if $lib.dict.has($svcinfo, ready) {
$ready = `{$svcinfo.ready}`
}
$ready = $ready.ljust(5)
$leader = null
if ( $svcinfo.leader != null ) {
if $leaders.has($svcinfo.run) {
$leader = true
} else {
$leader = false
}
}
$leader = $leader.ljust(6)
if $info {
$s = `{$name} {$leader} {$online} {$ready} {$host} {$port}`
if ($nexusOffset != null) {
$s = `{$s} {$nexusOffset}`
}
$lib.print($s)
}
}
}
'''
},
{
'name': 'aha.svc.mirror',
'descr': textwrap.dedent('''\
Query the AHA services and their mirror relationships.
Note: non-mirror services are not displayed.
'''),
'cmdargs': (
('--timeout', {'help': 'The timeout in seconds for individual service API calls.',
'default': 10, 'type': 'int'}),
('--wait', {'help': 'Whether to wait for the mirrors to sync.',
'action': 'store_true'}),
),
'storm': '''
init {
$conf = ({
"columns": [
{"name": "name", "width": 40},
{"name": "role", "width": 9},
{"name": "online", "width": 7},
{"name": "ready", "width": 6},
{"name": "host", "width": 16},
{"name": "port", "width": 8},
{"name": "version", "width": 12},
{"name": "nexus idx", "width": 10},
],
"separators": {
"row:outline": false,
"column:outline": false,
"header:row": "#",
"data:row": "",
"column": "",
},
})
$printer = $lib.tabular.printer($conf)
$timeout = $cmdopts.timeout
$wait = $cmdopts.wait
}
function get_cell_infos(vname, timeout) {
$cell_infos = ({})
$todo = $lib.utils.todo('getCellInfo')
for $info in $lib.aha.callPeerApi($vname, $todo, timeout=$timeout) {
$svcname = $info.0
($ok, $info) = $info.1
if $ok {
$cell_infos.$svcname = $info
}
}
return($cell_infos)
}
function build_status_list(members, cell_infos) {
$group_status = ()
for $svc in $members {
$svcinfo = $svc.svcinfo
$svcname = $svc.name
$status = ({
'name': $svcname,
'role': '<unknown>',
'online': $lib.dict.has($svcinfo, 'online'),
'ready': $svcinfo.ready,
'host': $svcinfo.urlinfo.host,
'port': $svcinfo.urlinfo.port,
'version': '<unknown>',
'nexs_indx': (0)
})
if ($cell_infos.$svcname) {
$info = $cell_infos.$svcname
$cell_info = $info.cell
$status.nexs_indx = $cell_info.nexsindx
if ($cell_info.uplink) {
$status.role = 'follower'
} else {
$status.role = 'leader'
}
$status.version = $info.synapse.verstring
}
$group_status.append($status)
}
return($group_status)
}
function check_sync_status(group_status) {
$indices = $lib.set()
$known_count = (0)
for $status in $group_status {
$indices.add($status.nexs_indx)
$known_count = ($known_count + (1))
}
if ($lib.len($indices) = 1) {
if ($known_count = $lib.len($group_status)) {
return(true)
}
}
}
function output_status(vname, group_status, printer) {
$lib.print($printer.header())
$lib.print($vname)
for $status in $group_status {
if ($status.nexs_indx = 0) {
$status.nexs_indx = '<unknown>'
}
$row = (
$status.name,
$status.role,
$status.online,
$status.ready,
$status.host,
$status.port,
$status.version,
$status.nexs_indx
)
$lib.print($printer.row($row))
}
}
$virtual_services = ({})
$member_servers = ({})
for $svc in $lib.aha.list() {
$name = $svc.name
$svcinfo = $svc.svcinfo
$urlinfo = $svcinfo.urlinfo
$hostname = $urlinfo.hostname
if ($name != $hostname) {
$virtual_services.$name = $svc
} else {
$member_servers.$name = $svc
}
}
$mirror_groups = ({})
for ($vname, $vsvc) in $virtual_services {
$vsvc_info = $vsvc.svcinfo
$vsvc_iden = $vsvc_info.iden
$vsvc_leader = $vsvc_info.leader
$vsvc_hostname = $vsvc_info.urlinfo.hostname
if (not $vsvc_iden or not $vsvc_hostname or not $vsvc_leader) {
continue
}
$primary_member = $member_servers.$vsvc_hostname
if (not $primary_member) {
continue
}
$members = ([$primary_member])
for ($mname, $msvc) in $member_servers {
if ($mname != $vsvc_hostname) {
$msvc_info = $msvc.svcinfo
if ($msvc_info.iden = $vsvc_iden and $msvc_info.leader = $vsvc_leader) {
$members.append($msvc)
}
}
}
if ($lib.len($members) > 1) {
$mirror_groups.$vname = $members
}
}
for ($vname, $members) in $mirror_groups {
$cell_infos = $get_cell_infos($vname, $timeout)
$group_status = $build_status_list($members, $cell_infos)
$lib.print('Service Mirror Groups:')
$output_status($vname, $group_status, $printer)
if $check_sync_status($group_status) {
$lib.print('Group Status: In Sync')
} else {
$lib.print(`Group Status: Out of Sync`)
if $wait {
$leader_nexs = (0)
for $status in $group_status {
if (($status.role = 'leader') and ($status.nexs_indx > 0)) {
$leader_nexs = $status.nexs_indx
}
}
if ($leader_nexs > 0) {
while (true) {
$responses = ()
$todo = $lib.utils.todo(waitNexsOffs, ($leader_nexs - 1), timeout=$timeout)
for $info in $lib.aha.callPeerApi($vname, $todo, timeout=$timeout) {
$svcname = $info.0
($ok, $info) = $info.1
if ($ok and $info) {
$responses.append(($svcname, $info))
}
}
if ($lib.len($responses) = $lib.len($members)) {
$cell_infos = $get_cell_infos($vname, $timeout)
$group_status = $build_status_list($members, $cell_infos)
$lib.print('')
$lib.print('Updated status:')
$output_status($vname, $group_status, $printer)
if $check_sync_status($group_status) {
$lib.print('Group Status: In Sync')
break
}
}
}
}
}
}
$lib.print('')
}
'''
},
)