Added orchestration.Server remote status retrieval
This commit is contained in:
parent
e74964417a
commit
7578c47c9a
26
TODO.md
26
TODO.md
|
@ -460,3 +460,29 @@ mkhomedir_helper or create ssh homes with bash.rc and such
|
||||||
|
|
||||||
|
|
||||||
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
||||||
|
|
||||||
|
# orchestra.server PING/SSH+uptime status
|
||||||
|
class ServerState(models.Model):
|
||||||
|
server = models.OneToOneField(Server)
|
||||||
|
ping = models.CharField(max_length=256)
|
||||||
|
uptime = models.CharField(max_length=256)
|
||||||
|
from orchestra.contrib.orchestration.models import Server
|
||||||
|
from orchestra.utils.sys import run, sshrun, joinall
|
||||||
|
def retrieve_state(servers):
|
||||||
|
uptimes = []
|
||||||
|
pings = []
|
||||||
|
for server in servers:
|
||||||
|
address = server.get_address()
|
||||||
|
ping = run('ping -c 1 %s' % address, async=True)
|
||||||
|
pings.append(ping)
|
||||||
|
uptime = sshrun(address, 'uptime', persist=True, async=True)
|
||||||
|
uptimes.append(uptime)
|
||||||
|
|
||||||
|
pings = joinall(pings, silent=True)
|
||||||
|
uptimes = joinall(uptimes, silent=True)
|
||||||
|
for ping in pings:
|
||||||
|
print(ping.stdout.splitlines()[-1])
|
||||||
|
|
||||||
|
for uptime in uptimes:
|
||||||
|
print(uptime.stdout)
|
||||||
|
retrieve_state(Server.objects.all())
|
||||||
|
|
|
@ -11,6 +11,7 @@ from . import settings, helpers
|
||||||
from .backends import ServiceBackend
|
from .backends import ServiceBackend
|
||||||
from .forms import RouteForm
|
from .forms import RouteForm
|
||||||
from .models import Server, Route, BackendLog, BackendOperation
|
from .models import Server, Route, BackendLog, BackendOperation
|
||||||
|
from .utils import retrieve_state
|
||||||
from .widgets import RouteBackendSelect
|
from .widgets import RouteBackendSelect
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,9 +168,25 @@ class BackendLogAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class ServerAdmin(admin.ModelAdmin):
|
class ServerAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'address', 'os')
|
list_display = ('name', 'address', 'os', 'display_ping', 'display_uptime')
|
||||||
list_filter = ('os',)
|
list_filter = ('os',)
|
||||||
|
|
||||||
|
def display_ping(self, instance):
|
||||||
|
return self._remote_state[instance.pk][0]
|
||||||
|
display_ping.short_description = _("Ping")
|
||||||
|
display_ping.allow_tags = True
|
||||||
|
|
||||||
|
def display_uptime(self, instance):
|
||||||
|
return self._remote_state[instance.pk][1]
|
||||||
|
display_uptime.short_description = _("Uptime")
|
||||||
|
display_uptime.allow_tags = True
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
""" Order by structured name and imporve performance """
|
||||||
|
qs = super(ServerAdmin, self).get_queryset(request)
|
||||||
|
if request.method == 'GET' and request.resolver_match.func.__name__ == 'changelist_view':
|
||||||
|
self._remote_state = retrieve_state(qs)
|
||||||
|
return qs
|
||||||
|
|
||||||
admin.site.register(Server, ServerAdmin)
|
admin.site.register(Server, ServerAdmin)
|
||||||
admin.site.register(BackendLog, BackendLogAdmin)
|
admin.site.register(BackendLog, BackendLogAdmin)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Server(models.Model):
|
||||||
validators=[OrValidator(validate_ip_address, validate_hostname)],
|
validators=[OrValidator(validate_ip_address, validate_hostname)],
|
||||||
null=True, unique=True, help_text=_(
|
null=True, unique=True, help_text=_(
|
||||||
"Optional IP address or domain name. Name field will be used if not provided.<br>"
|
"Optional IP address or domain name. Name field will be used if not provided.<br>"
|
||||||
"If the IP address never change you can set this field and save DNS requests."))
|
"If the IP address never changes you can set this field and save DNS requests."))
|
||||||
description = models.TextField(_("description"), blank=True)
|
description = models.TextField(_("description"), blank=True)
|
||||||
os = models.CharField(_("operative system"), max_length=32,
|
os = models.CharField(_("operative system"), max_length=32,
|
||||||
choices=settings.ORCHESTRATION_OS_CHOICES,
|
choices=settings.ORCHESTRATION_OS_CHOICES,
|
||||||
|
|
31
orchestra/contrib/orchestration/utils.py
Normal file
31
orchestra/contrib/orchestration/utils.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from orchestra.utils.sys import run, sshrun, join
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_state(servers):
|
||||||
|
uptimes = []
|
||||||
|
pings = []
|
||||||
|
for server in servers:
|
||||||
|
address = server.get_address()
|
||||||
|
ping = run('ping -c 1 -w 1 %s' % address, async=True)
|
||||||
|
pings.append(ping)
|
||||||
|
uptime = sshrun(address, 'uptime', persist=True, async=True, options={'ConnectTimeout': 1})
|
||||||
|
uptimes.append(uptime)
|
||||||
|
|
||||||
|
state = {}
|
||||||
|
for server, ping, uptime in zip(servers, pings, uptimes):
|
||||||
|
ping = join(ping, silent=True)
|
||||||
|
ping = ping.stdout.splitlines()[-1].decode()
|
||||||
|
if ping.startswith('rtt'):
|
||||||
|
ping = '%s ms' % ping.split('/')[4]
|
||||||
|
else:
|
||||||
|
ping = '<span style="color:red"><b>offline<b></span>'
|
||||||
|
|
||||||
|
uptime = join(uptime, silent=True)
|
||||||
|
uptime = uptime.stdout.decode().split()
|
||||||
|
if uptime:
|
||||||
|
uptime = 'Up %s %s load %s %s %s' % (uptime[2], uptime[3], uptime[-3], uptime[-2], uptime[-1])
|
||||||
|
else:
|
||||||
|
uptime = '<span style="color:red"><b>Timeout<b></span>'
|
||||||
|
state[server.pk] = (ping, uptime)
|
||||||
|
|
||||||
|
return state
|
|
@ -348,6 +348,17 @@ class PHPUploadMaxFileSize(PHPAppOption):
|
||||||
regex = r'^[0-9]{1,3}M$'
|
regex = r'^[0-9]{1,3}M$'
|
||||||
|
|
||||||
|
|
||||||
|
class PHPUploadTmpDir(PHPAppOption):
|
||||||
|
name = 'upload_tmp_dir'
|
||||||
|
verbose_name = _("Upload tmp dir")
|
||||||
|
help_text = _("The temporary directory used for storing files when doing file upload. "
|
||||||
|
"Must be writable by whatever user PHP is running as. "
|
||||||
|
"If not specified PHP will use the system's default.<br>"
|
||||||
|
"If the directory specified here is not writable, PHP falls back to the "
|
||||||
|
"system default temporary directory. If open_basedir is on, then the system "
|
||||||
|
"default directory must be allowed for an upload to succeed.")
|
||||||
|
regex = r'.*$'
|
||||||
|
|
||||||
class PHPZendExtension(PHPAppOption):
|
class PHPZendExtension(PHPAppOption):
|
||||||
name = 'zend_extension'
|
name = 'zend_extension'
|
||||||
verbose_name = _("Zend extension")
|
verbose_name = _("Zend extension")
|
||||||
|
|
|
@ -251,6 +251,7 @@ WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (
|
||||||
'orchestra.contrib.webapps.options.PHPSuhosinSimulation',
|
'orchestra.contrib.webapps.options.PHPSuhosinSimulation',
|
||||||
'orchestra.contrib.webapps.options.PHPSuhosinExecutorIncludeWhitelist',
|
'orchestra.contrib.webapps.options.PHPSuhosinExecutorIncludeWhitelist',
|
||||||
'orchestra.contrib.webapps.options.PHPUploadMaxFileSize',
|
'orchestra.contrib.webapps.options.PHPUploadMaxFileSize',
|
||||||
|
'orchestra.contrib.webapps.options.PHPUploadTmpDir',
|
||||||
'orchestra.contrib.webapps.options.PHPZendExtension',
|
'orchestra.contrib.webapps.options.PHPZendExtension',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
|
|
|
@ -143,6 +143,14 @@ def join(iterator, display=False, silent=False, valid_codes=(0,)):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def joinall(iterators, **kwargs):
|
||||||
|
results = []
|
||||||
|
for iterator in iterators:
|
||||||
|
out = join(iterator, **kwargs)
|
||||||
|
results.append(out)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def run(command, display=False, valid_codes=(0,), silent=False, stdin=b'', async=False):
|
def run(command, display=False, valid_codes=(0,), silent=False, stdin=b'', async=False):
|
||||||
iterator = runiterator(command, display, stdin)
|
iterator = runiterator(command, display, stdin)
|
||||||
next(iterator)
|
next(iterator)
|
||||||
|
@ -151,22 +159,24 @@ def run(command, display=False, valid_codes=(0,), silent=False, stdin=b'', async
|
||||||
return join(iterator, display=display, silent=silent, valid_codes=valid_codes)
|
return join(iterator, display=display, silent=silent, valid_codes=valid_codes)
|
||||||
|
|
||||||
|
|
||||||
def sshrun(addr, command, *args, executable='bash', persist=False, **kwargs):
|
def sshrun(addr, command, *args, executable='bash', persist=False, options=None, **kwargs):
|
||||||
from .. import settings
|
base_options = {
|
||||||
options = [
|
'stricthostkeychecking': 'no',
|
||||||
'stricthostkeychecking=no',
|
'BatchMode': 'yes',
|
||||||
'BatchMode=yes',
|
'EscapeChar': 'none',
|
||||||
'EscapeChar=none',
|
}
|
||||||
]
|
|
||||||
if persist:
|
if persist:
|
||||||
options.extend((
|
from .. import settings
|
||||||
'ControlMaster=auto',
|
base_options.update({
|
||||||
'ControlPersist=yes',
|
'ControlMaster': 'auto',
|
||||||
'ControlPath=' + settings.ORCHESTRA_SSH_CONTROL_PATH,
|
'ControlPersist': 'yes',
|
||||||
))
|
'ControlPath': settings.ORCHESTRA_SSH_CONTROL_PATH,
|
||||||
|
})
|
||||||
|
base_options.update(options or {})
|
||||||
|
options = ['%s=%s' % (k, v) for k, v in base_options.items()]
|
||||||
options = ' -o '.join(options)
|
options = ' -o '.join(options)
|
||||||
cmd = 'ssh -o {options} -C root@{addr} {executable}'.format(options=options, addr=addr,
|
cmd = 'ssh -o {options} -C root@{addr} {executable}'.format(
|
||||||
executable=executable)
|
options=options, addr=addr, executable=executable)
|
||||||
return run(cmd, *args, stdin=command.encode('utf8'), **kwargs)
|
return run(cmd, *args, stdin=command.encode('utf8'), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue