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.

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