You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
17 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. # OpenERP, Open Source Management Solution
  4. # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
  5. # $Id$
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. import xmlrpclib
  22. import socket
  23. import os
  24. import time
  25. import datetime
  26. import base64
  27. import re
  28. try:
  29. import pysftp
  30. except ImportError:
  31. raise ImportError(
  32. 'This module needs pysftp to automaticly write backups to the FTP '
  33. 'through SFTP.Please install pysftp on your system.'
  34. '(sudo pip install pysftp)'
  35. )
  36. from openerp.osv import fields, osv
  37. from openerp import tools
  38. from openerp import netsvc, _
  39. import logging
  40. _logger = logging.getLogger(__name__)
  41. def execute(connector, method, *args):
  42. res = False
  43. try:
  44. res = getattr(connector, method)(*args)
  45. except socket.error as e:
  46. raise e
  47. return res
  48. addons_path = tools.config['addons_path'] + '/auto_backup/DBbackups'
  49. class db_backup(osv.Model):
  50. _name = 'db.backup'
  51. def get_db_list(self, cr, user, ids, host, port, context={}):
  52. print("Host: " + host)
  53. print("Port: " + port)
  54. uri = 'http://' + host + ':' + port
  55. conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/db')
  56. db_list = execute(conn, 'list')
  57. return db_list
  58. def _get_db_name(self, cr, uid, vals, context=None):
  59. # attach_pool = self.pool.get("ir.logging")
  60. dbName = cr.dbname
  61. return dbName
  62. _columns = {
  63. # Columns local server
  64. 'host': fields.char('Host', size=100, required='True'),
  65. 'port': fields.char('Port', size=10, required='True'),
  66. 'name': fields.char(
  67. 'Database', size=100, required='True',
  68. help='Database you want to schedule backups for'
  69. ),
  70. 'bkp_dir': fields.char(
  71. 'Backup Directory', size=100,
  72. help='Absolute path for storing the backups',
  73. required='True'
  74. ),
  75. 'autoremove': fields.boolean(
  76. 'Auto. Remove Backups',
  77. help=(
  78. "If you check this option you can choose to "
  79. "automaticly remove the backup after xx days"
  80. )
  81. ),
  82. 'daystokeep': fields.integer(
  83. 'Remove after x days',
  84. help=(
  85. "Choose after how many days the backup should be "
  86. "deleted. For example:\nIf you fill in 5 the backups "
  87. "will be removed after 5 days."
  88. ), required=True
  89. ),
  90. # Columns for external server (SFTP)
  91. 'sftpwrite': fields.boolean(
  92. 'Write to external server with sftp',
  93. help=(
  94. "If you check this option you can specify the details "
  95. "needed to write to a remote server with SFTP."
  96. )
  97. ),
  98. 'sftppath': fields.char(
  99. 'Path external server',
  100. help=(
  101. "The location to the folder where the dumps should be "
  102. "written to. For example /odoo/backups/.\nFiles will then"
  103. " be written to /odoo/backups/ on your remote server."
  104. )
  105. ),
  106. 'sftpip': fields.char(
  107. 'IP Address SFTP Server',
  108. help=(
  109. "The IP address from your remote"
  110. " server. For example 192.168.0.1"
  111. )
  112. ),
  113. 'sftpport': fields.integer(
  114. "SFTP Port",
  115. help="The port on the FTP server that accepts SSH/SFTP calls."
  116. ),
  117. 'sftpusername': fields.char(
  118. 'Username SFTP Server',
  119. help=(
  120. "The username where the SFTP connection "
  121. "should be made with. This is the user on the external server."
  122. )
  123. ),
  124. 'sftppassword': fields.char(
  125. 'Password User SFTP Server',
  126. help=(
  127. "The password from the user where the SFTP connection "
  128. "should be made with. This is the password from the user"
  129. " on the external server."
  130. )
  131. ),
  132. 'daystokeepsftp': fields.integer(
  133. 'Remove SFTP after x days',
  134. help=(
  135. "Choose after how many days the backup should be deleted "
  136. "from the FTP server. For example:\nIf you fill in 5 the "
  137. "backups will be removed after 5 days from the FTP server."
  138. )
  139. ),
  140. 'sendmailsftpfail': fields.boolean(
  141. 'Auto. E-mail on backup fail', help=(
  142. "If you check this option you can choose to automaticly"
  143. " get e-mailed when the backup to the external server failed."
  144. )
  145. ),
  146. 'emailtonotify': fields.char(
  147. 'E-mail to notify',
  148. help=(
  149. "Fill in the e-mail where you want to be"
  150. " notified that the backup failed on the FTP."
  151. )
  152. ),
  153. }
  154. _defaults = {
  155. # 'bkp_dir' : lambda *a : addons_path,
  156. 'bkp_dir': '/odoo/backups',
  157. 'host': 'localhost',
  158. 'port': '8069',
  159. 'name': _get_db_name,
  160. 'daystokeepsftp': 30,
  161. 'sftpport': 22,
  162. }
  163. def _check_db_exist(self, cr, user, ids):
  164. for rec in self.browse(cr, user, ids):
  165. db_list = self.get_db_list(cr, user, ids, rec.host, rec.port)
  166. if rec.name in db_list:
  167. return True
  168. return False
  169. _constraints = [
  170. (_check_db_exist, _('Error ! No such database exists!'), [])
  171. ]
  172. def test_sftp_connection(self, cr, uid, ids, context=None):
  173. conf_ids = self.search(cr, uid, [])
  174. confs = self.browse(cr, uid, conf_ids)
  175. # Check if there is a success or fail and write messages
  176. messageTitle = ""
  177. messageContent = ""
  178. for rec in confs:
  179. # db_list = self.get_db_list(cr, uid, [], rec.host, rec.port)
  180. try:
  181. # pathToWriteTo = rec.sftppath
  182. ipHost = rec.sftpip
  183. portHost = rec.sftpport
  184. usernameLogin = rec.sftpusername
  185. passwordLogin = rec.sftppassword
  186. # Connect with external server over SFTP, so we know sure that
  187. # everything works.
  188. srv = pysftp.Connection(host=ipHost, username=usernameLogin,
  189. password=passwordLogin, port=portHost)
  190. srv.close()
  191. # We have a success.
  192. messageTitle = _("Connection Test Succeeded!")
  193. messageContent = _(
  194. "Everything seems properly set up for FTP back-ups!")
  195. except Exception as e:
  196. messageTitle = _("Connection Test Failed!")
  197. if len(rec.sftpip) < 8:
  198. messageContent += _(
  199. "\nYour IP address seems to be too short.\n")
  200. messageContent += "Here is what we got instead:\n"
  201. if "Failed" in messageTitle:
  202. raise osv.except_osv(
  203. _(messageTitle), _(
  204. messageContent + "%s") %
  205. tools.ustr(e))
  206. else:
  207. raise osv.except_osv(_(messageTitle), _(messageContent))
  208. def schedule_backup(self, cr, user, context={}):
  209. conf_ids = self.search(cr, user, [])
  210. confs = self.browse(cr, user, conf_ids)
  211. for rec in confs:
  212. db_list = self.get_db_list(cr, user, [], rec.host, rec.port)
  213. if rec.name in db_list:
  214. try:
  215. if not os.path.isdir(rec.bkp_dir):
  216. os.makedirs(rec.bkp_dir)
  217. except:
  218. raise
  219. # Create name for dumpfile.
  220. bkp_file = '%s_%s.dump' % (
  221. time.strftime('%d_%m_%Y_%H_%M_%S'),
  222. rec.name)
  223. file_path = os.path.join(rec.bkp_dir, bkp_file)
  224. uri = 'http://' + rec.host + ':' + rec.port
  225. conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/db')
  226. bkp = ''
  227. try:
  228. bkp = execute(
  229. conn,
  230. 'dump',
  231. tools.config['admin_passwd'],
  232. rec.name)
  233. except:
  234. _logger.notifyChannel(
  235. 'backup', netsvc.LOG_INFO,
  236. _(
  237. "Couldn't backup database %s. "
  238. "Bad database administrator"
  239. "password for server running at http://%s:%s"
  240. ) % (rec.name, rec.host, rec.port))
  241. continue
  242. bkp = base64.decodestring(bkp)
  243. fp = open(file_path, 'wb')
  244. fp.write(bkp)
  245. fp.close()
  246. else:
  247. _logger.notifyChannel(
  248. 'backup', netsvc.LOG_INFO,
  249. "database %s doesn't exist on http://%s:%s" %
  250. (rec.name, rec.host, rec.port))
  251. # Check if user wants to write to SFTP or not.
  252. if rec.sftpwrite is True:
  253. try:
  254. # Store all values in variables
  255. dir = rec.bkp_dir
  256. pathToWriteTo = rec.sftppath
  257. ipHost = rec.sftpip
  258. portHost = rec.sftpport
  259. usernameLogin = rec.sftpusername
  260. passwordLogin = rec.sftppassword
  261. # Connect with external server over SFTP
  262. srv = pysftp.Connection(
  263. host=ipHost,
  264. username=usernameLogin,
  265. password=passwordLogin,
  266. port=portHost)
  267. # Move to the correct directory on external server. If the
  268. # user made a typo in his path with multiple slashes
  269. # (/odoo//backups/) it will be fixed by this regex.
  270. pathToWriteTo = re.sub('([/]{2,5})+', '/', pathToWriteTo)
  271. print(pathToWriteTo)
  272. try:
  273. srv.chdir(pathToWriteTo)
  274. except IOError:
  275. # Create directory and subdirs if they do not exist.
  276. currentDir = ''
  277. for dirElement in pathToWriteTo.split('/'):
  278. currentDir += dirElement + '/'
  279. try:
  280. srv.chdir(currentDir)
  281. except:
  282. _logger.info(
  283. _(
  284. '(Part of the) path didn\'t exist. '
  285. 'Creating it now at %s'
  286. ) % currentDir
  287. )
  288. # Make directory and then navigate into it
  289. srv.mkdir(currentDir, mode=777)
  290. srv.chdir(currentDir)
  291. pass
  292. srv.chdir(pathToWriteTo)
  293. # Loop over all files in the directory.
  294. for f in os.listdir(dir):
  295. fullpath = os.path.join(dir, f)
  296. if os.path.isfile(fullpath):
  297. print(fullpath)
  298. srv.put(fullpath)
  299. # Navigate in to the correct folder.
  300. srv.chdir(pathToWriteTo)
  301. # Loop over all files in the directory from the back-ups.
  302. # We will check the creation date of every back-up.
  303. for file in srv.listdir(pathToWriteTo):
  304. # Get the full path
  305. fullpath = os.path.join(pathToWriteTo, file)
  306. # Get the timestamp from the file on the external
  307. # server
  308. timestamp = srv.stat(fullpath).st_atime
  309. createtime = datetime.datetime.fromtimestamp(timestamp)
  310. now = datetime.datetime.now()
  311. delta = now - createtime
  312. # If the file is older than the daystokeepsftp (the
  313. # days to keep that the user filled in on the Odoo form
  314. # it will be removed.
  315. if delta.days >= rec.daystokeepsftp:
  316. # Only delete files, no directories!
  317. if srv.isfile(fullpath) and ".dump" in file:
  318. print("Delete: " + file)
  319. srv.unlink(file)
  320. # Close the SFTP session.
  321. srv.close()
  322. except Exception as e:
  323. _logger.debug(
  324. 'Exception! We couldn\'t back '
  325. 'up to the FTP server..'
  326. )
  327. # At this point the SFTP backup failed.
  328. # We will now check if the user wants
  329. # an e-mail notification about this.
  330. if rec.sendmailsftpfail:
  331. try:
  332. ir_mail_server = self.pool.get('ir.mail_server')
  333. message = (
  334. "Dear,\n\nThe backup for the server %s"
  335. " (IP: %s) failed.Please check"
  336. " the following details:\n\n"
  337. "IP address SFTP server: %s \nUsername: %s"
  338. "\nPassword: %s"
  339. "\n\nError details: %s \n\nWith kind regards"
  340. ) % (
  341. rec.host, rec.sftpip, rec.sftpip,
  342. rec.sftpusername, rec.sftppassword,
  343. tools.ustr(e)
  344. )
  345. msg = ir_mail_server.build_email(
  346. "auto_backup@" + rec.name + ".com",
  347. [rec.emailtonotify],
  348. "Backup from " + rec.host + "(" + rec.sftpip +
  349. ") failed", message)
  350. ir_mail_server.send_email(cr, user, msg)
  351. except Exception:
  352. pass
  353. """Remove all old files (on local server) in case this is configured..
  354. This is done after the SFTP writing to prevent unusual behaviour:
  355. If the user would set local back-ups to be kept 0 days and the SFTP
  356. to keep backups xx days there wouldn't be any new back-ups added
  357. to the SFTP.
  358. If we'd remove the dump files before they're writen to the SFTP
  359. there willbe nothing to write. Meaning that if an user doesn't want
  360. to keep back-ups locally and only wants them on the SFTP
  361. (NAS for example) there wouldn't be any writing to the
  362. remote server if this if statement was before the SFTP write method
  363. right above this comment.
  364. """
  365. if rec.autoremove is True:
  366. dir = rec.bkp_dir
  367. # Loop over all files in the directory.
  368. for f in os.listdir(dir):
  369. fullpath = os.path.join(dir, f)
  370. timestamp = os.stat(fullpath).st_ctime
  371. createtime = datetime.datetime.fromtimestamp(timestamp)
  372. now = datetime.datetime.now()
  373. delta = now - createtime
  374. if delta.days >= rec.daystokeep:
  375. # Only delete files (which are .dump), no directories.
  376. if os.path.isfile(fullpath) and ".dump" in f:
  377. print("Delete: " + fullpath)
  378. os.remove(fullpath)
  379. db_backup()
  380. # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: