Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement config backup cli #1576

Merged
merged 12 commits into from Jun 24, 2017
Merged
12 changes: 3 additions & 9 deletions conf/settings.conf.in
Expand Up @@ -69,15 +69,6 @@ USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
Expand All @@ -97,6 +88,9 @@ MEDIA_URL = '/media/'
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = os.path.join('${buildout:depdir}', 'static')

# Absolute filesystem path where config backups are stored by default
DEFAULT_CB_DIR = os.path.join(MEDIA_ROOT, 'config-backups')

# Additional locations of static files
#STATICFILES_DIRS = (
# # Put strings here, like "/home/html/static" or "C:/www/django/static".
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -33,6 +33,7 @@
entry_points={
'console_scripts': [
'backup-plugin = backup.scheduler:main',
'backup-config = scripts.config_backup:main',
'bootstrap = scripts.bootstrap:main',
'data-collector = smart_manager.data_collector:main',
'debug-mode = scripts.debugmode:main',
Expand Down
29 changes: 29 additions & 0 deletions src/rockstor/scripts/config_backup.py
@@ -0,0 +1,29 @@
"""
Copyright (c) 2012-2016 RockStor, Inc. <http://rockstor.com>
This file is part of RockStor.

RockStor is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

RockStor is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from system.config_backup import backup_config

def main():
cbo = backup_config()
print('config exported to: "%s"' % cbo.full_path())


if __name__ == '__main__':
main()
11 changes: 10 additions & 1 deletion src/rockstor/storageadmin/models/config_backup.py
Expand Up @@ -16,8 +16,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import os
from django.db import models

from django.conf import settings

class ConfigBackup(models.Model):
filename = models.CharField(max_length=64)
Expand All @@ -28,5 +29,13 @@ class ConfigBackup(models.Model):
def __unicode__(self):
return "{0}".format(self.filename)

def full_path(self):
return os.path.join(self.cb_dir(), self.filename)

class Meta:
app_label = 'storageadmin'

@staticmethod
def cb_dir():
return settings.DEFAULT_CB_DIR

66 changes: 19 additions & 47 deletions src/rockstor/storageadmin/views/config_backup.py
Expand Up @@ -25,7 +25,8 @@
from storageadmin.util import handle_exception
import rest_framework_custom as rfc
from django.core.management import call_command
from system.osi import run_command
from system.osi import (run_command, md5sum)
from system.config_backup import backup_config
from datetime import datetime
from rest_framework.parsers import FileUploadParser, MultiPartParser
from django_ztask.decorators import task
Expand Down Expand Up @@ -160,58 +161,28 @@ def restore_config(cbid):

class ConfigBackupMixin(object):
serializer_class = ConfigBackupSerializer
cb_dir = os.path.join(settings.MEDIA_ROOT, 'config-backups')

@staticmethod
def _md5sum(fp):
return run_command(['/usr/bin/md5sum', fp])[0][0].split()[0]


class ConfigBackupListView(ConfigBackupMixin, rfc.GenericView):

def get_queryset(self, *args, **kwargs):
for cbo in ConfigBackup.objects.all():
fp = os.path.join(self.cb_dir, cbo.filename)
fp = os.path.join(ConfigBackup.cb_dir(), cbo.filename)
if (not os.path.isfile(fp)):
cbo.delete()
md5sum = self._md5sum(fp)
if (md5sum != cbo.md5sum):
fp_md5sum = md5sum(fp)
if (fp_md5sum != cbo.md5sum):
logger.error('md5sum mismatch for %s. cbo: %s file: %s. '
'Deleting dbo' %
(cbo.filename, cbo.md5sum, md5sum))
(cbo.filename, cbo.md5sum, fp_md5sum))
cbo.delete()
return ConfigBackup.objects.filter().order_by('-id')

@transaction.atomic
def post(self, request):
models = {'storageadmin':
['user', 'group', 'sambashare', 'netatalkshare', 'nfsexport',
'nfsexportgroup', 'advancednfsexport', ],
'smart_manager':
['service', ], }
model_list = []
for a in models:
for m in models[a]:
model_list.append('%s.%s' % (a, m))
logger.debug('model list = %s' % model_list)
logger.debug('backing up config...')
with self._handle_exception(request):
filename = ('backup-%s.json' %
datetime.now().strftime('%Y-%m-%d-%H%M%S'))
if (not os.path.isdir(self.cb_dir)):
os.mkdir(self.cb_dir)
fp = os.path.join(self.cb_dir, filename)
with open(fp, 'w') as dfo:
call_command('dumpdata', *model_list, stdout=dfo)
dfo.write('\n')
call_command('dumpdata', database='smart_manager', *model_list,
stdout=dfo)
run_command(['/usr/bin/gzip', fp])
gz_name = ('%s.gz' % filename)
fp = os.path.join(self.cb_dir, gz_name)
md5sum = self._md5sum(fp)
size = os.stat(fp).st_size
cbo = ConfigBackup(filename=gz_name, md5sum=md5sum, size=size)
cbo.save()
cbo = backup_config()
return Response(ConfigBackupSerializer(cbo).data)


Expand All @@ -221,7 +192,7 @@ class ConfigBackupDetailView(ConfigBackupMixin, rfc.GenericView):
def delete(self, request, backup_id):
with self._handle_exception(request):
cbo = self._validate_input(backup_id, request)
fp = os.path.join(self.cb_dir, cbo.filename)
fp = os.path.join(ConfigBackup.cb_dir(), cbo.filename)
if (os.path.isfile(fp)):
os.remove(fp)
cbo.delete()
Expand All @@ -232,7 +203,7 @@ def post(self, request, backup_id):
with self._handle_exception(request):
command = request.data.get('command', 'restore')
if (command == 'restore'):
cbo = self._validate_input(backup_id)
cbo = self._validate_input(backup_id, request)
# models that need to be restored.
# 1. User, Group, Accesskeys?
# 2. SambaShare
Expand Down Expand Up @@ -260,14 +231,14 @@ class ConfigBackupUpload(ConfigBackupMixin, rfc.GenericView):

def get_queryset(self, *args, **kwargs):
for cbo in ConfigBackup.objects.all():
fp = os.path.join(self.cb_dir, cbo.filename)
fp = os.path.join(ConfigBackup.cb_dir(), cbo.filename)
if (not os.path.isfile(fp)):
cbo.delete()
md5sum = self._md5sum(fp)
if (md5sum != cbo.md5sum):
fp_md5sum = md5sum(fp)
if (fp_md5sum != cbo.md5sum):
logger.error('md5sum mismatch for %s. cbo: %s file: %s. '
'Deleting dbo' %
(cbo.filename, cbo.md5sum, md5sum))
(cbo.filename, cbo.md5sum, fp_md5sum))
cbo.delete()
return ConfigBackup.objects.filter().order_by('-id')

Expand All @@ -282,11 +253,12 @@ def post(self, request, format=None):
cbo = ConfigBackup.objects.create(
filename=filename, config_backup=file_obj
)
if (not os.path.isdir(self.cb_dir)):
os.mkdir(self.cb_dir)
fp = os.path.join(self.cb_dir, filename)
cb_dir = ConfigBackup.cb_dir()
if (not os.path.isdir()):
os.mkdir(cb_dir)
fp = os.path.join(cb_dir, filename)

cbo.md5sum = self._md5sum(fp)
cbo.md5sum = md5sum(fp)
cbo.size = os.stat(fp).st_size
cbo.save()
return Response(ConfigBackupSerializer(cbo).data)
57 changes: 57 additions & 0 deletions src/rockstor/system/config_backup.py
@@ -0,0 +1,57 @@
"""
Copyright (c) 2012-2016 RockStor, Inc. <http://rockstor.com>
This file is part of RockStor.

RockStor is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

RockStor is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import logging
import os
from datetime import datetime
from django.core.management import call_command
from storageadmin.models import ConfigBackup
from system.osi import (run_command, md5sum)

logger = logging.getLogger(__name__)

def backup_config():
models = {'storageadmin':
['user', 'group', 'sambashare', 'netatalkshare', 'nfsexport',
'nfsexportgroup', 'advancednfsexport', ],
'smart_manager':
['service', ], }
model_list = []
for a in models:
for m in models[a]:
model_list.append('%s.%s' % (a, m))
logger.debug('model list = %s' % model_list)

filename = ('backup-%s.json' % datetime.now().strftime('%Y-%m-%d-%H%M%S'))
cb_dir = ConfigBackup.cb_dir()

if (not os.path.isdir(cb_dir)):
os.mkdir(cb_dir)
fp = os.path.join(cb_dir, filename)
with open(fp, 'w') as dfo:
call_command('dumpdata', *model_list, stdout=dfo)
dfo.write('\n')
call_command('dumpdata', database='smart_manager', *model_list, stdout=dfo)
run_command(['/usr/bin/gzip', fp])
gz_name = ('%s.gz' % filename)
fp = os.path.join(cb_dir, gz_name)
size = os.stat(fp).st_size
cbo = ConfigBackup(filename=gz_name, md5sum=md5sum(fp), size=size)
cbo.save()
return cbo