Holger Brunn
9 years ago
27 changed files with 772 additions and 0 deletions
-
73dead_mans_switch_client/README.rst
-
4dead_mans_switch_client/__init__.py
-
18dead_mans_switch_client/__openerp__.py
-
13dead_mans_switch_client/data/ir_actions.xml
-
13dead_mans_switch_client/data/ir_cron.xml
-
4dead_mans_switch_client/models/__init__.py
-
68dead_mans_switch_client/models/dead_mans_switch_client.py
-
BINdead_mans_switch_client/static/description/icon.png
-
4dead_mans_switch_client/tests/__init__.py
-
17dead_mans_switch_client/tests/test_dead_mans_switch_client.py
-
93dead_mans_switch_server/README.rst
-
5dead_mans_switch_server/__init__.py
-
23dead_mans_switch_server/__openerp__.py
-
4dead_mans_switch_server/controllers/__init__.py
-
28dead_mans_switch_server/controllers/main.py
-
13dead_mans_switch_server/data/ir_cron.xml
-
5dead_mans_switch_server/models/__init__.py
-
141dead_mans_switch_server/models/dead_mans_switch_instance.py
-
17dead_mans_switch_server/models/dead_mans_switch_log.py
-
4dead_mans_switch_server/security/ir.model.access.csv
-
14dead_mans_switch_server/security/res_groups.xml
-
BINdead_mans_switch_server/static/description/icon.png
-
4dead_mans_switch_server/tests/__init__.py
-
36dead_mans_switch_server/tests/test_dead_mans_switch_server.py
-
117dead_mans_switch_server/views/dead_mans_switch_instance.xml
-
38dead_mans_switch_server/views/dead_mans_switch_log.xml
-
16dead_mans_switch_server/views/menu.xml
@ -0,0 +1,73 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
========================== |
||||
|
Dead man's switch (client) |
||||
|
========================== |
||||
|
|
||||
|
This module is the client part of `dead_mans_switch_server`. It is responsible |
||||
|
of sending the server status updates, which in turn takes action if those |
||||
|
updates don't come in time. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
After installing this module, you need to fill in the system parameter |
||||
|
`dead_mans_switch_client.url`. This must be the full URL to the server's |
||||
|
controller, usually of the form https://your.server/dead_mans_switch/alive |
||||
|
|
||||
|
This module attempts to send CPU and RAM statistics to the server. While this |
||||
|
is not mandatory, it's helpful for assessing a server's health. If you want |
||||
|
this, you need to install `psutil`. |
||||
|
|
||||
|
You can also have the currently online users logged, but this only works if |
||||
|
the `im_chat` module is installed. |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
This module doesn't have any visible effect on the client. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
||||
|
|
||||
|
For further information, please visit: |
||||
|
|
||||
|
* https://www.odoo.com/forum/help-1 |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* certificate pinning would be nice |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback |
||||
|
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20dead_mans_switch_client%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Holger Brunn <hbrunn@therp.nl> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import models |
@ -0,0 +1,18 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
{ |
||||
|
"name": "Dead man's switch (client)", |
||||
|
"version": "8.0.1.0.0", |
||||
|
"author": "Therp BV,Odoo Community Association (OCA)", |
||||
|
"license": "AGPL-3", |
||||
|
"category": "Monitoring", |
||||
|
"summary": "Be notified when customers' odoo instances go down", |
||||
|
"depends": [ |
||||
|
'base', |
||||
|
], |
||||
|
"data": [ |
||||
|
"data/ir_actions.xml", |
||||
|
"data/ir_cron.xml", |
||||
|
], |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<act_window id="action_setup" res_model="ir.config_parameter" |
||||
|
name="Configure the dead man's switch server" view_mode="form" |
||||
|
context="{'default_key': 'dead_mans_switch_client.url'}"/> |
||||
|
<record id="todo_setup" model="ir.actions.todo"> |
||||
|
<field name="name">Configure the dead man's switch server</field> |
||||
|
<field name="type">automatic</field> |
||||
|
<field name="action_id" ref="action_setup" /> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data noupdate="1"> |
||||
|
<record id="cron_client" model="ir.cron"> |
||||
|
<field name="name">Dead man's switch client</field> |
||||
|
<field name="interval_number">5</field> |
||||
|
<field name="interval_type">minutes</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="model">dead.mans.switch.client</field> |
||||
|
<field name="function">alive</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import dead_mans_switch_client |
@ -0,0 +1,68 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import json |
||||
|
import logging |
||||
|
import os |
||||
|
try: |
||||
|
import psutil |
||||
|
except ImportError: |
||||
|
psutil = None |
||||
|
import urllib2 |
||||
|
from openerp import api, models |
||||
|
|
||||
|
|
||||
|
class DeadMansSwitchClient(models.AbstractModel): |
||||
|
_name = 'dead.mans.switch.client' |
||||
|
_register = True |
||||
|
|
||||
|
@api.model |
||||
|
def _get_data(self): |
||||
|
ram = 0 |
||||
|
cpu = 0 |
||||
|
if psutil: |
||||
|
process = psutil.Process(os.getpid()) |
||||
|
# psutil changed its api through versions |
||||
|
if process.parent: |
||||
|
if hasattr(process.parent, '__call__'): |
||||
|
process = process.parent() |
||||
|
else: |
||||
|
process = process.parent |
||||
|
if hasattr(process, 'memory_percent'): |
||||
|
ram = process.memory_percent() |
||||
|
if hasattr(process, 'cpu_percent'): |
||||
|
cpu = process.cpu_percent() |
||||
|
user_count = 0 |
||||
|
if 'im_chat.presence' in self.env.registry: |
||||
|
user_count = len(self.env['im_chat.presence'].search([ |
||||
|
('status', '!=', 'offline'), |
||||
|
])) |
||||
|
return { |
||||
|
'database_uuid': self.env['ir.config_parameter'].get_param( |
||||
|
'database.uuid'), |
||||
|
'cpu': cpu, |
||||
|
'ram': ram, |
||||
|
'user_count': user_count, |
||||
|
} |
||||
|
|
||||
|
@api.model |
||||
|
def alive(self): |
||||
|
url = self.env['ir.config_parameter'].get_param( |
||||
|
'dead_mans_switch_client.url') |
||||
|
logger = logging.getLogger(__name__) |
||||
|
if not url: |
||||
|
logger.error('No server configured!') |
||||
|
return |
||||
|
data = self._get_data() |
||||
|
logger.debug('sending %s', data) |
||||
|
urllib2.urlopen( |
||||
|
urllib2.Request( |
||||
|
url, |
||||
|
json.dumps({ |
||||
|
'jsonrpc': '2.0', |
||||
|
'method': 'call', |
||||
|
'params': data, |
||||
|
}), |
||||
|
{ |
||||
|
'Content-Type': 'application/json', |
||||
|
})) |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import test_dead_mans_switch_client |
@ -0,0 +1,17 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from openerp.tests.common import TransactionCase |
||||
|
|
||||
|
|
||||
|
class TestDeadMansSwitchClient(TransactionCase): |
||||
|
def test_dead_mans_switch_client(self): |
||||
|
# test unconfigured case |
||||
|
self.env['ir.config_parameter'].search([ |
||||
|
('key', '=', 'dead_mans_switch_client.url')]).unlink() |
||||
|
self.env['dead.mans.switch.client'].alive() |
||||
|
# test configured case |
||||
|
self.env['ir.config_parameter'].set_param( |
||||
|
'dead_mans_switch_client.url', 'fake_url') |
||||
|
with self.assertRaises(ValueError): |
||||
|
self.env['dead.mans.switch.client'].alive() |
@ -0,0 +1,93 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
========================== |
||||
|
Dead man's switch (server) |
||||
|
========================== |
||||
|
|
||||
|
This module receives status messages by `dead_mans_switch_client` and notifies |
||||
|
you if a client instance didn't check back in time. |
||||
|
|
||||
|
As a side effect, you'll also get some statistical data from your client |
||||
|
instances. |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
Install `dead_mans_switch_client` on a customer instance and configure it as |
||||
|
described in that module's documentation. The clients will register themselves |
||||
|
with the server automatically. They will show up with their database uuid, |
||||
|
you'll have to assign a human readable description yourself. |
||||
|
|
||||
|
At this point, you can assign a customer to this client instance for reporting |
||||
|
purposes, and, more important, add followers to the instance. They will be |
||||
|
notified in case the instance doesn't check back in time. Notification are only |
||||
|
turned on for instances in state 'active', instances in states 'new' or |
||||
|
'suspended' will be ignored. |
||||
|
|
||||
|
You'll find the instances' current state at Reporting/Customer instances. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
||||
|
|
||||
|
For further information, please visit: |
||||
|
|
||||
|
* https://www.odoo.com/forum/help-1 |
||||
|
|
||||
|
Security considerations |
||||
|
======================= |
||||
|
|
||||
|
As the controller receiving status updates is unauthenticated, any internet user |
||||
|
can have your server create monitoring instance records. While this is annoying, |
||||
|
it's quite harmless and basically the same as misuse of the fetchmail module. |
||||
|
|
||||
|
For a more substantial annoyance, the attacker would have to guess one of your |
||||
|
client's database uuids, so they functionally are your passwords. |
||||
|
|
||||
|
To be sure, consider blocking this controller from unknown origins in your SSL |
||||
|
proxy. In nginx, it would look like this:: |
||||
|
|
||||
|
location /dead_mans_switch/alive { |
||||
|
allow 192.168.1.0/24; |
||||
|
# add other client's IPs |
||||
|
deny all; |
||||
|
} |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* matching is done via the database's uuid, so take care to change this if you |
||||
|
clone a database |
||||
|
* logging some postgres stats and disk usage would be nice too |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback |
||||
|
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20dead_mans_switch_server%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Holger Brunn <hbrunn@therp.nl> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import models |
||||
|
from . import controllers |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
{ |
||||
|
"name": "Dead man's switch (server)", |
||||
|
"version": "8.0.1.0.0", |
||||
|
"author": "Therp BV,Odoo Community Association (OCA)", |
||||
|
"license": "AGPL-3", |
||||
|
"category": "Monitoring", |
||||
|
"summary": "Be notified when customers' odoo instances go down", |
||||
|
"depends": [ |
||||
|
'mail', |
||||
|
'web_kanban_sparkline', |
||||
|
], |
||||
|
"data": [ |
||||
|
"data/ir_cron.xml", |
||||
|
"security/res_groups.xml", |
||||
|
"views/dead_mans_switch_log.xml", |
||||
|
"views/dead_mans_switch_instance.xml", |
||||
|
"views/menu.xml", |
||||
|
'security/ir.model.access.csv', |
||||
|
], |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import main |
@ -0,0 +1,28 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import werkzeug |
||||
|
from openerp import http |
||||
|
from openerp.http import request |
||||
|
|
||||
|
|
||||
|
class Main(http.Controller): |
||||
|
@http.route('/dead_mans_switch/alive', type='json', auth="none") |
||||
|
def alive(self, **kwargs): |
||||
|
if 'database_uuid' not in kwargs: |
||||
|
raise werkzeug.exceptions.NotFound() |
||||
|
instance = request.env['dead.mans.switch.instance'].sudo().search([ |
||||
|
('database_uuid', '=', kwargs['database_uuid']), |
||||
|
]) |
||||
|
if not instance: |
||||
|
instance = request.env['dead.mans.switch.instance'].sudo().create({ |
||||
|
'database_uuid': kwargs['database_uuid'], |
||||
|
}) |
||||
|
data = { |
||||
|
field: value |
||||
|
for field, value in kwargs.iteritems() |
||||
|
if field in request.env['dead.mans.switch.log']._fields |
||||
|
} |
||||
|
instance.write({ |
||||
|
'log_ids': [(0, 0, data)], |
||||
|
}) |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data noupdate="1"> |
||||
|
<record id="cron_server" model="ir.cron"> |
||||
|
<field name="name">Dead man's switch server</field> |
||||
|
<field name="interval_number">1</field> |
||||
|
<field name="interval_type">minutes</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="model">dead.mans.switch.instance</field> |
||||
|
<field name="function">check_alive</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import dead_mans_switch_instance |
||||
|
from . import dead_mans_switch_log |
@ -0,0 +1,141 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import json |
||||
|
from datetime import datetime, timedelta |
||||
|
from openerp import _, api, fields, models |
||||
|
|
||||
|
|
||||
|
class DeadMansSwitchInstance(models.Model): |
||||
|
_inherit = ['mail.thread'] |
||||
|
_name = 'dead.mans.switch.instance' |
||||
|
_description = 'Instance to monitor' |
||||
|
_order = 'state, partner_id' |
||||
|
_rec_name = 'partner_id' |
||||
|
|
||||
|
state = fields.Selection( |
||||
|
[('new', 'New'), ('active', 'Active'), ('suspended', 'Suspended')], |
||||
|
'State', default='new') |
||||
|
partner_id = fields.Many2one( |
||||
|
'res.partner', 'Customer', |
||||
|
domain=[('is_company', '=', True), ('customer', '=', True)]) |
||||
|
database_uuid = fields.Char('Database id', required=True, readonly=True) |
||||
|
user_id = fields.Many2one('res.users', 'Responsible user', |
||||
|
track_visibility='onchange') |
||||
|
description = fields.Char('Description') |
||||
|
log_ids = fields.One2many( |
||||
|
'dead.mans.switch.log', 'instance_id', string='Log lines') |
||||
|
alive = fields.Boolean('Alive', compute='_compute_alive') |
||||
|
alive_max_delay = fields.Integer( |
||||
|
'Alive delay', help='The amount of seconds without notice after which ' |
||||
|
'the instance is considered dead', default=600) |
||||
|
last_seen = fields.Datetime('Last seen', compute='_compute_last_log') |
||||
|
last_cpu = fields.Float('CPU', compute='_compute_last_log') |
||||
|
last_cpu_sparkline = fields.Text('CPU', compute='_compute_last_log') |
||||
|
last_ram = fields.Float('RAM', compute='_compute_last_log') |
||||
|
last_ram_sparkline = fields.Text('RAM', compute='_compute_last_log') |
||||
|
last_user_count = fields.Integer( |
||||
|
'Active users', compute='_compute_last_log') |
||||
|
last_user_count_sparkline = fields.Text( |
||||
|
'Active users', compute='_compute_last_log') |
||||
|
|
||||
|
_sql_constraints = [ |
||||
|
('uuid_unique', 'unique(database_uuid)', 'Database ID must be unique'), |
||||
|
] |
||||
|
|
||||
|
@api.multi |
||||
|
def name_get(self): |
||||
|
return [ |
||||
|
( |
||||
|
this.id, |
||||
|
'%s%s' % ( |
||||
|
this.partner_id.name or this.database_uuid, |
||||
|
' (%s)' % (this.description) if this.description else '', |
||||
|
) |
||||
|
) |
||||
|
for this in self |
||||
|
] |
||||
|
|
||||
|
@api.onchange('partner_id') |
||||
|
def _onchange_partner_id(self): |
||||
|
if not self.user_id: |
||||
|
self.user_id = self.partner_id.user_id |
||||
|
|
||||
|
@api.multi |
||||
|
def button_active(self): |
||||
|
self.write({'state': 'active'}) |
||||
|
|
||||
|
@api.multi |
||||
|
def button_suspended(self): |
||||
|
self.write({'state': 'suspended'}) |
||||
|
|
||||
|
@api.multi |
||||
|
def button_logs(self): |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'res_model': 'dead.mans.switch.log', |
||||
|
'domain': [('instance_id', 'in', self.ids)], |
||||
|
'name': _('Logs'), |
||||
|
'view_mode': 'graph,tree,form', |
||||
|
'context': { |
||||
|
'search_default_this_month': 1, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def _compute_alive(self): |
||||
|
for this in self: |
||||
|
if this.state in ['new', 'suspended']: |
||||
|
this.alive = False |
||||
|
continue |
||||
|
this.alive = bool( |
||||
|
self.env['dead.mans.switch.log'].search( |
||||
|
[ |
||||
|
('instance_id', '=', this.id), |
||||
|
('create_date', '>=', fields.Datetime.to_string( |
||||
|
datetime.utcnow() - |
||||
|
timedelta(seconds=this.alive_max_delay))), |
||||
|
], |
||||
|
limit=1)) |
||||
|
|
||||
|
@api.multi |
||||
|
def _compute_last_log(self): |
||||
|
for this in self: |
||||
|
last_log = self.env['dead.mans.switch.log'].search( |
||||
|
[('instance_id', '=', this.id)], limit=12) |
||||
|
field_mapping = { |
||||
|
'last_seen': 'create_date', |
||||
|
'last_cpu': 'cpu', |
||||
|
'last_ram': 'ram', |
||||
|
'last_user_count': 'user_count', |
||||
|
} |
||||
|
for field, mapped_field in field_mapping.iteritems(): |
||||
|
this[field] = last_log[:1][mapped_field] |
||||
|
sparkline_fields = ['last_cpu', 'last_ram', 'last_user_count'] |
||||
|
for field in sparkline_fields: |
||||
|
this['%s_sparkline' % field] = json.dumps( |
||||
|
list(reversed(last_log.mapped(lambda log: { |
||||
|
'value': log[field_mapping[field]], |
||||
|
'tooltip': log.create_date, |
||||
|
})))) |
||||
|
|
||||
|
@api.model |
||||
|
def check_alive(self): |
||||
|
"""handle cronjob""" |
||||
|
for this in self.search([('state', '=', 'active')]): |
||||
|
if this.alive: |
||||
|
continue |
||||
|
last_post = fields.Datetime.from_string(this.message_last_post) |
||||
|
if not last_post or datetime.utcnow() - timedelta( |
||||
|
seconds=this.alive_max_delay * 2) > last_post: |
||||
|
this.panic() |
||||
|
|
||||
|
@api.multi |
||||
|
def panic(self): |
||||
|
"""override for custom handling""" |
||||
|
self.ensure_one() |
||||
|
self.message_post( |
||||
|
type='comment', subtype='mt_comment', |
||||
|
subject=_('Dead man\'s switch warning: %s') % |
||||
|
self.display_name, content_subtype='plaintext', |
||||
|
body=_('%s seems to be dead') % self.display_name) |
@ -0,0 +1,17 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from openerp import fields, models |
||||
|
|
||||
|
|
||||
|
class DeadMansSwitchLog(models.Model): |
||||
|
_name = 'dead.mans.switch.log' |
||||
|
_description = 'Instance log line' |
||||
|
_order = 'create_date desc' |
||||
|
_rec_name = 'create_date' |
||||
|
|
||||
|
instance_id = fields.Many2one( |
||||
|
'dead.mans.switch.instance', 'Instance', index=True) |
||||
|
cpu = fields.Float('CPU', group_operator='avg') |
||||
|
ram = fields.Float('RAM', group_operator='avg') |
||||
|
user_count = fields.Integer('Users logged in', group_operator='avg') |
@ -0,0 +1,4 @@ |
|||||
|
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
||||
|
access_dead_mans_switch_log,access_dead_mans_switch_log,model_dead_mans_switch_log,group_user,1,0,0,0 |
||||
|
access_dead_mans_switch_instance,access_dead_mans_switch_instance,model_dead_mans_switch_instance,group_user,1,0,0,0 |
||||
|
access_dead_mans_switch_instance_manager,access_dead_mans_switch_instance,model_dead_mans_switch_instance,group_manager,1,1,0,1 |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<record id="group_user" model="res.groups"> |
||||
|
<field name="name">User</field> |
||||
|
<field name="category_id" ref="base.module_category_monitoring" /> |
||||
|
</record> |
||||
|
<record id="group_manager" model="res.groups"> |
||||
|
<field name="name">Manager</field> |
||||
|
<field name="implied_ids" eval="[(4, ref('group_user'))]" /> |
||||
|
<field name="category_id" ref="base.module_category_monitoring" /> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from . import test_dead_mans_switch_server |
@ -0,0 +1,36 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2015 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from openerp.tests.common import TransactionCase |
||||
|
from openerp import http |
||||
|
from ..controllers.main import Main |
||||
|
|
||||
|
|
||||
|
class TestDeadMansSwitchServer(TransactionCase): |
||||
|
def test_dead_mans_switch_server(self): |
||||
|
if 'dead.mans.switch.client' not in self.env.registry: |
||||
|
return |
||||
|
data = self.env['dead.mans.switch.client']._get_data() |
||||
|
http._request_stack.push(self) |
||||
|
Main().alive(**data) |
||||
|
instance = self.env['dead.mans.switch.instance'].search([ |
||||
|
('database_uuid', '=', data['database_uuid']), |
||||
|
]) |
||||
|
self.assertTrue(instance) |
||||
|
self.assertTrue(instance.last_seen) |
||||
|
self.assertEqual(instance.display_name, data['database_uuid']) |
||||
|
main_partner = self.env.ref('base.main_partner') |
||||
|
instance.partner_id = main_partner |
||||
|
self.assertEqual(instance.display_name, main_partner.name) |
||||
|
instance._onchange_partner_id() |
||||
|
self.assertEqual(instance.user_id, main_partner.user_id) |
||||
|
instance.button_suspended() |
||||
|
self.assertEqual(instance.state, 'suspended') |
||||
|
instance.button_active() |
||||
|
self.assertTrue(instance.alive) |
||||
|
message_count = len(instance.message_ids) |
||||
|
instance.check_alive() |
||||
|
self.assertEqual(len(instance.message_ids), message_count) |
||||
|
instance.log_ids.unlink() |
||||
|
instance.check_alive() |
||||
|
self.assertEqual(len(instance.message_ids), message_count + 1) |
@ -0,0 +1,117 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<record id="search_dead_mans_switch_instance" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.instance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search> |
||||
|
<field name="partner_id" /> |
||||
|
<field name="database_uuid" /> |
||||
|
<field name="description" /> |
||||
|
<group string="State"> |
||||
|
<filter name="active" string="Active" domain="[('state', '=', 'active')]" /> |
||||
|
<filter name="new" string="New" domain="[('state', '=', 'new')]" /> |
||||
|
<filter name="suspended" string="Suspended" domain="[('state', '=', 'suspended')]" /> |
||||
|
</group> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
<record id="form_dead_mans_switch_instance" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.instance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<header> |
||||
|
<button type="object" name="button_active" |
||||
|
string="Activate" states="new,suspended" |
||||
|
class="oe_highlight" /> |
||||
|
<button type="object" name="button_suspended" |
||||
|
string="Suspend" states="active" /> |
||||
|
<field name="state" widget="statusbar" /> |
||||
|
</header> |
||||
|
<sheet> |
||||
|
<div class="oe_title"> |
||||
|
<h1 class="oe_read_only"><field name="display_name" /></h1> |
||||
|
</div> |
||||
|
<div class="oe_button_box oe_right"> |
||||
|
<button type="object" name="button_logs" |
||||
|
string="Open logs" class="oe_stat_button" |
||||
|
icon="fa-tasks" /> |
||||
|
</div> |
||||
|
<group> |
||||
|
<group name="description"> |
||||
|
<field name="partner_id" /> |
||||
|
<field name="description" /> |
||||
|
</group> |
||||
|
<group name="data"> |
||||
|
<field name="database_uuid" /> |
||||
|
<field name="user_id" /> |
||||
|
</group> |
||||
|
</group> |
||||
|
<div class="oe_chatter"> |
||||
|
<field name="message_follower_ids" widget="mail_followers"/> |
||||
|
<field name="message_ids" widget="mail_thread"/> |
||||
|
</div> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
<record id="tree_dead_mans_switch_instance" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.instance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree> |
||||
|
<field name="display_name" /> |
||||
|
<field name="alive" /> |
||||
|
<field name="user_id" /> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
<record id="kanban_dead_mans_switch_instance" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.instance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<kanban> |
||||
|
<field name="partner_id" /> |
||||
|
<field name="alive" /> |
||||
|
<field name="state" /> |
||||
|
<templates> |
||||
|
<div t-name="kanban-box" t-attf-class="oe_kanban_card #{widget.kanban_color(record.partner_id.raw_value[0])}"> |
||||
|
<h4> |
||||
|
<a type="open"><field name="display_name" /></a> |
||||
|
<t t-if="record.alive.raw_value and record.state.raw_value == 'active'"> |
||||
|
<i class="fa fa-circle" style="color: green" /> |
||||
|
</t> |
||||
|
<t t-if="!record.alive.raw_value and record.state.raw_value == 'active'"> |
||||
|
<i class="fa fa-circle" style="color: red" /> |
||||
|
</t> |
||||
|
<t t-if="record.state.raw_value != 'active'"> |
||||
|
<i class="fa fa-circle" style="color: gray" /> |
||||
|
</t> |
||||
|
</h4> |
||||
|
<table class="table"> |
||||
|
<tr> |
||||
|
<th>CPU</th> |
||||
|
<td><field name="last_cpu" />%</td> |
||||
|
<td><field name="last_cpu_sparkline" widget="sparkline_bar" /></td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<th>RAM</th> |
||||
|
<td><field name="last_ram" />%</td> |
||||
|
<td><field name="last_ram_sparkline" widget="sparkline_bar" /></td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<th>Users</th> |
||||
|
<td><field name="last_user_count" /></td> |
||||
|
<td><field name="last_user_count_sparkline" widget="sparkline_bar" /></td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<th>Last seen</th> |
||||
|
<td><field name="last_seen" /></td> |
||||
|
<td /> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</templates> |
||||
|
</kanban> |
||||
|
</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,38 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<record id="tree_dead_mans_switch_log" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.log</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree> |
||||
|
<field name="create_date" /> |
||||
|
<field name="cpu" /> |
||||
|
<field name="ram" /> |
||||
|
<field name="user_count" /> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
<record id="graph_dead_mans_switch_log" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.log</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<graph type="pivot"> |
||||
|
<field name="create_date" type="row" interval="day" /> |
||||
|
<field name="cpu" type="measure" /> |
||||
|
<field name="ram" type="measure" /> |
||||
|
<field name="user_count" type="measure" /> |
||||
|
</graph> |
||||
|
</field> |
||||
|
</record> |
||||
|
<record id="search_dead_mans_switch_log" model="ir.ui.view"> |
||||
|
<field name="model">dead.mans.switch.log</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search> |
||||
|
<filter name="this_month" string="This month" |
||||
|
domain="[('create_date', '>=', (datetime.date.today() - relativedelta(days=31)).strftime('%Y-%m-%d'))]" /> |
||||
|
<filter name="this_week" string="This week" |
||||
|
domain="[('create_date', '>=', (datetime.date.today() - relativedelta(days=6)).strftime('%Y-%m-%d'))]" /> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<act_window id="action_dead_mans_switch_instance" |
||||
|
name="Customer instances" |
||||
|
res_model="dead.mans.switch.instance" |
||||
|
context="{'search_default_active': 1}" |
||||
|
view_mode="kanban,tree,form" /> |
||||
|
<menuitem id="menu_dead_mans_switch" |
||||
|
name="Customer instances" |
||||
|
parent="base.menu_reporting" /> |
||||
|
<menuitem id="menu_dead_mans_switch_instance" |
||||
|
action="action_dead_mans_switch_instance" |
||||
|
parent="menu_dead_mans_switch" /> |
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue