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.

425 lines
16 KiB

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