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)