Exemple #1
0
def setup_profile(profile,
                  only_config,
                  set_default=False,
                  non_interactive=False,
                  **kwargs):
    """
    Setup an AiiDA profile and AiiDA user (and the AiiDA default user).

    :param profile: Profile name
    :param only_config: do not create a new user
    :param set_default: set the new profile as the default
    :param non_interactive: do not prompt for configuration values, fail if not all values are given as kwargs.
    :param backend: one of 'django', 'sqlalchemy'
    :param email: valid email address for the user
    :param db_host: hostname for the database
    :param db_port: port to connect to the database
    :param db_user: name of the db user
    :param db_pass: password of the db user
    """
    from aiida.backends import settings
    from aiida.backends.profile import BACKEND_SQLA, BACKEND_DJANGO
    from aiida.backends.utils import set_backend_type
    from aiida.cmdline import EXECNAME
    from aiida.cmdline.commands import cmd_user
    from aiida.common.exceptions import InvalidOperation
    from aiida.common.setup import (create_base_dirs, create_configuration,
                                    set_default_profile, DEFAULT_UMASK,
                                    create_config_noninteractive)

    only_user_config = only_config

    # Create the directories to store the configuration files
    create_base_dirs()
    if settings.AIIDADB_PROFILE and profile:
        sys.exit(
            'the profile argument cannot be used if verdi is called with -p option: {} and {}'
            .format(settings.AIIDADB_PROFILE, profile))
    gprofile = settings.AIIDADB_PROFILE or profile
    if gprofile == profile:
        settings.AIIDADB_PROFILE = profile
    if not settings.AIIDADB_PROFILE:
        settings.AIIDADB_PROFILE = 'default'

    # used internally later
    gprofile = settings.AIIDADB_PROFILE

    created_conf = None
    # ask and store the configuration of the DB
    if non_interactive:
        try:
            created_conf = create_config_noninteractive(
                profile=gprofile,
                backend=kwargs['backend'],
                email=kwargs['email'],
                db_host=kwargs['db_host'],
                db_port=kwargs['db_port'],
                db_name=kwargs['db_name'],
                db_user=kwargs['db_user'],
                db_pass=kwargs.get('db_pass', ''),
                repo=kwargs['repo'],
                force_overwrite=kwargs.get('force_overwrite', False))
        except ValueError as exception:
            click.echo("Error during configuation: {}".format(
                exception.message),
                       err=True)
            sys.exit(1)
        except KeyError as exception:
            import traceback
            click.echo(traceback.format_exc())
            click.echo(
                "--non-interactive requires all values to be given on the commandline! Missing argument: {}"
                .format(exception.message),
                err=True)
            sys.exit(1)
    else:
        try:
            created_conf = create_configuration(profile=gprofile)
        except ValueError as exception:
            print >> sys.stderr, "Error during configuration: {}".format(
                exception.message)
            sys.exit(1)

        # Set default DB profile
        set_default_profile(gprofile, force_rewrite=False)

    if only_user_config:
        print("Only user configuration requested, "
              "skipping the migrate command")
    else:
        print("Executing now a migrate command...")

        backend_choice = created_conf['AIIDADB_BACKEND']
        if backend_choice == BACKEND_DJANGO:
            print("...for Django backend")
            # The correct profile is selected within load_dbenv.
            # Setting os.umask here since sqlite database gets created in
            # this step.
            old_umask = os.umask(DEFAULT_UMASK)

            # This check should be done more properly
            # try:
            #     backend_type = get_backend_type()
            # except KeyError:
            #     backend_type = None
            #
            # if backend_type is not None and backend_type != BACKEND_DJANGO:
            #     raise InvalidOperation("An already existing database found"
            #                            "and a different than the selected"
            #                            "backend was used for its "
            #                            "management.")

            try:
                from aiida.backends.djsite.utils import pass_to_django_manage
                pass_to_django_manage([EXECNAME, 'migrate'], profile=gprofile)
            finally:
                os.umask(old_umask)

            set_backend_type(BACKEND_DJANGO)

        elif backend_choice == BACKEND_SQLA:
            print("...for SQLAlchemy backend")
            from aiida import is_dbenv_loaded
            from aiida.backends.sqlalchemy.utils import _load_dbenv_noschemacheck, check_schema_version
            from aiida.backends.profile import load_profile

            # We avoid calling load_dbenv since we want to force the schema
            # migration
            if not is_dbenv_loaded():
                settings.LOAD_DBENV_CALLED = True
                # This is going to set global variables in settings, including settings.BACKEND
                load_profile()
                _load_dbenv_noschemacheck()

            # Perform the needed migration quietly
            check_schema_version(force_migration=True)
            set_backend_type(BACKEND_SQLA)

        else:
            raise InvalidOperation("Not supported backend selected.")

    print("Database was created successfully")

    # I create here the default user
    print("Loading new environment...")
    if only_user_config:
        from aiida.backends.utils import load_dbenv, is_dbenv_loaded
        # db environment has not been loaded in this case
        if not is_dbenv_loaded():
            load_dbenv()

    from aiida.common.setup import DEFAULT_AIIDA_USER
    from aiida.orm.backend import construct_backend

    backend = construct_backend()
    if not backend.users.find(email=DEFAULT_AIIDA_USER):
        print("Installing default AiiDA user...")
        nuser = backend.users.create(email=DEFAULT_AIIDA_USER,
                                     first_name="AiiDA",
                                     last_name="Daemon")
        nuser.is_active = True
        nuser.store()

    from aiida.common.utils import get_configured_user_email
    email = get_configured_user_email()
    print("Starting user configuration for {}...".format(email))
    if email == DEFAULT_AIIDA_USER:
        print("You set up AiiDA using the default Daemon email ({}),".format(
            email))
        print("therefore no further user configuration will be asked.")
    else:
        if non_interactive:
            # Here we map the keyword arguments onto the command line arguments
            # for verdi user configure.  We have to be careful that there the
            # argument names are the same as those int he kwargs dict
            commands = [kwargs['email'], '--non-interactive']

            for arg in ('first_name', 'last_name', 'institution'):
                value = kwargs.get(arg, None)
                if value is not None:
                    commands.extend(
                        ('--{}'.format(arg.replace('_', '-')), str(value)))
        else:
            commands = [email]

        # Ask to configure the user
        try:
            # pylint: disable=no-value-for-parameter
            cmd_user.configure(commands)
        except SystemExit:
            # Have to catch this as the configure command will do a sys.exit()
            pass

    if set_default:
        set_default_profile(profile, force_rewrite=True)

    print("Setup finished.")
Exemple #2
0
def setup(profile, only_config, non_interactive=False, **kwargs):
    '''
    setup an aiida profile and aiida user (and the aiida default user).

    :param profile: Profile name
    :param only_config: do not create a new user
    :param non_interactive: do not prompt for configuration values, fail if not all values are given as kwargs.
    :param backend: one of 'django', 'sqlalchemy'
    :param email: valid email address for the user
    :param db_host: hostname for the database
    :param db_port: port to connect to the database
    :param db_user: name of the db user
    :param db_pass: password of the db user
    '''
    from aiida.common.setup import (create_base_dirs, create_configuration,
                                    set_default_profile, DEFAULT_UMASK,
                                    create_config_noninteractive)
    from aiida.backends.profile import BACKEND_SQLA, BACKEND_DJANGO
    from aiida.backends.utils import set_backend_type
    from aiida.common.exceptions import InvalidOperation

    # ~ cmdline_args = list(args)

    # ~ only_user_config = False
    # ~ try:
    # ~ cmdline_args.remove('--only-config')
    # ~ only_user_config = True
    # ~ except ValueError:
    # ~ # Parameter not provided
    # ~ pass
    only_user_config = only_config

    # ~ if cmdline_args:
    # ~ print >> sys.stderr, "Unknown parameters on the command line: "
    # ~ print >> sys.stderr, ", ".join(cmdline_args)
    # ~ sys.exit(1)

    # create the directories to store the configuration files
    create_base_dirs()
    # gprofile = 'default' if profile is None else profile
    # ~ gprofile = profile if settings_profile.AIIDADB_PROFILE is None \
    # ~ else settings_profile.AIIDADB_PROFILE
    if settings_profile.AIIDADB_PROFILE and profile:
        sys.exit(
            'the profile argument cannot be used if verdi is called with -p option: {} and {}'
            .format(settings_profile.AIIDADB_PROFILE, profile))
    gprofile = settings_profile.AIIDADB_PROFILE or profile
    if gprofile == profile:
        settings_profile.AIIDADB_PROFILE = profile
    if not settings_profile.AIIDADB_PROFILE:
        settings_profile.AIIDADB_PROFILE = 'default'

    # used internally later
    gprofile = settings_profile.AIIDADB_PROFILE

    created_conf = None
    # ask and store the configuration of the DB
    if non_interactive:
        try:
            created_conf = create_config_noninteractive(
                profile=gprofile,
                backend=kwargs['backend'],
                email=kwargs['email'],
                db_host=kwargs['db_host'],
                db_port=kwargs['db_port'],
                db_name=kwargs['db_name'],
                db_user=kwargs['db_user'],
                db_pass=kwargs.get('db_pass', ''),
                repo=kwargs['repo'],
                force_overwrite=kwargs.get('force_overwrite', False))
        except ValueError as e:
            click.echo("Error during configuation: {}".format(e.message),
                       err=True)
            sys.exit(1)
        except KeyError as e:
            click.echo(
                "--non-interactive requires all values to be given on the commandline! Missing argument: {}"
                .format(e.message),
                err=True)
            sys.exit(1)
    else:
        try:
            created_conf = create_configuration(profile=gprofile)
        except ValueError as e:
            print >> sys.stderr, "Error during configuration: {}".format(
                e.message)
            sys.exit(1)

        # set default DB profiles
        set_default_profile('verdi', gprofile, force_rewrite=False)
        set_default_profile('daemon', gprofile, force_rewrite=False)

    if only_user_config:
        print(
            "Only user configuration requested, "
            "skipping the migrate command")
    else:
        print "Executing now a migrate command..."

        backend_choice = created_conf['AIIDADB_BACKEND']
        if backend_choice == BACKEND_DJANGO:
            print("...for Django backend")
            # The correct profile is selected within load_dbenv.
            # Setting os.umask here since sqlite database gets created in
            # this step.
            old_umask = os.umask(DEFAULT_UMASK)

            # This check should be done more properly
            # try:
            #     backend_type = get_backend_type()
            # except KeyError:
            #     backend_type = None
            #
            # if backend_type is not None and backend_type != BACKEND_DJANGO:
            #     raise InvalidOperation("An already existing database found"
            #                            "and a different than the selected"
            #                            "backend was used for its "
            #                            "management.")

            try:
                pass_to_django_manage([execname, 'migrate'], profile=gprofile)
            finally:
                os.umask(old_umask)

            set_backend_type(BACKEND_DJANGO)

        elif backend_choice == BACKEND_SQLA:
            print("...for SQLAlchemy backend")
            from aiida import is_dbenv_loaded
            from aiida.backends import settings
            from aiida.backends.sqlalchemy.utils import (
                _load_dbenv_noschemacheck, check_schema_version)
            from aiida.backends.profile import load_profile

            # We avoid calling load_dbenv since we want to force the schema
            # migration
            if not is_dbenv_loaded():
                settings.LOAD_DBENV_CALLED = True
                # This is going to set global variables in settings, including
                # settings.BACKEND
                load_profile()
                _load_dbenv_noschemacheck()

            # Perform the needed migration quietly
            check_schema_version(force_migration=True)
            set_backend_type(BACKEND_SQLA)

        else:
            raise InvalidOperation("Not supported backend selected.")

    print "Database was created successfully"

    # I create here the default user
    print "Loading new environment..."
    if only_user_config:
        from aiida.backends.utils import load_dbenv, is_dbenv_loaded
        # db environment has not been loaded in this case
        if not is_dbenv_loaded():
            load_dbenv()

    from aiida.common.setup import DEFAULT_AIIDA_USER
    from aiida.orm.user import User as AiiDAUser

    if not AiiDAUser.search_for_users(email=DEFAULT_AIIDA_USER):
        print "Installing default AiiDA user..."
        nuser = AiiDAUser(email=DEFAULT_AIIDA_USER)
        nuser.first_name = "AiiDA"
        nuser.last_name = "Daemon"
        nuser.is_staff = True
        nuser.is_active = True
        nuser.is_superuser = True
        nuser.force_save()

    from aiida.common.utils import get_configured_user_email
    email = get_configured_user_email()
    print "Starting user configuration for {}...".format(email)
    if email == DEFAULT_AIIDA_USER:
        print "You set up AiiDA using the default Daemon email ({}),".format(
            email)
        print "therefore no further user configuration will be asked."
    else:
        # Ask to configure the new user
        if not non_interactive:
            user.configure.main(args=[email])
        else:
            # or don't ask
            aiida.cmdline.commands.user.do_configure(
                email=kwargs['email'],
                first_name=kwargs.get('first_name'),
                last_name=kwargs.get('last_name'),
                institution=kwargs.get('institution'),
                no_password=True,
                non_interactive=non_interactive,
                force=True)

    print "Setup finished."
Exemple #3
0
    def test_migrations_forward_backward(self):
        """
        This is a very broad test that checks that the migration mechanism
        works. More specifically, it checks that:
        - Alembic database migrations to specific versions work (upgrade &
          downgrade)
        - The methods that are checking the database schema version and perform
          the migration procedure to the last version work correctly.
        """
        from aiida.backends.sqlalchemy.tests.migration_test import versions
        from aiida.backends.sqlalchemy.utils import check_schema_version

        try:
            # Constructing the versions directory
            versions_dpath = os.path.join(os.path.dirname(versions.__file__))

            # Setting dynamically the the path to the alembic configuration
            # (this is where the env.py file can be found)
            alembic_cfg = Config()
            alembic_cfg.set_main_option('script_location', self.alembic_dpath)
            # Setting dynamically the versions directory. These are the
            # migration scripts to pass from one version to the other. The
            # default ones are overridden with test-specific migrations.
            alembic_cfg.set_main_option('version_locations', versions_dpath)

            # Using the connection initialized by the tests
            with sa.engine.begin() as connection:
                alembic_cfg.attributes['connection'] = connection

                self.assertIsNone(
                    get_db_schema_version(alembic_cfg),
                    "The initial database version should be "
                    "None (no version) since the test setUp "
                    "method should undo all migrations")
            # Migrate the database to the latest version
            check_schema_version(force_migration=True, alembic_cfg=alembic_cfg)
            with sa.engine.begin() as connection:
                alembic_cfg.attributes['connection'] = connection
                self.assertEquals(
                    get_db_schema_version(alembic_cfg),
                    get_migration_head(alembic_cfg),
                    "The latest database version is not the "
                    "expected one.")
            with sa.engine.begin() as connection:
                alembic_cfg.attributes['connection'] = connection
                # Migrating the database to the base version
                command.downgrade(alembic_cfg, "base")
                self.assertIsNone(
                    get_db_schema_version(alembic_cfg),
                    "The database version is not the expected "
                    "one. It should be None (initial).")

        except Exception as test_ex:
            # If there is an exception, clean the alembic related tables
            from sqlalchemy.engine import reflection

            # Getting the current database table names
            inspector = reflection.Inspector.from_engine(
                sa.get_scoped_session().bind)
            db_table_names = set(inspector.get_table_names())
            # The alembic related database names
            alemb_table_names = set(['account', 'alembic_version'])

            # Get the intersection of the above tables
            tables_to_drop = set.intersection(db_table_names,
                                              alemb_table_names)
            # Delete only the tables that exist
            for table in tables_to_drop:
                from psycopg2 import ProgrammingError
                from sqlalchemy.orm import sessionmaker, scoped_session
                try:
                    with sa.engine.begin() as connection:
                        connection.execute('DROP TABLE {};'.format(table))
                except Exception as db_ex:
                    print("The following error occured during the cleaning of"
                          "the database: {}".format(db_ex.message))
            # Since the database cleaning is over, raise the test
            # exception that was caught
            raise test_ex