Browse Source

[MIG] auto_backup: Migrate to v9

* Add self.ensure_ones
* Add test coverage
pull/1384/head
Dave Lasley 8 years ago
committed by Aitor Bouzas
parent
commit
5063b5cc81
  1. 20
      auto_backup/README.rst
  2. 19
      auto_backup/__manifest__.py
  3. 28
      auto_backup/data/backup_data.yml
  4. 18
      auto_backup/data/ir_cron.xml
  5. 19
      auto_backup/data/mail_message_subtype.xml
  6. 10
      auto_backup/models/db_backup.py
  7. 2
      auto_backup/tests/__init__.py
  8. 28
      auto_backup/tests/test_auto_backup.py
  9. 232
      auto_backup/tests/test_db_backup.py
  10. 9
      auto_backup/view/db_backup_view.xml

20
auto_backup/README.rst

@ -70,15 +70,24 @@ manually execute the selected processes.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/8.0
:target: https://runbot.odoo-community.org/runbot/149/10.0
Known issues / Roadmap
======================
* On larger databases, it is possible that backups will die due to Odoo server
settings. In order to circumvent this without frivolously changing settings,
you need to run the backup from outside of the main Odoo instance. How to do
this is outlined in `this blog post
<https://blog.laslabs.com/2016/10/running-python-scripts-within-odoos-environment/>`_.
Bug Tracker 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:%20auto_backup%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
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.
Credits Credits
======= =======
@ -89,6 +98,7 @@ Contributors
* Yenthe Van Ginneken <yenthe.vanginneken@vanroey.be> * Yenthe Van Ginneken <yenthe.vanginneken@vanroey.be>
* Alessio Gerace <alessio.gerace@agilebg.com> * Alessio Gerace <alessio.gerace@agilebg.com>
* Jairo Llopis <yajo.sk8@gmail.com> * Jairo Llopis <yajo.sk8@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer Maintainer
---------- ----------

19
auto_backup/__openerp__.py → auto_backup/__manifest__.py

@ -7,19 +7,24 @@
{ {
"name": "Database Auto-Backup", "name": "Database Auto-Backup",
"summary": "Backups database", "summary": "Backups database",
"version": "8.0.1.0.1",
"version": "10.0.1.0.0",
"author": ( "author": (
"VanRoey.be - Yenthe Van Ginneken, Agile Business Group,"
" Grupo ESOC Ingeniería de Servicios,"
" Odoo Community Association (OCA)"
"Yenthe Van Ginneken, "
"Agile Business Group, "
"Grupo ESOC Ingeniería de Servicios, "
"LasLabs, "
"Odoo Community Association (OCA)"
), ),
'license': "AGPL-3", 'license': "AGPL-3",
"website": "http://www.vanroey.be/applications/bedrijfsbeheer/odoo", "website": "http://www.vanroey.be/applications/bedrijfsbeheer/odoo",
"category": "Tools", "category": "Tools",
"depends": ['email_template'],
"demo": [],
"depends": [
'base_setup',
'mail',
],
"data": [ "data": [
"data/backup_data.yml",
"data/ir_cron.xml",
"data/mail_message_subtype.xml",
"security/ir.model.access.csv", "security/ir.model.access.csv",
"view/db_backup_view.xml", "view/db_backup_view.xml",
], ],

28
auto_backup/data/backup_data.yml

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# Cron job
- !record {model: ir.cron, id: ir_cron_backupscheduler0}:
name: Backup scheduler
user_id: base.user_root
interval_number: 1
interval_type: days
numbercall: -1
nextcall: !eval
(datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d 02:00:00")
model: db.backup
function: action_backup_all
# New message subtypes
- !record {model: mail.message.subtype, id: success}:
name: Backup successful
res_model: db.backup
default: False
description: Database backup succeeded.
- !record {model: mail.message.subtype, id: failure}:
name: Backup failed
res_model: db.backup
default: True
description: Database backup failed.

18
auto_backup/data/ir_cron.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo noupdate="1">
<record id="ir_cron_backup_scheduler_0" model="ir.cron">
<field name="name">Backup Scheduler</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="nextcall"
eval="(datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d 02:00:00')"
/>
<field name="model">db.backup</field>
<field name="function">action_backup_all</field>
</record>
</odoo>

19
auto_backup/data/mail_message_subtype.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo noupdate="1">
<record id="mail_message_subtype_success" model="mail.message.subtype">
<field name="name">Backup Successful</field>
<field name="description">Database backup succeeded.</field>
<field name="res_model">db.backup</field>
<field name="default" eval="False" />
</record>
<record id="mail_message_subtype_failure" model="mail.message.subtype">
<field name="name">Backup Failed</field>
<field name="description">Database backup failed.</field>
<field name="res_model">db.backup</field>
<field name="default" eval="True" />
</record>
</odoo>

10
auto_backup/models/db_backup.py

@ -10,13 +10,13 @@ import traceback
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta from datetime import datetime, timedelta
from glob import iglob from glob import iglob
from openerp import exceptions, models, fields, api, _, tools
from openerp.service import db
from odoo import exceptions, models, fields, api, _, tools
from odoo.service import db
import logging import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
try: try:
import pysftp import pysftp
except ImportError:
except ImportError: # pragma: no cover
_logger.debug('Cannot import pysftp') _logger.debug('Cannot import pysftp')
@ -107,8 +107,8 @@ class DbBackup(models.Model):
rec.name = "sftp://%s@%s:%d%s" % ( rec.name = "sftp://%s@%s:%d%s" % (
rec.sftp_user, rec.sftp_host, rec.sftp_port, rec.folder) rec.sftp_user, rec.sftp_host, rec.sftp_port, rec.folder)
@api.constrains("folder", "method")
@api.multi @api.multi
@api.constrains("folder", "method")
def _check_folder(self): def _check_folder(self):
"""Do not use the filestore or you will backup your backups.""" """Do not use the filestore or you will backup your backups."""
for s in self: for s in self:
@ -235,6 +235,7 @@ class DbBackup(models.Model):
@contextmanager @contextmanager
def cleanup_log(self): def cleanup_log(self):
"""Log a possible cleanup failure.""" """Log a possible cleanup failure."""
self.ensure_one()
try: try:
_logger.info("Starting cleanup process after database backup: %s", _logger.info("Starting cleanup process after database backup: %s",
self.name) self.name)
@ -263,6 +264,7 @@ class DbBackup(models.Model):
@api.multi @api.multi
def sftp_connection(self): def sftp_connection(self):
"""Return a new SFTP connection with found parameters.""" """Return a new SFTP connection with found parameters."""
self.ensure_one()
params = { params = {
"host": self.sftp_host, "host": self.sftp_host,
"username": self.sftp_user, "username": self.sftp_user,

2
auto_backup/tests/__init__.py

@ -4,4 +4,4 @@
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis # © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_auto_backup
from . import test_db_backup

28
auto_backup/tests/test_auto_backup.py

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# © 2015 Agile Business Group <http://www.agilebg.com>
# © 2015 Alessio Gerace <alesiso.gerace@agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
from datetime import datetime
from openerp.tests import common
class TestsAutoBackup(common.TransactionCase):
def setUp(self):
super(TestsAutoBackup, self).setUp()
self.abk = self.env["db.backup"].create(
{
'name': u'Têst backup',
}
)
def test_local(self):
"""A local database is backed up."""
filename = self.abk.filename(datetime.now())
self.abk.action_backup()
generated_backup = [f for f in os.listdir(self.abk.folder)
if f >= filename]
self.assertEqual(len(generated_backup), 1)

232
auto_backup/tests/test_db_backup.py

@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
# © 2015 Agile Business Group <http://www.agilebg.com>
# © 2015 Alessio Gerace <alesiso.gerace@agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import mock
from datetime import datetime
from contextlib import contextmanager
from odoo.tests import common
from odoo import exceptions, tools
try:
import pysftp
except ImportError:
pass
model = 'odoo.addons.auto_backup.models.db_backup'
class TestConnectionException(pysftp.ConnectionException):
def __init__(self):
super(TestConnectionException, self).__init__('test', 'test')
class TestDbBackup(common.TransactionCase):
def setUp(self):
super(TestDbBackup, self).setUp()
self.Model = self.env["db.backup"]
@contextmanager
def mock_assets(self):
""" It provides mocked core assets """
self.path_join_val = '/this/is/a/path'
with mock.patch('%s.db' % model) as db:
with mock.patch('%s.os' % model) as os:
with mock.patch('%s.shutil' % model) as shutil:
os.path.join.return_value = self.path_join_val
yield {
'db': db,
'os': os,
'shutil': shutil,
}
@contextmanager
def patch_filtered_sftp(self, record, mocks=None):
""" It patches filtered record and provides a mock """
if mocks is None:
mocks = ['sftp_connection']
mocks = {m: mock.DEFAULT for m in mocks}
with mock.patch.object(record, 'filtered') as filtered:
with mock.patch.object(record, 'backup_log'):
with mock.patch.multiple(record, **mocks):
filtered.side_effect = [], [record]
yield filtered
def new_record(self, method='sftp'):
vals = {
'name': u'Têst backup',
'method': method,
}
if method == 'sftp':
vals.update({
'sftp_host': 'test_host',
'sftp_port': '222',
'sftp_user': 'tuser',
'sftp_password': 'password',
'folder': '/folder/',
})
self.vals = vals
return self.Model.create(vals)
def test_compute_name_sftp(self):
""" It should create proper SFTP URI """
rec_id = self.new_record()
self.assertEqual(
'sftp://%(user)s@%(host)s:%(port)s%(folder)s' % {
'user': self.vals['sftp_user'],
'host': self.vals['sftp_host'],
'port': self.vals['sftp_port'],
'folder': self.vals['folder'],
},
rec_id.name,
)
def test_check_folder(self):
""" It should not allow recursive backups """
rec_id = self.new_record('local')
with self.assertRaises(exceptions.ValidationError):
rec_id.write({
'folder': '%s/another/path' % tools.config.filestore(
self.env.cr.dbname
),
})
@mock.patch('%s._' % model)
def test_action_sftp_test_connection_success(self, _):
""" It should raise connection succeeded warning """
rec_id = self.new_record()
with mock.patch.object(rec_id, 'sftp_connection'):
with self.assertRaises(exceptions.Warning):
rec_id.action_sftp_test_connection()
_.assert_called_once_with("Connection Test Succeeded!")
@mock.patch('%s._' % model)
def test_action_sftp_test_connection_fail(self, _):
""" It should raise connection fail warning """
rec_id = self.new_record()
with mock.patch.object(rec_id, 'sftp_connection') as conn:
conn().__enter__.side_effect = TestConnectionException
with self.assertRaises(exceptions.Warning):
rec_id.action_sftp_test_connection()
_.assert_called_once_with("Connection Test Failed!")
def test_action_backup_local(self):
""" It should backup local database """
rec_id = self.new_record('local')
filename = rec_id.filename(datetime.now())
rec_id.action_backup()
generated_backup = [f for f in os.listdir(rec_id.folder)
if f >= filename]
self.assertEqual(1, len(generated_backup))
def test_action_backup_sftp_mkdirs(self):
""" It should create remote dirs """
rec_id = self.new_record()
with self.mock_assets():
with self.patch_filtered_sftp(rec_id):
conn = rec_id.sftp_connection().__enter__()
rec_id.action_backup()
conn.makedirs.assert_called_once_with(rec_id.folder)
def test_action_backup_sftp_mkdirs_conn_exception(self):
""" It should guard from ConnectionException on remote.mkdirs """
rec_id = self.new_record()
with self.mock_assets():
with self.patch_filtered_sftp(rec_id):
conn = rec_id.sftp_connection().__enter__()
conn.makedirs.side_effect = TestConnectionException
rec_id.action_backup()
# No error was raised, test pass
self.assertTrue(True)
def test_action_backup_sftp_remote_open(self):
""" It should open remote file w/ proper args """
rec_id = self.new_record()
with self.mock_assets() as assets:
with self.patch_filtered_sftp(rec_id):
conn = rec_id.sftp_connection().__enter__()
rec_id.action_backup()
conn.open.assert_called_once_with(
assets['os'].path.join(),
'wb'
)
def test_action_backup_sftp_remote_open(self):
""" It should open remote file w/ proper args """
rec_id = self.new_record()
with self.mock_assets() as assets:
with self.patch_filtered_sftp(rec_id):
conn = rec_id.sftp_connection().__enter__()
rec_id.action_backup()
conn.open.assert_called_once_with(
assets['os'].path.join(),
'wb'
)
def test_action_backup_all_search(self):
""" It should search all records """
rec_id = self.new_record()
with mock.patch.object(rec_id, 'search'):
rec_id.action_backup_all()
rec_id.search.assert_called_once_with([])
def test_action_backup_all_return(self):
""" It should return result of backup operation """
rec_id = self.new_record()
with mock.patch.object(rec_id, 'search'):
res = rec_id.action_backup_all()
self.assertEqual(
rec_id.search().action_backup(), res
)
@mock.patch('%s.pysftp' % model)
def test_sftp_connection_init_passwd(self, pysftp):
""" It should initiate SFTP connection w/ proper args and pass """
rec_id = self.new_record()
rec_id.sftp_connection()
pysftp.Connection.assert_called_once_with(
host=rec_id.sftp_host,
username=rec_id.sftp_user,
port=rec_id.sftp_port,
password=rec_id.sftp_password,
)
@mock.patch('%s.pysftp' % model)
def test_sftp_connection_init_key(self, pysftp):
""" It should initiate SFTP connection w/ proper args and key """
rec_id = self.new_record()
rec_id.write({
'sftp_private_key': 'pkey',
'sftp_password': 'pkeypass',
})
rec_id.sftp_connection()
pysftp.Connection.assert_called_once_with(
host=rec_id.sftp_host,
username=rec_id.sftp_user,
port=rec_id.sftp_port,
private_key=rec_id.sftp_private_key,
private_key_pass=rec_id.sftp_password,
)
@mock.patch('%s.pysftp' % model)
def test_sftp_connection_return(self, pysftp):
""" It should return new sftp connection """
rec_id = self.new_record()
res = rec_id.sftp_connection()
self.assertEqual(
pysftp.Connection(), res,
)
def test_filename(self):
""" It should not error and should return a .dump.zip file str """
now = datetime.now()
res = self.Model.filename(now)
self.assertTrue(res.endswith(".dump.zip"))

9
auto_backup/view/db_backup_view.xml

@ -1,6 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<openerp>
<data>
<odoo>
<record model="ir.ui.view" id="view_backup_conf_form"> <record model="ir.ui.view" id="view_backup_conf_form">
<field name="name">Automated Backups</field> <field name="name">Automated Backups</field>
@ -80,7 +79,7 @@
res_model="db.backup"/> res_model="db.backup"/>
<menuitem <menuitem
parent="base.menu_config"
parent="base_setup.menu_config"
action="action_backup_conf_form" action="action_backup_conf_form"
id="backup_conf_menu"/> id="backup_conf_menu"/>
@ -104,5 +103,5 @@
<field name="model">db.backup</field> <field name="model">db.backup</field>
<field name="key2">client_action_multi</field> <field name="key2">client_action_multi</field>
</record> </record>
</data>
</openerp>
</odoo>
Loading…
Cancel
Save