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
yunohost monitor info [-h] [-u] [-d] [-p] [-c] [-m] [-i]
yunohost monitor process [-h] [-e PROCESS] [-d PROCESS]
[--stop PROCESS] [-c PORT] [-i]
[--start PROCESS]
yunohost monitor disk [-h] [-m MOUNTPOINT] [-t] [-f] [-H]
yunohost monitor network [-h] [-u] [-i] [-H]
yunohost monitor system [-h] [-m] [-u] [-i] [-p] [-c] [-H]
### 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

View file

@ -334,6 +334,31 @@ app:
-v:
full: --value
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()
checkport:
@ -421,68 +446,148 @@ backup:
# Monitor #
#############################
monitor:
category_help: Monitoring functions
category_help: Monitor the server
actions:
### monitor_info()
info:
action_help: Check System
### monitor_disk()
disk:
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:
-m:
full: --memory
help: Check Memory
action: store_true
-s:
full: --swap
help: Check Swap
action: store_true
help: Show memory usage
action: append_const
const: memory
dest: units
-c:
full: --cpu
help: Check CPU
action: store_true
-d:
full: --disk
help: Check Disk
action: store_true
-i:
full: --ifconfig
help: Show Ip and MAC Adress
action: store_true
help: Show CPU usage and load
action: append_const
const: cpu
dest: units
-p:
full: --process
help: Show processes summary
action: append_const
const: process
dest: units
-u:
full: --uptime
help: Show Uptime
action: store_true
-p:
full: --public
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
help: Show the system uptime
action: append_const
const: uptime
dest: units
-i:
full: --info
help: Process info
full: --infos
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
#############################
# 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 #
#############################

View file

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

View file

@ -550,7 +550,7 @@ def app_removeaccess(apps, users):
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
@ -558,6 +558,7 @@ def app_setting(app, key, value=None):
value -- Value to set
app -- App ID
key -- Key to get/set
delete -- Delete the key
"""
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
app_settings = {}
if value is None:
if value is None and not delete:
# Get the value
if app_settings is not None and key in app_settings:
print(app_settings[key])
@ -577,7 +578,7 @@ def app_setting(app, key, value=None):
# Set the value
if app_settings is None:
app_settings = {}
if value == '' and key in app_settings:
if delete and key in app_settings:
del app_settings[key]
else:
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)
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):
"""
Check availability of a local port

View file

@ -23,38 +23,266 @@
Monitoring functions
"""
import xmlrpclib
import re
import json
import psutil
import subprocess
import xmlrpclib
from urllib import urlopen
from datetime import datetime, timedelta
from yunohost import YunoHostError, win_msg, colorize, validate, get_required_args
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
from yunohost import YunoHostError
s = xmlrpclib.ServerProxy('http://127.0.0.1:61209')
glances_uri = 'http://127.0.0.1:61209'
def get_ip_address(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
def monitor_disk(units=None, mountpoint=None, human_readable=False):
"""
Monitor disk space and usage
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')
prefix = {}
for i, s in enumerate(symbols):
@ -63,137 +291,4 @@ def bytes2human(n):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%sB" % 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())
return "%s" % n

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