Merge remote-tracking branch 'vanilla/dev' into dev

This commit is contained in:
titoko 2013-12-05 21:33:30 +01:00
commit b39815a89c
6 changed files with 651 additions and 217 deletions

View file

@ -57,10 +57,18 @@ Specifications
### Monitoring ### Monitoring
yunohost monitor info [-h] [-u] [-d] [-p] [-c] [-m] [-i] yunohost monitor disk [-h] [-m MOUNTPOINT] [-t] [-f] [-H]
yunohost monitor process [-h] [-e PROCESS] [-d PROCESS] yunohost monitor network [-h] [-u] [-i] [-H]
[--stop PROCESS] [-c PORT] [-i] yunohost monitor system [-h] [-m] [-u] [-i] [-p] [-c] [-H]
[--start PROCESS]
### Services
yunohost service status [-h] [NAME [NAME ...]]
yunohost service start [-h] NAME [NAME ...]
yunohost service stop [-h] NAME [NAME ...]
yunohost service enable [-h] NAME [NAME ...]
yunohost service disable [-h] NAME [NAME ...]
### Tools ### Tools

View file

@ -334,6 +334,31 @@ app:
-v: -v:
full: --value full: --value
help: Value to set help: Value to set
-d:
full: --delete
help: Delete the key
action: store_true
### app_service()
service:
action_help: Add or remove a YunoHost monitored service
api: POST /app/service/{service}
arguments:
service:
help: Service to add/remove
-s:
full: --status
help: Custom status command
-l:
full: --log
help: Absolute path to log file to display
-r:
full: --runlevel
help: Runlevel priority of the service
-R:
full: --remove
help: Remove service
action: store_true
### app_checkport() ### app_checkport()
checkport: checkport:
@ -421,68 +446,148 @@ backup:
# Monitor # # Monitor #
############################# #############################
monitor: monitor:
category_help: Monitoring functions category_help: Monitor the server
actions: actions:
### monitor_info() ### monitor_disk()
info: disk:
action_help: Check System action_help: Monitor disk space and usage
arguments:
-f:
full: --filesystem
help: Show filesystem disk space
action: append_const
const: filesystem
dest: units
-t:
full: --io
help: Show I/O throughput
action: append_const
const: io
dest: units
-m:
full: --mountpoint
help: Monitor only the device mounted on MOUNTPOINT
action: store
-H:
full: --human-readable
help: Print sizes in human readable format
action: store_true
### monitor_network()
network:
action_help: Monitor network interfaces
arguments:
-u:
full: --usage
help: Show interfaces bit rates
action: append_const
const: usage
dest: units
-i:
full: --infos
help: Show network informations
action: append_const
const: infos
dest: units
-H:
full: --human-readable
help: Print sizes in human readable format
action: store_true
### monitor_system()
system:
action_help: Monitor system informations and usage
arguments: arguments:
-m: -m:
full: --memory full: --memory
help: Check Memory help: Show memory usage
action: store_true action: append_const
-s: const: memory
full: --swap dest: units
help: Check Swap
action: store_true
-c: -c:
full: --cpu full: --cpu
help: Check CPU help: Show CPU usage and load
action: store_true action: append_const
-d: const: cpu
full: --disk dest: units
help: Check Disk -p:
action: store_true full: --process
-i: help: Show processes summary
full: --ifconfig action: append_const
help: Show Ip and MAC Adress const: process
action: store_true dest: units
-u: -u:
full: --uptime full: --uptime
help: Show Uptime help: Show the system uptime
action: store_true action: append_const
-p: const: uptime
full: --public dest: units
help: Show IP public
action: store_true
process:
action_help: Check Process
arguments:
-e:
full: --enable
help: Enable process
metavar: PROCESS
-d:
full: --disable
help: Disable process
metavar: PROCESS
--start:
help: Start process
metavar: PROCESS
--stop:
help: Stop process
metavar: PROCESS
-c:
full: --check
help: Check process
action: store_true
-i: -i:
full: --info full: --infos
help: Process info help: Show system informations
action: append_const
const: infos
dest: units
-H:
full: --human-readable
help: Print sizes in human readable format
action: store_true action: store_true
#############################
# Service #
#############################
service:
category_help: Manage services
actions:
### service_start()
start:
action_help: Start one or more services
arguments:
names:
help: Service name to start
nargs: +
metavar: NAME
### service_stop()
stop:
action_help: Stop one or more services
arguments:
names:
help: Service name to stop
nargs: +
metavar: NAME
### service_enable()
enable:
action_help: Enable one or more services
arguments:
names:
help: Service name to enable
nargs: +
metavar: NAME
### service_disable()
disable:
action_help: Disable one or more services
arguments:
names:
help: Service name to disable
nargs: +
metavar: NAME
### service_status()
status:
action_help: Show status information about one or more services (all by default)
arguments:
names:
help: Service name to show
nargs: "*"
metavar: NAME
############################# #############################
# Firewall # # Firewall #
############################# #############################

View file

@ -1,4 +1,4 @@
apache2: nginx:
status: service status: service
bind9: bind9:
status: service status: service

View file

@ -550,7 +550,7 @@ def app_removeaccess(apps, users):
app_ssowatconf() app_ssowatconf()
def app_setting(app, key, value=None): def app_setting(app, key, value=None, delete=False):
""" """
Set ou get an app setting value Set ou get an app setting value
@ -558,6 +558,7 @@ def app_setting(app, key, value=None):
value -- Value to set value -- Value to set
app -- App ID app -- App ID
key -- Key to get/set key -- Key to get/set
delete -- Delete the key
""" """
settings_file = apps_setting_path + app +'/settings.yml' settings_file = apps_setting_path + app +'/settings.yml'
@ -569,7 +570,7 @@ def app_setting(app, key, value=None):
# Do not fail if setting file is not there # Do not fail if setting file is not there
app_settings = {} app_settings = {}
if value is None: if value is None and not delete:
# Get the value # Get the value
if app_settings is not None and key in app_settings: if app_settings is not None and key in app_settings:
print(app_settings[key]) print(app_settings[key])
@ -577,7 +578,7 @@ def app_setting(app, key, value=None):
# Set the value # Set the value
if app_settings is None: if app_settings is None:
app_settings = {} app_settings = {}
if value == '' and key in app_settings: if delete and key in app_settings:
del app_settings[key] del app_settings[key]
else: else:
app_settings[key] = value app_settings[key] = value
@ -586,6 +587,45 @@ def app_setting(app, key, value=None):
yaml.safe_dump(app_settings, f, default_flow_style=False) yaml.safe_dump(app_settings, f, default_flow_style=False)
def app_service(service, status=None, log=None, runlevel=None, remove=False):
"""
Add or remove a YunoHost monitored service
Keyword argument:
service -- Service to add/remove
status -- Custom status command
log -- Absolute path to log file to display
runlevel -- Runlevel priority of the service
remove -- Remove service
"""
service_file = '/etc/yunohost/services.yml'
try:
with open(service_file) as f:
services = yaml.load(f)
except IOError:
# Do not fail if service file is not there
services = {}
if remove and service in services:
del services[service]
else:
if status is None:
services[service] = { 'status': 'service' }
else:
services[service] = { 'status': status }
if log is not None:
services[service]['log'] = log
if runlevel is not None:
services[service]['runlevel'] = runlevel
with open(service_file, 'w') as f:
yaml.safe_dump(services, f, default_flow_style=False)
def app_checkport(port): def app_checkport(port):
""" """
Check availability of a local port Check availability of a local port

View file

@ -23,38 +23,266 @@
Monitoring functions Monitoring functions
""" """
import xmlrpclib import re
import json import json
import psutil import psutil
import subprocess
import xmlrpclib
from urllib import urlopen from urllib import urlopen
from datetime import datetime, timedelta from datetime import datetime, timedelta
from yunohost import YunoHostError, win_msg, colorize, validate, get_required_args from yunohost import YunoHostError
import os
import sys
try:
import yaml
except ImportError:
sys.stderr.write('Error: Yunohost CLI Require yaml lib\n')
sys.stderr.write('apt-get install python-yaml\n')
sys.exit(1)
import json
import socket
import fcntl
import struct
if not __debug__:
import traceback
s = xmlrpclib.ServerProxy('http://127.0.0.1:61209') glances_uri = 'http://127.0.0.1:61209'
def get_ip_address(ifname): def monitor_disk(units=None, mountpoint=None, human_readable=False):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) """
return socket.inet_ntoa(fcntl.ioctl( Monitor disk space and usage
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
def bytes2human(n): Keyword argument:
units -- Unit(s) to monitor
mountpoint -- Device mountpoint
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result_dname = None
result = {}
if units is None:
units = ['io', 'filesystem']
# Get mounted block devices
devices = {}
output = subprocess.check_output('lsblk -o NAME,MOUNTPOINT -l -n'.split())
for d in output.split('\n'):
m = re.search(r'([a-z]+[0-9]*)[ ]+(\/\S*)', d) # Extract device name (1) and its mountpoint (2)
if m and (mountpoint is None or m.group(2) == mountpoint):
(dn, dm) = (m.group(1), m.group(2))
devices[dn] = dm
result[dn] = {} if len(units) > 1 else []
result_dname = dn if mountpoint is not None else None
if len(devices) == 0:
if mountpoint is None:
raise YunoHostError(1, _("No mounted block device found"))
raise YunoHostError(1, _("Unknown mountpoint '%s'") % mountpoint)
# Retrieve monitoring for unit(s)
for u in units:
if u == 'io':
for d in json.loads(glances.getDiskIO()):
dname = d['disk_name']
if dname in devices.keys():
del d['disk_name']
if len(units) > 1:
result[dname][u] = d
else:
d['mnt_point'] = devices[dname]
result[dname] = d
for dname in devices.keys():
if len(units) > 1 and u not in result[dname]:
result[dname][u] = 'not-available'
elif len(result[dname]) == 0:
result[dname] = 'not-available'
elif u == 'filesystem':
for d in json.loads(glances.getFs()):
dmount = d['mnt_point']
for (dn, dm) in devices.items():
# TODO: Show non-block filesystems?
if dm != dmount:
continue
del d['device_name']
if human_readable:
for i in ['used', 'avail', 'size']:
d[i] = _binary_to_human(d[i]) + 'B'
if len(units) > 1:
result[dn][u] = d
else:
result[dn] = d
else:
raise YunoHostError(1, _("Unknown unit '%s'") % u)
if result_dname is not None:
return result[result_dname]
return result
def monitor_network(units=None, human_readable=False):
"""
Monitor network interfaces
Keyword argument:
units -- Unit(s) to monitor
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result = {}
if units is None:
units = ['usage', 'infos']
# Get network devices and their addresses
devices = {}
output = subprocess.check_output('ip addr show'.split())
for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
d = re.sub('\n[ ]+', ' % ', d) # Replace new lines by %
m = re.match('([a-z]+[0-9]?): (.*)', d) # Extract device name (1) and its addresses (2)
if m:
devices[m.group(1)] = m.group(2)
# Retrieve monitoring for unit(s)
for u in units:
if u == 'usage':
result[u] = {}
for i in json.loads(glances.getNetwork()):
iname = i['interface_name']
if iname in devices.keys():
del i['interface_name']
if human_readable:
for k in i.keys():
if k != 'time_since_update':
i[k] = _binary_to_human(i[k]) + 'B'
result[u][iname] = i
elif u == 'infos':
try:
p_ip = str(urlopen('http://ip.yunohost.org').read())
except:
raise YunoHostError(1, _("Public IP resolution failed"))
l_ip = None
for name, addrs in devices.items():
if name == 'lo':
continue
if len(devices) == 2:
l_ip = _extract_inet(addrs)
else:
if l_ip is None:
l_ip = {}
l_ip[name] = _extract_inet(addrs)
gateway = None
output = subprocess.check_output('ip route show'.split())
m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
if m:
gateway = _extract_inet(m.group(1), True)
result[u] = {
'public_ip': p_ip,
'local_ip': l_ip,
'gateway': gateway
}
else:
raise YunoHostError(1, _("Unknown unit '%s'") % u)
if len(units) == 1:
return result[units[0]]
return result
def monitor_system(units=None, human_readable=False):
"""
Monitor system informations and usage
Keyword argument:
units -- Unit(s) to monitor
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result = {}
if units is None:
units = ['memory', 'cpu', 'process', 'uptime', 'infos']
# Retrieve monitoring for unit(s)
for u in units:
if u == 'memory':
ram = json.loads(glances.getMem())
swap = json.loads(glances.getMemSwap())
if human_readable:
for i in ram.keys():
if i != 'percent':
ram[i] = _binary_to_human(ram[i]) + 'B'
for i in swap.keys():
if i != 'percent':
swap[i] = _binary_to_human(swap[i]) + 'B'
result[u] = {
'ram': ram,
'swap': swap
}
elif u == 'cpu':
result[u] = {
'load': json.loads(glances.getLoad()),
'usage': json.loads(glances.getCpu())
}
elif u == 'process':
result[u] = json.loads(glances.getProcessCount())
elif u == 'uptime':
result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).split('.')[0])
elif u == 'infos':
result[u] = json.loads(glances.getSystem())
else:
raise YunoHostError(1, _("Unknown unit '%s'") % u)
if len(units) == 1 and type(result[units[0]]) is not str:
return result[units[0]]
return result
def _get_glances_api():
"""
Retrieve Glances API running on the local server
"""
try:
p = xmlrpclib.ServerProxy(glances_uri)
p.system.methodHelp('getAll')
except (xmlrpclib.ProtocolError, IOError):
# TODO: Try to start Glances service
raise YunoHostError(1, _("Connection to Glances server failed"))
return p
def _extract_inet(string, skip_netmask=False):
"""
Extract IP address (v4 or v6) from a string
Keyword argument:
string -- String to search in
"""
# TODO: Return IPv4 and IPv6?
ip4 = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
ip6 = '((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?'
ip4 += '/[0-9]{1,2})' if not skip_netmask else ')'
ip6 += '/[0-9]{1,2})' if not skip_netmask else ')'
ip4_prog = re.compile(ip4)
ip6_prog = re.compile(ip6)
m = ip4_prog.search(string)
if m:
return m.group(1)
m = ip6_prog.search(string)
if m:
return m.group(1)
return None
def _binary_to_human(n, customary=False):
"""
Convert bytes or bits into human readable format with binary prefix
Keyword argument:
n -- Number to convert
customary -- Use customary symbol instead of IEC standard
"""
symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
if customary:
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {} prefix = {}
for i, s in enumerate(symbols): for i, s in enumerate(symbols):
@ -63,137 +291,4 @@ def bytes2human(n):
if n >= prefix[s]: if n >= prefix[s]:
value = float(n) / prefix[s] value = float(n) / prefix[s]
return '%.1f%s' % (value, s) return '%.1f%s' % (value, s)
return "%sB" % n return "%s" % n
def process_enable(args):
output = subprocess.Popen(['update-rc.d', args, 'defaults'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if output.wait() == 0:
return process_start(args)
return resultat
else:
raise YunoHostError(1, 'Enable : ' + args.title() + " " + _("failure"))
def process_disable(args):
output = subprocess.Popen(['update-rc.d', args, 'remove'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if output.wait() == 0:
return process_stop(args)
return resultat
else:
raise YunoHostError(1, 'Disable : ' + args.title() + " " + _("failure"))
def process_start(args):
output = subprocess.Popen(['service', args, 'start'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if output.wait() == 0:
return { 'Start' : args.title() }
else:
raise YunoHostError(1, 'Start : ' + args.title() + " " + _("failure"))
def process_stop(args):
output = subprocess.Popen(['service', args, 'stop'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if output.wait() == 0:
return { 'Stop' : args.title() }
else:
raise YunoHostError(1, 'Stop : ' + args.title() + " " + _("failure"))
def process_check(args):
with open('process.yml', 'r') as f:
processes = yaml.load(f)
result = {}
for process, commands in processes.items():
if commands['status'] == 'service':
cmd = "service " + process + " status"
else:
cmd = commands['status']
if os.system(cmd + " > /dev/null 2>&1") == 0:
result.update({ process : _('Running') })
else:
result.update({ process : _('Down') })
return { 'Status' : result }
def monitor_info(memory=False, swap=False, cpu=False, disk=False, ifconfig=False, uptime=False, public=False):
"""
Check System
Keyword argument:
memory -- Check Memory
ifconfig -- Show Ip and MAC Adress
disk -- Check Disk
uptime -- Show Uptime
swap -- Check Swap
public -- Show IP public
cpu -- Check CPU
"""
if memory:
return json.loads(s.getMem())
if swap:
return json.loads(s.getMemSwap())
elif cpu:
return json.loads(s.getLoad())
elif ifconfig:
result = {}
for k, fs in enumerate(json.loads(s.getNetwork())):
interface = fs['interface_name']
if interface != "lo":
ip = get_ip_address(str(interface))
del fs['interface_name']
result[ip] = fs
else:
del fs['interface_name']
result[interface] = fs
return result
elif disk:
result = {}
for k, fs in enumerate(json.loads(s.getFs())):
if fs['fs_type'] != 'tmpfs' and fs['fs_type'] != 'rpc_pipefs':
mnt_point = str(fs['mnt_point'])
del fs['mnt_point']
result[mnt_point] = fs
return result
elif uptime:
uptime_value = (str(datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).split('.')[0])
return { 'Uptime' : uptime_value }
elif public:
try:
ip = str(urlopen('http://ip.yunohost.org').read())
except:
raise YunoHostError(1, _("No connection") )
return { 'Public IP' : ip }
else:
raise YunoHostError(1, _('No arguments provided'))
def monitor_process(enable=None, disable=None, start=None, stop=None, check=False, info=False):
"""
Check Process
Keyword argument:
info -- Process info
disable -- Disable process
enable -- Enable process
start -- Start process
check -- Check process
stop -- Stop process
"""
if enable:
return process_enable(enable)
elif disable:
return process_disable(disable)
elif start:
return process_start(start)
elif stop:
return process_stop(stop)
elif check:
return process_check(check)
elif info:
return json.loads(s.getProcessCount())

186
yunohost_service.py Normal file
View file

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
""" yunohost_service.py
Manage services
"""
import yaml
import glob
import subprocess
import os.path
from yunohost import YunoHostError, win_msg
def service_start(names):
"""
Start one or more services
Keyword argument:
names -- Services name to start
"""
for name in names:
if _run_service_command('start', name):
win_msg(_("'%s' service started") % name)
else:
raise YunoHostError(1, _("Service starting failed for '%s'") % name)
def service_stop(names):
"""
Stop one or more services
Keyword argument:
name -- Services name to stop
"""
for name in names:
if _run_service_command('stop', name):
win_msg(_("'%s' service stopped") % name)
else:
raise YunoHostError(1, _("Service stopping failed for '%s'") % name)
def service_enable(names):
"""
Enable one or more services
Keyword argument:
names -- Services name to enable
"""
for name in names:
if _run_service_command('enable', name):
win_msg(_("'%s' service enabled") % name)
else:
raise YunoHostError(1, _("Service enabling failed for '%s'") % name)
def service_disable(names):
"""
Disable one or more services
Keyword argument:
names -- Services name to disable
"""
for name in names:
if _run_service_command('disable', name):
win_msg(_("'%s' service disabled") % name)
else:
raise YunoHostError(1, _("Service disabling failed for '%s'") % name)
def service_status(names=None):
"""
Show status information about one or more services (all by default)
Keyword argument:
names -- Services name to show
"""
services = _get_services()
check_names = True
result = {}
if names is None or len(names) == 0:
names = services.keys()
check_names = False
for name in names:
if check_names and name not in services.keys():
raise YunoHostError(1, _("Unknown service '%s'") % name)
status = None
if services[name]['status'] == 'service':
status = 'service %s status' % name
else:
status = str(services[name]['status'])
runlevel = 5
if 'runlevel' in services[name].keys():
runlevel = int(services[name]['runlevel'])
result[name] = { 'status': 'unknown', 'loaded': 'unknown' }
# Retrieve service status
try:
ret = subprocess.check_output(status.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
# TODO: log error
result[name]['status'] = _("inactive")
else:
result[name]['status'] = _("running")
# Retrieve service loading
rc_path = glob.glob("/etc/rc%d.d/S[0-9][0-9]%s" % (runlevel, name))
if len(rc_path) == 1 and os.path.islink(rc_path[0]):
result[name]['loaded'] = _("enabled")
elif os.path.isfile("/etc/init.d/%s" % name):
result[name]['loaded'] = _("disabled")
else:
result[name]['loaded'] = _("not-found")
return result
def _run_service_command(action, service):
"""
Run services management command (start, stop, enable, disable)
Keyword argument:
service -- Service name
action -- Action to perform
"""
if service not in _get_services().keys():
raise YunoHostError(1, _("Unknown service '%s'") % service)
cmd = None
if action in ['start', 'stop']:
cmd = 'service %s %s' % (service, action)
elif action in ['enable', 'disable']:
arg = 'defaults' if action == 'enable' else 'remove'
cmd = 'update-rc.d %s %s' % (service, arg)
else:
raise YunoHostError(1, _("Unknown action '%s'") % service)
try:
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
# TODO: log error instead
if os.isatty(1):
err = e.output.rstrip()
print(_("'%s' has returned:\n%s") % (' '.join(e.cmd), err))
return False
return True
def _get_services():
"""
Get a dict of managed services with their parameters
"""
with open('/etc/yunohost/services.yml', 'r') as f:
services = yaml.load(f)
return services