Added orchestration.Server remote status retrieval

This commit is contained in:
Marc Aymerich 2016-02-04 12:09:09 +00:00
parent e74964417a
commit 7578c47c9a
7 changed files with 113 additions and 17 deletions

26
TODO.md
View file

@ -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())

View file

@ -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)

View file

@ -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,

View 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

View file

@ -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")

View file

@ -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

View file

@ -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)