This commit is contained in:
Kload 2013-12-05 12:37:37 +00:00
commit c1981ce067
4 changed files with 561 additions and 183 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

@ -425,68 +425,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

@ -23,154 +23,198 @@
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(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
def bytes2human(n):
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
prefix[s] = 1 << (i+1)*10
for s in reversed(symbols):
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 Monitor disk space and usage
Keyword argument: Keyword argument:
memory -- Check Memory units -- Unit(s) to monitor
ifconfig -- Show Ip and MAC Adress mountpoint -- Device mountpoint
disk -- Check Disk human_readable -- Print sizes in human readable format
uptime -- Show Uptime
swap -- Check Swap
public -- Show IP public
cpu -- Check CPU
""" """
if memory: glances = _get_glances_api()
return json.loads(s.getMem()) result_dname = None
result = {}
if swap: if units is None:
return json.loads(s.getMemSwap()) units = ['io', 'filesystem']
elif cpu: # Get mounted block devices
return json.loads(s.getLoad()) 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:
raise YunoHostError(1, _("Unknown mountpoint '%s'") % mountpoint)
elif ifconfig: # Retrieve monitoring for unit(s)
result = {} for u in units:
for k, fs in enumerate(json.loads(s.getNetwork())): if u == 'io':
interface = fs['interface_name'] for d in json.loads(glances.getDiskIO()):
if interface != "lo": dname = d['disk_name']
ip = get_ip_address(str(interface)) if dname in devices.keys():
del fs['interface_name'] del d['disk_name']
result[ip] = fs if len(units) > 1:
else: result[dname][u] = d
del fs['interface_name'] else:
result[interface] = fs d['mnt_point'] = devices[dname]
return result result[dname] = d
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)
elif disk: if result_dname is not None:
result = {} return result[result_dname]
for k, fs in enumerate(json.loads(s.getFs())): return result
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: def monitor_network(units=None, human_readable=False):
try: """
ip = str(urlopen('http://ip.yunohost.org').read()) Monitor network interfaces
except:
raise YunoHostError(1, _("No connection") ) Keyword argument:
return { 'Public IP' : ip } 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)
result[u] = {
'public_ip': p_ip,
'local_ip': l_ip,
'gateway': 'TODO'
}
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
else:
raise YunoHostError(1, _('No arguments provided'))
def monitor_process(enable=None, disable=None, start=None, stop=None, check=False, info=False): def monitor_process(enable=None, disable=None, start=None, stop=None, check=False, info=False):
""" """
@ -197,3 +241,63 @@ def monitor_process(enable=None, disable=None, start=None, stop=None, check=Fals
return process_check(check) return process_check(check)
elif info: elif info:
return json.loads(s.getProcessCount()) return json.loads(s.getProcessCount())
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):
"""
Extract IP address (v4 or v6) from a string
Keyword argument:
string -- String to search in
"""
# TODO: Return IPv4 and IPv6?
ip4_prog = re.compile('((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}/[0-9]{1,2})')
ip6_prog = re.compile('((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/[0-9]{1,2})')
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):
prefix[s] = 1 << (i+1)*10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
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('process.yml', 'r') as f:
services = yaml.load(f)
return services