From 7578c47c9ab18676fe81efcacfa7fd9100898e2b Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Thu, 4 Feb 2016 12:09:09 +0000 Subject: [PATCH] Added orchestration.Server remote status retrieval --- TODO.md | 26 ++++++++++++++++ orchestra/contrib/orchestration/admin.py | 21 +++++++++++-- orchestra/contrib/orchestration/models.py | 2 +- orchestra/contrib/orchestration/utils.py | 31 ++++++++++++++++++ orchestra/contrib/webapps/options.py | 11 +++++++ orchestra/contrib/webapps/settings.py | 1 + orchestra/utils/sys.py | 38 ++++++++++++++--------- 7 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 orchestra/contrib/orchestration/utils.py diff --git a/TODO.md b/TODO.md index bfad8a93..bfa860a2 100644 --- a/TODO.md +++ b/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>' + +# 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()) diff --git a/orchestra/contrib/orchestration/admin.py b/orchestra/contrib/orchestration/admin.py index 19c9a876..4a0d7bad 100644 --- a/orchestra/contrib/orchestration/admin.py +++ b/orchestra/contrib/orchestration/admin.py @@ -11,6 +11,7 @@ from . import settings, helpers from .backends import ServiceBackend from .forms import RouteForm from .models import Server, Route, BackendLog, BackendOperation +from .utils import retrieve_state from .widgets import RouteBackendSelect @@ -167,9 +168,25 @@ class BackendLogAdmin(admin.ModelAdmin): class ServerAdmin(admin.ModelAdmin): - list_display = ('name', 'address', 'os') + list_display = ('name', 'address', 'os', 'display_ping', 'display_uptime') 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(BackendLog, BackendLogAdmin) diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py index dd14a0e7..9e65638b 100644 --- a/orchestra/contrib/orchestration/models.py +++ b/orchestra/contrib/orchestration/models.py @@ -28,7 +28,7 @@ class Server(models.Model): validators=[OrValidator(validate_ip_address, validate_hostname)], null=True, unique=True, help_text=_( "Optional IP address or domain name. Name field will be used if not provided.
" - "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) os = models.CharField(_("operative system"), max_length=32, choices=settings.ORCHESTRATION_OS_CHOICES, diff --git a/orchestra/contrib/orchestration/utils.py b/orchestra/contrib/orchestration/utils.py new file mode 100644 index 00000000..cf227260 --- /dev/null +++ b/orchestra/contrib/orchestration/utils.py @@ -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 = 'offline' + + 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 = 'Timeout' + state[server.pk] = (ping, uptime) + + return state diff --git a/orchestra/contrib/webapps/options.py b/orchestra/contrib/webapps/options.py index 220d2d93..6e917df4 100644 --- a/orchestra/contrib/webapps/options.py +++ b/orchestra/contrib/webapps/options.py @@ -348,6 +348,17 @@ class PHPUploadMaxFileSize(PHPAppOption): 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.
" + "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): name = 'zend_extension' verbose_name = _("Zend extension") diff --git a/orchestra/contrib/webapps/settings.py b/orchestra/contrib/webapps/settings.py index 4fc97bc2..e5f0298c 100644 --- a/orchestra/contrib/webapps/settings.py +++ b/orchestra/contrib/webapps/settings.py @@ -251,6 +251,7 @@ WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', ( 'orchestra.contrib.webapps.options.PHPSuhosinSimulation', 'orchestra.contrib.webapps.options.PHPSuhosinExecutorIncludeWhitelist', 'orchestra.contrib.webapps.options.PHPUploadMaxFileSize', + 'orchestra.contrib.webapps.options.PHPUploadTmpDir', 'orchestra.contrib.webapps.options.PHPZendExtension', ), # lazy loading diff --git a/orchestra/utils/sys.py b/orchestra/utils/sys.py index bd98409b..e39bcd91 100644 --- a/orchestra/utils/sys.py +++ b/orchestra/utils/sys.py @@ -143,6 +143,14 @@ def join(iterator, display=False, silent=False, valid_codes=(0,)): 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): iterator = runiterator(command, display, stdin) 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) -def sshrun(addr, command, *args, executable='bash', persist=False, **kwargs): - from .. import settings - options = [ - 'stricthostkeychecking=no', - 'BatchMode=yes', - 'EscapeChar=none', - ] +def sshrun(addr, command, *args, executable='bash', persist=False, options=None, **kwargs): + base_options = { + 'stricthostkeychecking': 'no', + 'BatchMode': 'yes', + 'EscapeChar': 'none', + } if persist: - options.extend(( - 'ControlMaster=auto', - 'ControlPersist=yes', - 'ControlPath=' + settings.ORCHESTRA_SSH_CONTROL_PATH, - )) + from .. import settings + base_options.update({ + 'ControlMaster': 'auto', + '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) - cmd = 'ssh -o {options} -C root@{addr} {executable}'.format(options=options, addr=addr, - executable=executable) + cmd = 'ssh -o {options} -C root@{addr} {executable}'.format( + options=options, addr=addr, executable=executable) return run(cmd, *args, stdin=command.encode('utf8'), **kwargs)