def validate_controller_version(version):
    if not version or version in ['beta', 'dev']:
        return
    try:
        decode_version(version)
    except ValueError as e:
        raise CommandError(e)
Example #2
0
def validate_controller_version(version):
    if not version or version == 'dev':
        return
    try:
        decode_version(version)
    except ValueError as e:
        raise CommandError(e)
Example #3
0
 def test_decode_version(self):
     self.assertEqual(decode_version('0.12'), (0, 12, 0))
     self.assertEqual(decode_version('0.12.3'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3a4'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3b5'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3rc6'), (0, 12, 3))
     self.assertEqual(decode_version('1.0'), (1, 0, 0))
     self.assertEqual(decode_version('1.0b3'), (1, 0, 0))
     self.assertRaises(ValueError, decode_version, '0_34b')
     self.assertRaises(ValueError, decode_version, '=0.34b')
     self.assertRaises(TypeError, decode_version, None)
     self.assertRaises(TypeError, decode_version, 0123)
Example #4
0
 def test_decode_version(self):
     self.assertEqual(decode_version('0.12'), (0, 12, 0))
     self.assertEqual(decode_version('0.12.3'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3a4'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3b5'), (0, 12, 3))
     self.assertEqual(decode_version('0.12.3rc6'), (0, 12, 3))
     self.assertEqual(decode_version('1.0'), (1, 0, 0))
     self.assertEqual(decode_version('1.0b3'), (1, 0, 0))
     self.assertRaises(ValueError, decode_version, '0_34b')
     self.assertRaises(ValueError, decode_version, '=0.34b')
     self.assertRaises(TypeError, decode_version, None)
     self.assertRaises(TypeError, decode_version, 0123)
 def handle(self, *args, **options):
     # Warn about deprecated options
     if options.get('local'):
         self.stderr.write("Warning: 'local' option is deprecated and will be ignored.\n")
     
     version = options.get('version')
     upgrade_notes = []
     if version:
         try:
             major, major2, minor = decode_version(version)
         except ValueError as e:
             raise CommandError(e)
         # Represent version as two digits per number: 1.2.2 -> 10202
         version = int(str(major) + "%02d" % int(major2) + "%02d" % int(minor))
         
         # Pre-upgrade operations (version specific)
         if version < 835:
             # prevent schema migrations from failing
             if is_installed('firmware'):
                 from firmware.models import Build
                 Build.objects.filter(base_image=None).update(base_image='')
         if version <= 902:
             if is_installed('maintenance'):
                 # Apply losed migrations
                 from south.models import MigrationHistory
                 migrated = MigrationHistory.objects.filter(app_name='maintenance').exists()
                 if not migrated:
                     run('python manage.py migrate maintenance 0001 --fake')
         if version < 1002:
             # Update monitor settings (fix typo and add DiskFreeMonitor)
             context = {
                 'settings': run("find . -type f -name 'settings.py'|grep -v 'vct/'")
             }
             # Try automaticate update (making a backup)
             if context['settings']:
                 run("cp %(settings)s %(settings)s.upgrade.bak" % context)
                 # fix NumProcessesMonitor typo
                 run("sed -i 's/NumPocessesMonitor/NumProcessesMonitor/g' "
                     "%(settings)s" % context)
                 # append disk monitor (if needed)
                 # this is a rude check (but runned in a conservative way)
                 if 'DiskFreeMonitor' not in open(context['settings']).read():
                     run("sed -i '/MONITOR_MONITORS = ($/ a\ "
                         "   (\"monitor.monitors.DiskFreeMonitor\",),' "
                         "%(settings)s" % context)
             # warn the user about settings changes
             autoupdate_status = 'OK' if context['settings'] else 'FAIL'
             upgrade_notes.append('The monitor application has changed and .'
                 'some settings updates are required:\n'
                 ' - Fix typo on NumProcessesMonitor (missing "r")\n'
                 ' - Enable disk monitor\n'
                 ' Please read the monitor app doc (MONITOR_MONITORS setting)\n'
                 'AUTOUPDATE: %s' % autoupdate_status)
         if version <= 1102:
             # Handle InconsistentMigrationHistory on tinc app
             # * v0.11.2 tinc includes 0022, 0028..0030
             # * v0.11.3 tinc adds 0023..0027
             # We can force south to merge migrations because
             # are implemented to be runned without dependencies
             run('python manage.py migrate tinc 0030 --merge --noinput')
     
     if not options.get('specifics_only'):
         # Common stuff
         development = options.get('development')
         controller_admin = os.path.join(os.path.dirname(__file__), '../../bin/')
         controller_admin = os.path.join(controller_admin, 'controller-admin.sh')
         run('chmod +x %s' % controller_admin)
         
         extra = '--development' if development else ''
         if options.get('proxy'):
             extra += ' --proxy %s' % options.get('proxy')
         run("%s install_requirements " % controller_admin + extra)
         run("python manage.py collectstatic --noinput")
         
         run("python manage.py syncdb --noinput")
         run("python manage.py migrate --noinput")
         if is_installed('firmware'):
             run("python manage.py syncfirmwareplugins")
         if is_installed('notifications'):
             run("python manage.py syncnotifications")
         if is_installed('resources'):
             run("python manage.py syncresources")
         if options.get('restart'):
             run("python manage.py restartservices")
     
     if not version:
         self.stderr.write('\nNext time you migth want to provide a --from argument '
                           'in order to run version specific upgrade operations\n')
         return
     
     # Post-upgrade operations (version specific)
     if version <= 629:
         # Clean existing sessions because of change on auth backend
         run('echo "delete from django_session;" | python manage.py dbshell')
     if version < 801:
         deprecate_periodic_tasks(('state.ping',))
     if version < 809:
         # Add PKI directories
         from pki import ca
         from controller.utils.paths import get_site_root
         site_root = get_site_root()
         username = run("stat -c %%U %s" % site_root)
         get_dir = lambda f: os.path.dirname(getattr(ca, f+'_path'))
         for d in set( get_dir(f) for f in ['priv_key', 'pub_key', 'cert'] ):
             run('mkdir -p %s' % d)
             run('chown %s %s' % (username, d))
         upgrade_notes.append('HTTPS certificate support for the management '
             'network has been introduced in version 0.8.9.\n'
             'In order to use it you sould run:\n'
             '  > python manage.py setuppki\n'
             '  > sudo python manage.py setupapache\n')
     if version < 838:
         # Purge communitynetworks.periodic_cache_node_db
         from djcelery.models import PeriodicTask
         PeriodicTask.objects.filter(name='communitynetworks.periodic_cache_node_db').delete()
         run('rabbitmqctl stop_app')
         run('rabbitmqctl reset')
         run('rabbitmqctl start_app')
         run('service celeryd restart')
         upgrade_notes.append('New Celeryd init.d configuration has been '
             'introduced in 0.8.38.\nIt is strongly recommended to upgrade by\n'
             '  > sudo python manage.py setupceleryd\n')
         # Deprecate x86 and amd64 architectures
         from nodes.models import Node
         Node.objects.filter(arch='x86').update(arch='i686')
         Node.objects.filter(arch='amd64').update(arch='x86_64')
         upgrade_notes.append('In order to support Base authentication while downloading '
             'firmwares you should add "WSGIPassAuthorization On" on your apache config.\n'
             'Alternatively you can perform this operation with the following command\n'
             '  > sudo python manage.py setupapache\n'
             '  > /etc/init.d/apache2 reload\n')
     if version < 900:
         upgrade_notes.append('Apache configuration is now placed under '
             '/etc/apache2/conf.d/<project_name>.conf. It is convenient for you '
             'to migrate your current configuration located on /etc/apache2/httpd.conf '
             'to this new location.\n')
         upgrade_notes.append('Celery workers configuration has been updated. '
             'Please update it by running:\n'
             '  > sudo python manage.py setupceleryd\n')
     if version < 905:
         # TODO find the root cause of this
         # maybe is shit imported on settings that import settings like add_app
         # Prevent crazy import erros to appear :S
         from django.utils import translation
         translation.activate('en-us')
         # Change template types for more generic ones
         from slices.models import Template
         from slices.settings import SLICES_TEMPLATE_TYPES
         template_types = [ t[0] for t in SLICES_TEMPLATE_TYPES ]
         if 'debian' in template_types:
             Template.objects.filter(type='debian6').update(type='debian')
         if 'openwrt' in template_types:
             Template.objects.filter(type='openwrt-backfire').update(type='openwrt')
     if version < 906:
         deprecate_periodic_tasks(('state.nodestate', 'state.sliverstate'))
     if version <= 907:
         # Generate sha256
         from slices.models import Template
         for template in Template.objects.all():
             template.save()
         upgrade_notes.append("It is extremly recommended to update your database "
             "settings to enable atomic request behaviour:\n"
             "  https://docs.djangoproject.com/en/dev/topics/db/transactions/#tying-transactions-to-http-requests\n"
             "Just add:\n"
             "   'ATOMIC_REQUESTS': True,\n"
             "into DATABASES setting within <project_name>/<project_name>/settings.py")
     if version <= 1003:
         # Update firmware configuration after Island refactor (#264)
         from firmware.models import ConfigFile
         try:
             cfg_file = ConfigFile.objects.get(path__contains="node.tinc.connect_to")
         except (ConfigFile.DoesNotExist, ConfigFile.MultipleObjectsReturned):
             # Warn the user that needs to perform manual update
             msg = "Firmware configuration update has failed. "
         else:
             cfg_file.content = cfg_file.content.replace("node.tinc.island", "node.island")
             cfg_file.save()
             msg = "Firmware configuration updated successfully. Updated ConfigFile ID: %i." % cfg_file.pk
         upgrade_notes.append("%s\nPlease check version 0.10.4 release notes:\n"
             "https://wiki.confine-project.eu/soft:server-release-notes#section0104" % msg)
     if version < 1103:
         # Update mgmt network Server APIs certificate
         # perform raw SQL querie because models hasn't been
         # reloaded yet and cannot access e.g. server.api
         from django.db import connection
         from nodes.models import Server
         from pki import ca
         
         server_id = Server.objects.order_by('id').first().pk
         try:
             cert = ca.get_cert().as_pem()
         except IOError:
             msg = ("Failed to update Server APIs certificate. Missing "
                    "server certificate '%s'.\n"
                    "Run 'python manage.py setuppki --help'" % ca.cert_path)
             upgrade_notes.append(msg)
         else:
             update_sql = ('UPDATE "nodes_serverapi" SET cert = %s '
                           'WHERE "nodes_serverapi"."server_id" = %s')
             cursor = connection.cursor()
             cursor.execute(update_sql, [cert, server_id])
             del cursor
             upgrade_notes.append("Updated Server APIs certificate.")
     
     if upgrade_notes and options.get('print_upgrade_notes'):
         self.stdout.write('\n\033[1m\n'
             '    ===================\n'
             '    ** UPGRADE NOTES **\n'
             '    ===================\n\n' +
             '\n'.join(upgrade_notes) + '\033[m\n')
    def handle(self, *args, **options):
        # Warn about deprecated options
        if options.get('local'):
            self.stderr.write(
                "Warning: 'local' option is deprecated and will be ignored.\n")

        version = options.get('version')
        upgrade_notes = []
        if version:
            try:
                major, major2, minor = decode_version(version)
            except ValueError as e:
                raise CommandError(e)
            # Represent version as two digits per number: 1.2.2 -> 10202
            version = int(
                str(major) + "%02d" % int(major2) + "%02d" % int(minor))

            # Pre-upgrade operations (version specific)
            if version < 835:
                # prevent schema migrations from failing
                if is_installed('firmware'):
                    from firmware.models import Build
                    Build.objects.filter(base_image=None).update(base_image='')
            if version <= 902:
                if is_installed('maintenance'):
                    # Apply losed migrations
                    from south.models import MigrationHistory
                    migrated = MigrationHistory.objects.filter(
                        app_name='maintenance').exists()
                    if not migrated:
                        run('python manage.py migrate maintenance 0001 --fake')
            if version < 1002:
                # Update monitor settings (fix typo and add DiskFreeMonitor)
                context = {
                    'settings':
                    run("find . -type f -name 'settings.py'|grep -v 'vct/'")
                }
                # Try automaticate update (making a backup)
                if context['settings']:
                    run("cp %(settings)s %(settings)s.upgrade.bak" % context)
                    # fix NumProcessesMonitor typo
                    run("sed -i 's/NumPocessesMonitor/NumProcessesMonitor/g' "
                        "%(settings)s" % context)
                    # append disk monitor (if needed)
                    # this is a rude check (but runned in a conservative way)
                    if 'DiskFreeMonitor' not in open(
                            context['settings']).read():
                        run("sed -i '/MONITOR_MONITORS = ($/ a\ "
                            "   (\"monitor.monitors.DiskFreeMonitor\",),' "
                            "%(settings)s" % context)
                # warn the user about settings changes
                autoupdate_status = 'OK' if context['settings'] else 'FAIL'
                upgrade_notes.append(
                    'The monitor application has changed and .'
                    'some settings updates are required:\n'
                    ' - Fix typo on NumProcessesMonitor (missing "r")\n'
                    ' - Enable disk monitor\n'
                    ' Please read the monitor app doc (MONITOR_MONITORS setting)\n'
                    'AUTOUPDATE: %s' % autoupdate_status)
            if version <= 1102:
                # Handle InconsistentMigrationHistory on tinc app
                # * v0.11.2 tinc includes 0022, 0028..0030
                # * v0.11.3 tinc adds 0023..0027
                # We can force south to merge migrations because
                # are implemented to be runned without dependencies
                run('python manage.py migrate tinc 0030 --merge --noinput')

        if not options.get('specifics_only'):
            # Common stuff
            development = options.get('development')
            controller_admin = os.path.join(os.path.dirname(__file__),
                                            '../../bin/')
            controller_admin = os.path.join(controller_admin,
                                            'controller-admin.sh')
            run('chmod +x %s' % controller_admin)

            extra = '--development' if development else ''
            if options.get('proxy'):
                extra += ' --proxy %s' % options.get('proxy')
            run("%s install_requirements " % controller_admin + extra)
            run("python manage.py collectstatic --noinput")

            run("python manage.py syncdb --noinput")
            run("python manage.py migrate --noinput")
            if is_installed('firmware'):
                run("python manage.py syncfirmwareplugins")
            if is_installed('notifications'):
                run("python manage.py syncnotifications")
            if is_installed('resources'):
                run("python manage.py syncresources")
            if options.get('restart'):
                run("python manage.py restartservices")

        if not version:
            self.stderr.write(
                '\nNext time you migth want to provide a --from argument '
                'in order to run version specific upgrade operations\n')
            return

        # Post-upgrade operations (version specific)
        if version <= 629:
            # Clean existing sessions because of change on auth backend
            run('echo "delete from django_session;" | python manage.py dbshell'
                )
        if version < 801:
            deprecate_periodic_tasks(('state.ping', ))
        if version < 809:
            # Add PKI directories
            from pki import ca
            from controller.utils.paths import get_site_root
            site_root = get_site_root()
            username = run("stat -c %%U %s" % site_root)
            get_dir = lambda f: os.path.dirname(getattr(ca, f + '_path'))
            for d in set(get_dir(f) for f in ['priv_key', 'pub_key', 'cert']):
                run('mkdir -p %s' % d)
                run('chown %s %s' % (username, d))
            upgrade_notes.append(
                'HTTPS certificate support for the management '
                'network has been introduced in version 0.8.9.\n'
                'In order to use it you sould run:\n'
                '  > python manage.py setuppki\n'
                '  > sudo python manage.py setupapache\n')
        if version < 838:
            # Purge communitynetworks.periodic_cache_node_db
            from djcelery.models import PeriodicTask
            PeriodicTask.objects.filter(
                name='communitynetworks.periodic_cache_node_db').delete()
            run('rabbitmqctl stop_app')
            run('rabbitmqctl reset')
            run('rabbitmqctl start_app')
            run('service celeryd restart')
            upgrade_notes.append(
                'New Celeryd init.d configuration has been '
                'introduced in 0.8.38.\nIt is strongly recommended to upgrade by\n'
                '  > sudo python manage.py setupceleryd\n')
            # Deprecate x86 and amd64 architectures
            from nodes.models import Node
            Node.objects.filter(arch='x86').update(arch='i686')
            Node.objects.filter(arch='amd64').update(arch='x86_64')
            upgrade_notes.append(
                'In order to support Base authentication while downloading '
                'firmwares you should add "WSGIPassAuthorization On" on your apache config.\n'
                'Alternatively you can perform this operation with the following command\n'
                '  > sudo python manage.py setupapache\n'
                '  > /etc/init.d/apache2 reload\n')
        if version < 900:
            upgrade_notes.append(
                'Apache configuration is now placed under '
                '/etc/apache2/conf.d/<project_name>.conf. It is convenient for you '
                'to migrate your current configuration located on /etc/apache2/httpd.conf '
                'to this new location.\n')
            upgrade_notes.append(
                'Celery workers configuration has been updated. '
                'Please update it by running:\n'
                '  > sudo python manage.py setupceleryd\n')
        if version < 905:
            # TODO find the root cause of this
            # maybe is shit imported on settings that import settings like add_app
            # Prevent crazy import erros to appear :S
            from django.utils import translation
            translation.activate('en-us')
            # Change template types for more generic ones
            from slices.models import Template
            from slices.settings import SLICES_TEMPLATE_TYPES
            template_types = [t[0] for t in SLICES_TEMPLATE_TYPES]
            if 'debian' in template_types:
                Template.objects.filter(type='debian6').update(type='debian')
            if 'openwrt' in template_types:
                Template.objects.filter(type='openwrt-backfire').update(
                    type='openwrt')
        if version < 906:
            deprecate_periodic_tasks(('state.nodestate', 'state.sliverstate'))
        if version <= 907:
            # Generate sha256
            from slices.models import Template
            for template in Template.objects.all():
                template.save()
            upgrade_notes.append(
                "It is extremly recommended to update your database "
                "settings to enable atomic request behaviour:\n"
                "  https://docs.djangoproject.com/en/dev/topics/db/transactions/#tying-transactions-to-http-requests\n"
                "Just add:\n"
                "   'ATOMIC_REQUESTS': True,\n"
                "into DATABASES setting within <project_name>/<project_name>/settings.py"
            )
        if version <= 1003:
            # Update firmware configuration after Island refactor (#264)
            from firmware.models import ConfigFile
            try:
                cfg_file = ConfigFile.objects.get(
                    path__contains="node.tinc.connect_to")
            except (ConfigFile.DoesNotExist,
                    ConfigFile.MultipleObjectsReturned):
                # Warn the user that needs to perform manual update
                msg = "Firmware configuration update has failed. "
            else:
                cfg_file.content = cfg_file.content.replace(
                    "node.tinc.island", "node.island")
                cfg_file.save()
                msg = "Firmware configuration updated successfully. Updated ConfigFile ID: %i." % cfg_file.pk
            upgrade_notes.append(
                "%s\nPlease check version 0.10.4 release notes:\n"
                "https://wiki.confine-project.eu/soft:server-release-notes#section0104"
                % msg)
        if version < 1103:
            # Update mgmt network Server APIs certificate
            # perform raw SQL querie because models hasn't been
            # reloaded yet and cannot access e.g. server.api
            from django.db import connection
            from nodes.models import Server
            from pki import ca

            server_id = Server.objects.order_by('id').first().pk
            try:
                cert = ca.get_cert().as_pem()
            except IOError:
                msg = ("Failed to update Server APIs certificate. Missing "
                       "server certificate '%s'.\n"
                       "Run 'python manage.py setuppki --help'" % ca.cert_path)
                upgrade_notes.append(msg)
            else:
                update_sql = ('UPDATE "nodes_serverapi" SET cert = %s '
                              'WHERE "nodes_serverapi"."server_id" = %s')
                cursor = connection.cursor()
                cursor.execute(update_sql, [cert, server_id])
                del cursor
                upgrade_notes.append("Updated Server APIs certificate.")

        if upgrade_notes and options.get('print_upgrade_notes'):
            self.stdout.write('\n\033[1m\n'
                              '    ===================\n'
                              '    ** UPGRADE NOTES **\n'
                              '    ===================\n\n' +
                              '\n'.join(upgrade_notes) + '\033[m\n')