# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
import sys
from aiida.cmdline.baseclass import VerdiCommandWithSubcommands
from aiida.backends.utils import load_dbenv, is_dbenv_loaded
from aiida.common.exceptions import ValidationError
[docs]def prompt_for_computer_configuration(computer):
import inspect, readline
from aiida.orm.computer import Computer as Computer
from aiida.common.exceptions import ValidationError
for internal_name, name, desc, multiline in (
Computer._conf_attributes):
# Check if I should skip this entry
shouldcall_name = '_shouldcall_{}'.format(internal_name)
try:
shouldcallfunc = dict(inspect.getmembers(
computer))[shouldcall_name]
shouldcall = shouldcallfunc()
except KeyError:
shouldcall = True
if not shouldcall:
# Call cleanup code, if present
cleanup_name = '_cleanup_{}'.format(internal_name)
try:
cleanup = dict(inspect.getmembers(
computer))[cleanup_name]
cleanup()
except KeyError:
# No cleanup function: this is not a problem, simply
# no cleanup is needed
pass
# Skip this question
continue
getter_name = '_get_{}_string'.format(internal_name)
try:
getter = dict(inspect.getmembers(
computer))[getter_name]
except KeyError:
print >> sys.stderr, ("Internal error! "
"No {} getter defined in Computer".format(getter_name))
sys.exit(1)
previous_value = getter()
setter_name = '_set_{}_string'.format(internal_name)
try:
setter = dict(inspect.getmembers(
computer))[setter_name]
except KeyError:
print >> sys.stderr, ("Internal error! "
"No {} setter defined in Computer".format(setter_name))
sys.exit(1)
valid_input = False
while not valid_input:
if multiline:
newlines = []
print "=> {}: ".format(name)
print " # This is a multiline input, press CTRL+D on a"
print " # empty line when you finish"
try:
for l in previous_value.splitlines():
while True:
readline.set_startup_hook(lambda:
readline.insert_text(l))
input_txt = raw_input()
if input_txt.strip() == '?':
print [" > {}".format(descl) for descl
in "HELP: {}".format(desc).split('\n')]
continue
else:
newlines.append(input_txt)
break
# Reset the hook (no default text printed)
readline.set_startup_hook()
print " # ------------------------------------------"
print " # End of old input. You can keep adding "
print " # lines, or press CTRL+D to store this value"
print " # ------------------------------------------"
while True:
input_txt = raw_input()
if input_txt.strip() == '?':
print "\n".join([" > {}".format(descl) for descl
in "HELP: {}".format(desc).split('\n')])
continue
else:
newlines.append(input_txt)
except EOFError:
# Ctrl+D pressed: end of input.
pass
input_txt = "\n".join(newlines)
else: # No multiline
readline.set_startup_hook(lambda: readline.insert_text(
previous_value))
input_txt = raw_input("=> {}: ".format(name))
if input_txt.strip() == '?':
print "HELP:", desc
continue
try:
setter(input_txt)
valid_input = True
except ValidationError as e:
print >> sys.stderr, "Invalid input: {}".format(e.message)
print >> sys.stderr, "Enter '?' for help".format(e.message)
[docs]class Computer(VerdiCommandWithSubcommands):
"""
Setup and manage computers to be used
This command allows to list, add, modify and configure computers.
"""
[docs] def __init__(self):
"""
A dictionary with valid commands and functions to be called.
"""
self.valid_subcommands = {
'list': (self.computer_list, self.complete_none),
'show': (self.computer_show, self.complete_computers),
'setup': (self.computer_setup, self.complete_none),
'update': (self.computer_update, self.complete_computers),
'enable': (self.computer_enable, self.complete_computers),
'disable': (self.computer_disable, self.complete_computers),
'rename': (self.computer_rename, self.complete_computers),
'configure': (self.computer_configure, self.complete_computers),
'test': (self.computer_test, self.complete_computers),
'delete': (self.computer_delete, self.complete_computers),
}
[docs] def complete_computers(self, subargs_idx, subargs):
if not is_dbenv_loaded():
load_dbenv()
computer_names = self.get_computer_names()
print computer_names
return "\n".join(computer_names)
[docs] def computer_list(self, *args):
"""
List available computers
"""
import argparse
if not is_dbenv_loaded():
load_dbenv()
from aiida.orm.computer import Computer as AiiDAOrmComputer
from aiida.backends.utils import get_automatic_user
parser = argparse.ArgumentParser(
prog=self.get_full_command_name(),
description='List the computers in the database.')
# The default states are those that are shown if no option is given
parser.add_argument('-C', '--color', action='store_true',
help="Use colors to help visualizing the different categories",
)
parser.add_argument('-o', '--only-usable', action='store_true',
help="Show only computers that are usable (i.e., "
"configured for the given user and enabled)",
)
parser.add_argument('-p', '--parsable', action='store_true',
help="Show only the computer names, one per line, "
"without any other information or string.",
)
parser.add_argument('-a', '--all', action='store_true',
help="Show also disabled or unconfigured computers",
)
parser.set_defaults(also_disabled=False)
parsed_args = parser.parse_args(args)
use_colors = parsed_args.color
only_usable = parsed_args.only_usable
parsable = parsed_args.parsable
all_comps = parsed_args.all
computer_names = self.get_computer_names()
if use_colors:
color_id = 90 # Dark gray
color_id = None # Default color
if color_id is not None:
start_color = "\x1b[{}m".format(color_id)
end_color = "\x1b[0m"
else:
start_color = ""
end_color = ""
else:
start_color = ""
end_color = ""
if not parsable:
print "{}# List of configured computers:{}".format(
start_color, end_color)
print ("{}# (use 'verdi computer show COMPUTERNAME' "
"to see the details){}".format(start_color, end_color))
if computer_names:
for name in sorted(computer_names):
computer = AiiDAOrmComputer.get(name)
# color_id = 90 # Dark gray
# color_id = 34 # Blue
is_configured = computer.is_user_configured(get_automatic_user())
is_user_enabled = computer.is_user_enabled(get_automatic_user())
is_usable = False # True if both enabled and configured
if not all_comps:
if not is_configured or not is_user_enabled or not computer.is_enabled():
continue
if computer.is_enabled():
if is_configured:
configured_str = ""
if is_user_enabled:
symbol = "*"
color_id = None
enabled_str = ""
is_usable = True
else:
symbol = "x"
color_id = 31 # Red
enabled_str = "[DISABLED for this user]"
else:
symbol = "x"
color_id = 90 # Dark gray
enabled_str = ""
configured_str = " [unconfigured]"
else: # GLOBALLY DISABLED
symbol = "x"
color_id = 31 # Red
if is_configured and not is_user_enabled:
enabled_str = " [DISABLED globally AND for this user]"
else:
enabled_str = " [DISABLED globally]"
if is_configured:
configured_str = ""
else:
configured_str = " [unconfigured]"
if use_colors:
if color_id is not None:
start_color = "\x1b[{}m".format(color_id)
bold_sequence = "\x1b[1;{}m".format(color_id)
nobold_sequence = "\x1b[0;{}m".format(color_id)
else:
start_color = "\x1b[0m"
bold_sequence = "\x1b[1m"
nobold_sequence = "\x1b[0m"
end_color = "\x1b[0m"
else:
start_color = ""
end_color = ""
bold_sequence = ""
nobold_sequence = ""
if parsable:
print "{}{}{}".format(start_color, name, end_color)
else:
if (not only_usable) or is_usable:
print "{}{} {}{}{} {}{}{}".format(
start_color, symbol,
bold_sequence, name, nobold_sequence,
enabled_str, configured_str, end_color)
else:
print "# No computers configured yet. Use 'verdi computer setup'"
[docs] def computer_show(self, *args):
"""
Show information on a given computer
"""
if not is_dbenv_loaded():
load_dbenv()
from aiida.common.exceptions import NotExistent
if len(args) != 1:
print >> sys.stderr, ("after 'computer show' there should be one "
"argument only, being the computer name.")
sys.exit(1)
try:
computer = self.get_computer(name=args[0])
except NotExistent:
print >> sys.stderr, "No computer in the DB with name {}.".format(
args[0])
sys.exit(1)
print computer.full_text_info
[docs] def computer_update(self, *args):
"""
Update an existing computer
"""
import argparse
from aiida.common.exceptions import NotExistent
if not is_dbenv_loaded():
load_dbenv()
# from aiida.backends.djsite.db.models import DbNode
from aiida.orm.computer import Computer
parser = argparse.ArgumentParser(
prog=self.get_full_command_name(),
description='Update a computer')
# The default states are those that are shown if no option is given
parser.add_argument('computer_name', help="The name of the computer")
parsed_args = parser.parse_args(args)
computer_name = parsed_args.computer_name
try:
computer = Computer.get(computer_name)
except NotExistent:
print "No computer {} was found".format(computer_name)
sys.exit(1)
calculation_on_computer = computer.get_calculations_on_computer()
if calculation_on_computer:
# Note: this is an artificial comment.
# If you comment the following lines, you will be able to overwrite
# the old computer anyway, at your own risk.
print "You cannot modify a computer, after you run some calculations on it."
print "Disable this computer and set up a new one."
sys.exit(1)
print "*" * 75
print "WARNING! Modifying existing computer with name '{}'".format(computer_name)
print "Are you sure you want to continue? The UUID will remain the same!"
print "Continue only if you know what you are doing."
print "If you just want to rename a computer, use the 'verdi computer rename'"
print "command. In most cases, it is better to create a new computer."
print "Moreover, if you change the transport, you must also reconfigure"
print "each computer for each user!"
print "*" * 75
print "Press [Enter] to continue, or [Ctrl]+C to exit."
raw_input()
prompt_for_computer_configuration(computer)
try:
computer.store()
except ValidationError as e:
print "Unable to store the computer: {}. Exiting...".format(e.message)
sys.exit(1)
print "Computer '{}' successfully updated.".format(computer_name)
print "pk: {}, uuid: {}".format(computer.pk, computer.uuid)
print "(Note: machine_dependent transport parameters cannot be set via "
print "the command-line interface at the moment)"
print "OK"
pass
[docs] def computer_setup(self, *args):
"""
Setup a new or existing computer
"""
import readline
if len(args) != 0:
print >> sys.stderr, ("after 'computer setup' there cannot be any "
"argument.")
sys.exit(1)
if not is_dbenv_loaded():
load_dbenv()
from aiida.common.exceptions import NotExistent, ValidationError
from aiida.orm.computer import Computer as AiidaOrmComputer
print "At any prompt, type ? to get some help."
print "---------------------------------------"
# get the new computer name
readline.set_startup_hook(lambda: readline.insert_text(previous_value))
input_txt = raw_input("=> Computer name: ")
if input_txt.strip() == '?':
print "HELP:", "The computer name"
computer_name = input_txt.strip()
try:
computer = self.get_computer(name=computer_name)
print "A computer called {} already exists.".format(computer_name)
print "Use 'verdi computer update' to update it, and be careful if"
print "you really want to modify a database entry!"
print "Now exiting..."
sys.exit(1)
except NotExistent:
computer = AiidaOrmComputer(name=computer_name)
print "Creating new computer with name '{}'".format(computer_name)
prompt_for_computer_configuration(computer)
try:
computer.store()
except ValidationError as e:
print "Unable to store the computer: {}. Exiting...".format(e.message)
sys.exit(1)
print "Computer '{}' successfully stored in DB.".format(computer_name)
print "pk: {}, uuid: {}".format(computer.pk, computer.uuid)
print "Note: before using it with AiiDA, configure it using the command"
print " verdi computer configure {}".format(computer_name)
print "(Note: machine_dependent transport parameters cannot be set via "
print "the command-line interface at the moment)"
[docs] def computer_rename(self, *args):
"""
Rename a computer
"""
if not is_dbenv_loaded():
load_dbenv()
from aiida.common.exceptions import (
NotExistent, UniquenessError, ValidationError)
try:
oldname = args[0]
newname = args[1]
except IndexError:
print >> sys.stderr, "Pass as parameters the old and the new name"
sys.exit(1)
if oldname == newname:
print >> sys.stderr, "The initial and final name are the same."
sys.exit(1)
try:
computer = self.get_computer(name=oldname)
except NotExistent:
print >> sys.stderr, "No computer exists with name '{}'".format(
oldname)
sys.exit(1)
try:
computer.set_name(newname)
computer.store()
except ValidationError as e:
print >> sys.stderr, "Invalid input! {}".format(e.message)
sys.exit(1)
except UniquenessError as e:
print >> sys.stderr, ("Uniqueness error encountered! Probably a "
"computer with name '{}' already exists"
"".format(newname))
print >> sys.stderr, "(Message was: {})".format(e.message)
sys.exit(1)
print "Computer '{}' renamed to '{}'".format(oldname, newname)
[docs] def computer_delete(self, *args):
"""
Configure the authentication information for a given computer
Does not delete the computer if there are calculations that are using
it.
"""
if not is_dbenv_loaded():
load_dbenv()
from aiida.common.exceptions import (
NotExistent, InvalidOperation)
from aiida.orm.computer import delete_computer
if len(args) != 1:
print >> sys.stderr, ("after 'computer delete' there should be one "
"argument only, being the computer name.")
sys.exit(1)
computername = args[0]
try:
computer = self.get_computer(name=computername)
except NotExistent:
print >> sys.stderr, "No computer exists with name '{}'".format(
computername)
sys.exit(1)
try:
delete_computer(computer)
except InvalidOperation as e:
print >> sys.stderr, e.message
sys.exit(1)
print "Computer '{}' deleted.".format(computername)
[docs] def computer_test(self, *args):
"""
Test the connection to a computer.
It tries to connect, to get the list of calculations on the queue and
to perform other tests.
"""
import argparse
import traceback
if not is_dbenv_loaded():
load_dbenv()
from django.core.exceptions import ObjectDoesNotExist
from aiida.common.exceptions import NotExistent
from aiida.orm.user import User
from aiida.backends.utils import get_automatic_user
from aiida.orm.computer import Computer as OrmComputer
parser = argparse.ArgumentParser(
prog=self.get_full_command_name(),
description='Test a remote computer')
# The default states are those that are shown if no option is given
parser.add_argument('-u', '--user', type=str, metavar='EMAIL',
dest='user_email',
help="Test the connection for a given AiiDA user."
"If not specified, uses the current "
"default user.",
)
parser.add_argument('-t', '--traceback', action='store_true',
help="Print the full traceback in case an exception "
"is raised",
)
parser.add_argument('computer', type=str,
help="The name of the computer that you "
"want to test")
parsed_args = parser.parse_args(args)
user_email = parsed_args.user_email
computername = parsed_args.computer
print_traceback = parsed_args.traceback
try:
computer = self.get_computer(name=computername)
except NotExistent:
print >> sys.stderr, "No computer exists with name '{}'".format(
computername)
sys.exit(1)
if user_email is None:
user = User(dbuser=get_automatic_user())
else:
user_list = User.search_for_users(email=user_email)
# If no user is found
if not user_list:
print >> sys.stderr, ("No user with email '{}' in the "
"database.".format(user_email))
sys.exit(1)
user = user_list[0]
print "Testing computer '{}' for user {}...".format(computername,
user.email)
try:
dbauthinfo = computer.get_dbauthinfo(user._dbuser)
except NotExistent:
print >> sys.stderr, ("User with email '{}' is not yet configured "
"for computer '{}' yet.".format(
user.email, computername))
sys.exit(1)
warning_string = None
if not dbauthinfo.enabled:
warning_string = ("** NOTE! Computer is disabled for the "
"specified user!\n Do you really want to test it? [y/N] ")
if not computer.is_enabled():
warning_string = ("** NOTE! Computer is disabled!\n"
" Do you really want to test it? [y/N] ")
if warning_string:
answer = raw_input(warning_string)
if not (answer == 'y' or answer == 'Y'):
sys.exit(0)
s = OrmComputer(dbcomputer=dbauthinfo.dbcomputer).get_scheduler()
t = dbauthinfo.get_transport()
## STARTING TESTS HERE
num_failures = 0
num_tests = 0
try:
print "> Testing connection..."
with t:
s.set_transport(t)
num_tests += 1
for test in [self._computer_test_get_jobs,
self._computer_create_temp_file]:
num_tests += 1
try:
succeeded = test(transport=t, scheduler=s,
dbauthinfo=dbauthinfo)
except Exception as e:
print "* The test raised an exception!"
if print_traceback:
print "** Full traceback:"
# Indent
print "\n".join([" {}".format(l) for l
in traceback.format_exc().splitlines()])
else:
print "** {}: {}".format(e.__class__.__name__,
e.message)
print ("** (use the --traceback option to see the "
"full traceback)")
succeeded = False
if not succeeded:
num_failures += 1
if num_failures:
print "Some tests failed! ({} out of {} failed)".format(
num_failures, num_tests)
else:
print "Test completed (all {} tests succeeded)".format(
num_tests)
except Exception as e:
print "** Error while trying to connect to the computer! Cannot "
print " perform following tests, stopping."
if print_traceback:
print "** Full traceback:"
# Indent
print "\n".join([" {}".format(l) for l
in traceback.format_exc().splitlines()])
else:
print "{}: {}".format(e.__class__.__name__, e.message)
print ("(use the --traceback option to see the "
"full traceback)")
succeeded = False
[docs] def _computer_test_get_jobs(self, transport, scheduler, dbauthinfo):
"""
Internal test to check if it is possible to check the queue state.
:note: exceptions could be raised
:param transport: an open transport
:param scheduler: the corresponding scheduler class
:param dbauthinfo: the dbauthinfo object (from which one can get
computer and aiidauser)
:return: True if the test succeeds, False if it fails.
"""
print "> Getting job list..."
found_jobs = scheduler.getJobs(as_dict=True)
# For debug
# for jid, data in found_jobs.iteritems():
# print jid, data['submission_time'], data['dispatch_time'], data['job_state']
print " `-> OK, {} jobs found in the queue.".format(len(found_jobs))
return True
[docs] def _computer_create_temp_file(self, transport, scheduler, dbauthinfo):
"""
Internal test to check if it is possible to create a temporary file
and then delete it in the work directory
:note: exceptions could be raised
:param transport: an open transport
:param scheduler: the corresponding scheduler class
:param dbauthinfo: the dbauthinfo object (from which one can get
computer and aiidauser)
:return: True if the test succeeds, False if it fails.
"""
import tempfile
import datetime
import os
file_content = "Test from 'verdi computer test' on {}".format(
datetime.datetime.now().isoformat())
print "> Creating a temporary file in the work directory..."
print " `-> Getting the remote user name..."
remote_user = transport.whoami()
print " [remote username: {}]".format(remote_user)
workdir = dbauthinfo.get_workdir().format(
username=remote_user)
print " [Checking/creating work directory: {}]".format(workdir)
try:
transport.chdir(workdir)
except IOError:
transport.makedirs(workdir)
transport.chdir(workdir)
with tempfile.NamedTemporaryFile() as f:
fname = os.path.split(f.name)[1]
print " `-> Creating the file {}...".format(fname)
remote_file_path = os.path.join(workdir, fname)
f.write(file_content)
f.flush()
transport.putfile(f.name, remote_file_path)
print " `-> Checking if the file has been created..."
if not transport.path_exists(remote_file_path):
print "* ERROR! The file was not found!"
return False
else:
print " [OK]"
print " `-> Retrieving the file and checking its content..."
fd, destfile = tempfile.mkstemp()
os.close(fd)
try:
transport.getfile(remote_file_path, destfile)
with open(destfile) as f:
read_string = f.read()
print " [Retrieved]"
if read_string != file_content:
print ("* ERROR! The file content is different from what was "
"expected!")
print "** Expected:"
print file_content
print "** Found:"
print read_string
return False
else:
print " [Content OK]"
finally:
os.remove(destfile)
print " `-> Removing the file..."
transport.remove(remote_file_path)
print " [Deleted successfully]"
return True
[docs] def computer_enable(self, *args):
"""
Enable a computer.
"""
if not is_dbenv_loaded():
load_dbenv()
import argparse
from django.core.exceptions import ObjectDoesNotExist
from aiida.common.exceptions import NotExistent
from aiida.orm.implementation import User
parser = argparse.ArgumentParser(
prog=self.get_full_command_name(),
description='Enable a computer')
# The default states are those that are shown if no option is given
parser.add_argument('-u', '--only-for-user', type=str, metavar='EMAIL',
dest='user_email',
help="Enable a computer only for the given user. "
"If not specified, enables the computer "
"globally.",
)
parser.add_argument('computer', type=str,
help="The name of the computer that you "
"want to enable")
parsed_args = parser.parse_args(args)
user_email = parsed_args.user_email
computername = parsed_args.computer
try:
computer = self.get_computer(name=computername)
except NotExistent:
print >> sys.stderr, "No computer exists with name '{}'".format(
computername)
sys.exit(1)
if user_email is None:
if computer.is_enabled():
print "Computer '{}' already enabled.".format(computername)
else:
computer.set_enabled_state(True)
print "Computer '{}' enabled.".format(computername)
else:
user_list = User.search_for_users(email=user_email)
if user_list is None or len(user_list) == 0:
print >> sys.stderr, ("No user with email '{}' in the "
"database.".format(user_email))
sys.exit(1)
user = user_list[0]
try:
dbauthinfo = computer.get_dbauthinfo(user._dbuser)
if not dbauthinfo.enabled:
dbauthinfo.enabled = True
dbauthinfo.save()
print "Computer '{}' enabled for user {}.".format(
computername, user.get_full_name())
else:
print "Computer '{}' was already enabled for user {} {}.".format(
computername, user.first_name, user.last_name)
except NotExistent:
print >> sys.stderr, ("User with email '{}' is not configured "
"for computer '{}' yet.".format(
user_email, computername))
sys.exit(1)
[docs] def computer_disable(self, *args):
"""
Disable a computer.
If a computer is disabled, AiiDA does not try to connect to it to
submit new calculations or check for the state of existing calculations.
Useful, for instance, if you know that a computer is under maintenance.
"""
if not is_dbenv_loaded():
load_dbenv()
import argparse
from aiida.common.exceptions import NotExistent
parser = argparse.ArgumentParser(
prog=self.get_full_command_name(),
description='Disable a computer')
# The default states are those that are shown if no option is given
parser.add_argument('-u', '--only-for-user', type=str, metavar='EMAIL',
dest='user_email',
help="Disable a computer only for the given user. "
"If not specified, disables the computer "
"globally.",
)
parser.add_argument('computer', type=str,
help="The name of the computer that you "
"want to disable")
parsed_args = parser.parse_args(args)
user_email = parsed_args.user_email
computername = parsed_args.computer
try:
computer = self.get_computer(name=computername)
except NotExistent:
print >> sys.stderr, "No computer exists with name '{}'".format(
computername)
sys.exit(1)
if user_email is None:
if not computer.is_enabled():
print "Computer '{}' already disabled.".format(computername)
else:
computer.set_enabled_state(False)
print "Computer '{}' disabled.".format(computername)
else:
user_list = User.search_for_users(email=user_email)
if user_list is None or len(user_list) == 0:
print >> sys.stderr, ("No user with email '{}' in the "
"database.".format(user_email))
sys.exit(1)
user = user_list[0]
try:
dbauthinfo = computer.get_dbauthinfo(user)
if dbauthinfo.enabled:
dbauthinfo.enabled = False
dbauthinfo.save()
print "Computer '{}' disabled for user {}.".format(
computername, user.get_full_name())
else:
print("Computer '{}' was already disabled for user {} {}."
.format(computername, user.first_name, user.last_name))
except NotExistent:
print >> sys.stderr, ("User with email '{}' is not configured "
"for computer '{}' yet.".format(
user_email, computername))
[docs] def get_computer_names(self):
"""
Retrieve the list of computers in the DB.
ToDo: use an API or cache the results, sometime it is quite slow!
"""
from aiida.orm.querybuilder import QueryBuilder
qb = QueryBuilder()
qb.append(type='computer', project=['name'])
if qb.count() > 0:
return zip(*qb.all())[0]
else:
return None
[docs] def get_computer(self, name):
"""
Get a Computer object with given name, or raise NotExistent
"""
from aiida.orm.computer import Computer as AiidaOrmComputer
return AiidaOrmComputer.get(name)