Hi,

I have attached the ds_backup.py code. Tack on all disclaimers - it has not been tested in the XS-0.7 and 12.1.0 environment. It will need some revision to deal with the 'shared' XO environment in Lesotho so I will work on it while I am there.

However, it really is not changed from XS-0.7 in areas that matter to you. The changes are in the way the Journal objects are handled. Martin Langhoff used rsync so that the backup is a snapshot of the local datastore. I am trying to establish the server-side as the real Journal with the local datastore containing only the currently relevant content. It seems sad that kids must delete their Journal because of the limited size of the local store. The message: Journal is full is not helpful, because the fact is that the store is full, often because of the excessive number of installed activities. With a school server, this is totally unnecessary since the Sugar Activities are available on the school server so that unused ones can be deleted on the XO.

I had to move to mod_wsgi for Django (library). As far as I know, there is no change needed client-side. Apache needs to have wsgi installed (in XS-0.7 it is). Then all that is needed is a file in httpd/conf.d.
I have also attached the one I am using.


On 04/07/2013 06:04 AM, George Hunt wrote:

Hi Tony,

I'm playing with ds-backup now, because one of the dependencies for
ds-backup is mod_python which was dropped from fedora 18, in favor of
mod_wsgi. It seems like a great time to rethink the issue.

I cannot find the working code you mention in this email in my google
stack.  Can you send it to me again?

I'm also talking with Tim Moody, about using puppet client as a way of
pushing and modifying the configuration of XO's in the classroom.  Do
you have any experience, or ideas, about such a proposal? I'm not sure
whether ds-backup might be used to introduce puppet client back into XOs.

George

On Sun, Dec 9, 2012 at 12:25 PM, Tony Anderson <[email protected]
<mailto:[email protected]>> wrote:

    Hi,

    I will send you the scripts as soon as I have a chance to try them
    on 12.0.1 and XS-0.7. Last year they were running on xs-0.6 and
    build 852 at Saint Jacob. There shouldn't be too much difference,
    although so far on most of the code I have been wrong by about 3 days!

    The identity problem really isn't that hard. A lot of the mailing
    list discussion seems related to running Sugar on something besides
    an XO.

    The user is identified on the server by the serial-number. For the
    backup, this is not a problem. The problem comes from <OLPC where an
    XO is shared. Then there needs to be something like a login to
    identify the actual user. There also needs to be separate Journals
    on the XO and separate backups on the school server.

    I should be able to give you working code within a week (I leave
    Kigali on 12/23 so it must be before then).

    Yours,

    Tony


    On 12/09/2012 04:11 PM, George Hunt wrote:

        Wow Tony,

        Your solution definitely needs to be part of the mix as we go
        forward.
        Can you send me copies of the scripts, or the changes you made to
        accomplish these objectives?  If we are going to look at the
        serialnumber-user-identity issue, we might be making changes to
        the same
        packages.

        Have you worked through the changes needed to add user identity to
        journal backups? As Paul Fox was suggesting, I think there will
        be lots
        of ripple effects, if and when we start adding additional users, in
        addition to the user "olpc".  Or maybe, someone will come up with a
        simplifying assumption, or approach.

        George



        On Sun, Dec 9, 2012 at 4:49 AM, Tony Anderson
        <[email protected] <mailto:[email protected]>
        <mailto:[email protected] <mailto:[email protected]>>__>
        wrote:

             Hi, Sameer

             I got an email from Nick Doiron re his visit to the Marshall
             Islands. He mentioned that you gave him a script to create
        a csv
             from the Journal backup.

             As I have mentioned several times on the list, I believe
        Martin's
             backup scheme while elegantly implemented is not adequate.
        His model
             is the traditional backup/restore.

             The problem, as always, is storage space. When an XO-1 is
        out of
             space, the user gets a message 'Journal is full'. The only
        practical
             solution often is to reflash the XO losing all of the Journal
             objects. If a user deletes an object from the Journal on
        the XO, the
             rsynch also removes it from the backup.

             I modified the scripts ds-backup.sh and ds-backup.py on the
        XO to
             use a different paradigm. The registration process creates two
             scripts in the /library/users/serial-number folder on the
        school
             server: journal and log. The backup script uploads the
        object to
             journal if there is an associated data file; otherwise, it is
             uploaded to the log folder and deleted from the local
        store. The
             remaining objects are marked as favorites in the Journal
        Activity to
             show that the data file is available on the XO. If the user
        clears
             the star, the data file is deleted from the datastore but
        remains on
             the school server. If the user fills a clear star, the
        backup script
             downloads the data file from the school server.

             At Saint Jacob, all of the laptops are 4gb XO-1.5 so I have
        not had
             to implement storage management. The plan would be to set
        limits on
             the size of the datastore and the /home/olpc/Activities
        folders. If
             a datastore outgrows the limit, data files can be deleted LRU.
             Similarly, activities can be deleted LRU. This way the user
        will not
             have to be involved. The user can always request a needed
        file to be
             restored to the local datastore and can request an activity be
             downloaded and installed from the school server repository.
        I expect
             this will be needed in Lesotho for XO-1.

             This method also supports restoring the users Journal after
        a reflash.
             It also enables a replacement laptop by renaming the
        serial-number
             folder on the school server (and updating the registration
        information).

             I am not sure if you are trying to implement this
        functionality, but
             it is important at a deployment.

             Yours,

             Tony





LoadModule wsgi_module modules/mod_wsgi.so
#!/usr/bin/python
# -*- coding: utf-8 -*-
#updated version
# Copyright (C) 2007 Ivan Krstić
# Copyright (C) 2007 Tomeu Vizoso
# Copyright (C) 2007 One Laptop per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License (and
# no other version) as published by the Free Software Foundation.
#
# This program 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import os, re, sys, tempfile, glob, time
import urllib2
from urllib2 import URLError, HTTPError, urlopen
import subprocess
from subprocess import Popen, PIPE, call

from sugar import env
from sugar import profile

from sugar.datastore import datastore
from path import path
from sftp import sftp
import datetime

LIMIT = 1024
WORKPATH = path('/tmp/')
EXCLUDED = ['org.laptop.Terminal', 'org.laptop.Log']

class BackupError(Exception): pass
class ProtocolVersionError(BackupError): pass
class RefusedByServerError(BackupError): pass
class ServerTooBusyError(BackupError): pass
class TransferError(BackupError): pass
class NoPriorBackups(BackupError): pass
class BulkRestoreUnavailable(BackupError): pass

def check_server_available(server, xo_serial):

    try:
        ret = urllib2.urlopen(server + '/available/%s' % xo_serial).read()
        return 200
    except HTTPError, e:
        # server is there, did not fullfull req
        #  expect 404, 403, 503 as e[1]
        return e.code
    except URLError, e:
        # log it?
        # print e.reason
        return -1

def sync_time_to_xs():

    xstime = urlopen('http://schoolserver/bibliotheque/school/time')
    response = xstime.read().replace('\n','')
    txtout = 'sudo date --set="' + response + '"'
    print txtout
    fout = open('/tmp/xstime','w')
    fout.write(txtout)
    fout.close()

def datastore_to_xs():
    #open log
    log = open('/tmp/logBackup','w')
    #get journal records
    query = {}
    keys = ['uid','activity','title','title_set_by_user','mime_type','journal','keep']
    ds_objects,num_objects = datastore.find(query,properties=keys)
    print >> log, 'num_objects',num_objects
    #process each one
    for i in range(num_objects):
        ds_object = ds_objects[i]
        #is it in the journal
        try:
            journal = ds_object.metadata['journal']
        except:
            print >> log,sys.exc_info()[:2]
            #new record created since last run of ds-backup
            #is it a journal record? (title_set_by_user == 1 and has_file_path)
            print >> log, i, 'new record'
            activity = ds_object.metadata['activity']
            if activity in EXCLUDED:
                jr = False
                print >> log, 'activity',activity,'excluded'
            else:
                jr = True
            title_set_by_user = ds_object.metadata['title_set_by_user']
            file_path = ds_object.get_file_path()
            jobject = datastore.get(ds_object.object_id)
            metadata = {}
            for key in jobject.metadata.keys():
                if not key == 'preview':
                    metadata[key] = jobject.metadata[key]
            metadata_path = WORKPATH+path(file_path).namebase+'.metadata'
            fout = open(metadata_path,'w')
            fout.write(str(metadata))
            fout.close()
            if path(file_path).exists() and title_set_by_user == '1' and jr:
                #journal record
                print >>log, 'journal record',file_path
                try:
                    ds_object.metadata['journal'] = 1
                except:
                    print >> log, 'create journal key failed',sys.exc_info()[:2]
                ds_object.metadata['keep'] = 1
                script = 'cd journal\nput '+ metadata_path+'\nput '+file_path+'\n'
                result,err = sftp(script,WORKPATH)
                if err:
                    print >> log, script, 'err', err
                print >> log, ds_object.metadata['title'],'added to Journal',
                print >> log, 'keep', ds_object.metadata['keep']
                datastore.write(ds_object)
            else:
                #log record
                print >> log, 'log record'
                script = 'cd log\nput '+metadata_path+'\n'
                result,err = sftp(script,WORKPATH)
       	       	if err:
       	       	    print >> log, script, 'err', err
                else: 
                    try:
                        datastore.delete(ds_object.object_id)
                    except:
                        pass
            subprocess.call('rm -rf ' + WORKPATH / '*.metadata',shell=True)
        else:
            #journal record
            file_path = ds_object.get_file_path()
            fn = path(file_path).name
            try:
                testjournal = ds_object.metadata['journal']
            except:
                testjournal = 'key error'
            try:
                testkeep = ds_object.metadata['keep']
            except:
                testkeep = 'key error'
            print >> log, i, 'existing journal record', fn, 
            print >> log, 'journal',testjournal,
            print >> log, 'keep',testkeep
            if journal == 2:
                print >> log, 'delete requested'
                #delete it from ss
                file_path = ds_object.get_filepath
                fn = path(file_path).name
                script = 'cd journal\nrm -rf ' + fn + '*'
                result,err = sftp(script)
       	       	if result:
       	       	    print >> log, 'result',script,result
       	       	if err:
       	       	    print >> log, 'err',script,err 
                #delete locally
                try:
                    datastore.delete(ds_object.object_id)
                except:
                    pass
            else:
                try:
                    keep = ds_object.metadata['keep']
                except:
                    keep = 'key error'
                try:
                    journal = ds_object.metadata['journal']
                except:
                    journal = 'key error'
                print >> log, 'journal record: keep', keep, 'journal', journal
                if keep == 0 and journal == 1:
                    #delete local data
                    print >> log, 'deleting local data'
                    dsobject.file_path = None
                    dsobject.metadata['journal'] = 0
                    datastore.write(ds_object)
                if keep == 1 and journal == 0:
                    #get local copy of data from ss
                    print >> log, 'getting local copy of data from ss'
                    script = 'cd journal\nget '+fn
                    result,err=sftp(script,WORKPATH)
       	       	    if result:
       	       	        print >> log, 'result', script, result
       	       	    if err:
       	       	        print >> log, 'err',script,err 
                    dsobject.file_path = path('/tmp') / fn
                    dsobject.metadata['journal'] = 1
                    datastore.write(ds_object)
    #log version
    pth = path('/home/olpc/.sugar/default/patch')
    if pth.exists():
        fin = open(pth,'r')
        version = fin.read()
        fin.close()
        metadata = {}
        metadata['mtime'] = datetime.date
        metadata['title'] = 'version'
        metadata['title_set_by_user'] = 1
        metadata['version'] = version
        metadata_path = WORKPATH+path(file_path).namebase+'.metadata'
        fout = open(metadata_path,'w')
        fout.write(str(metadata))
        fout.close()
        script = 'cd log\nput '+metadata_path+'\n'
        result,err = sftp(script,WORKPATH)
        if err:
            print >> log, script, 'err', err
        else:
            try:
                datastore.delete(ds_object.object_id)
            except:
                pass
    log.close()


def get_sn():
    if have_ofw_tree():
        return read_ofw('mfg-data/SN')
    # on SoaS try gconf, 'identifiers'
    sn = gconf_get_string('/desktop/sugar/soas_serial')
    if sn:
        return sn
    sn = identifier_get_string('sn')
    if sn:
        return sn

    return 'SHF00000000'

def get_backup_url():

    bu = gconf_get_string('/desktop/sugar/backup_url')
    if bu:
        return bu
    try: # pre-gconf
        from iniparse import INIConfig
        conf = INIConfig(open(os.path.expanduser('~')+'/.sugar/default/config'))
        # this access mode throws an exception if the value
        # does not exist
        bu = conf['Server']['backup1']
    except:
        pass
    if bu:
        return bu
    bu = identifier_get_string('backup_url')
    if bu:
        return bu
    return ''

def gconf_get_string(key):
    """We cannot use python gconf from cron scripts,
    but cli gconftool-2 does the trick.
    Will throw subprocess.Popen exceptions"""
    try:
        value = Popen(['gconftool-2', '-g', key],
                      stdout=PIPE).communicate()[0]
        return value
    except:
        return ''

def identifier_get_string(key):
    """This is a config method used by some versions of
    Sugar -- in use in some SoaS"""
    try:
        fpath = os.path.expanduser('~')+'/.sugar/default/identifiers/'+key
        fh    = open(fpath, 'r')
        value = fh.read().rstrip('\0\n')
        fh.close()
        return value
    except:
        return ''

def have_ofw_tree():
    return os.path.exists('/ofw')

def read_ofw(path):
    path = os.path.join('/ofw', path)
    if not os.path.exists(path):
        return None
    fh = open(path, 'r')
    data = fh.read().rstrip('\0\n')
    fh.close()
    return data


_______________________________________________
Server-devel mailing list
[email protected]
http://lists.laptop.org/listinfo/server-devel

Reply via email to